Python의 마법 방법 VI

2024. 4. 12. 19:28python/advanced

설명자를 통해 속성 관리

Python 설명자는 특정 사용자 정의 클래스의 속성을 세부적으로 제어할 수 있는 클래스입니다. 설명자를 사용하여 특정 클래스의 속성 위에 함수와 유사한 동작을 추가할 수 있습니다.

설명자 클래스에는 최소한 .__get__()특수 메서드가 필요합니다. 그러나 전체 설명자 프로토콜은 다음 메서드로 구성됩니다.

방법설명
.get(self, instance, type=None) 관리되는 속성의 현재 값을 검색할 수 있는 Getter 메서드
.set(self, instance, value) 관리되는 속성에 새 값을 설정할 수 있는 Setter 메서드
.delete(self, instance) 포함 클래스에서 관리되는 속성을 제거할 수 있는 삭제자 메서드
.set_name(self, owner, name) 관리 속성의 이름을 정의할 수 있는 이름 설정 방법

설명자가 어떤 용도로 사용되는지 설명하기 위해 shapes.py 모듈이 있다고 가정해 보겠습니다. Circle, Square 및 Rectangle 클래스를 정의했습니다. 원의 반경, 정사각형의 변 등과 같은 매개변수의 유효성을 검사해야 한다는 것을 깨달았습니다. 또한 유효성 검사 논리가 이러한 모든 매개 변수에 공통된다는 점도 언급했습니다.

이 상황에서는 설명자를 사용하여 제공된 값이 양수인지 확인하는 유효성 검사 논리를 관리할 수 있습니다. 이 설명자의 가능한 구현은 다음과 같습니다.

(참고) 아래 코드를 shapes.py 파일에 추가 하십시요.

In [ ]:
class PositiveNumber:
    def __set_name__(self, owner, name):
        self._name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, int | float) or value <= 0:
            raise ValueError("positive number expected")
        instance.__dict__[self._name] = value

이 클래스에서는 먼저 .__set_name__()특수 메서드를 정의합니다. owner 인수는 포함 클래스를 나타내고 name 인수는 속성 이름을 나타냅니다.

그런 다음 .__get__() 메서드를 정의합니다. 이 메서드는 인스턴스와 포함 클래스에 대한 참조를 사용합니다. 이 메서드 내에서 .__dict__ 특수 속성을 사용하여 인스턴스의 네임스페이스에 액세스하고 관리되는 속성의 값을 검색합니다.

마지막으로, 포함 클래스의 인스턴스와 관리되는 속성에 대한 새 값을 사용하는 .__set__() 메서드를 정의합니다. 이 방법에서는 제공된 값이 양수가 아닌지 확인하고, 이 경우 ValueError 예외를 발생시킵니다. 그렇지 않으면 .__dict__ 네임스페이스를 다시 사용하여 관리 속성에 입력 값을 할당합니다.

이제 이 설명자를 사용하여 모양의 매개변수를 정의할 수 있습니다.

(참고) 아래 코드를 shapes1.py로 저장하세요.

In [ ]:
import math

# ...

class Circle:
    radius = PositiveNumber()

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return round(math.pi * self.radius**2, 2)

class Square:
    side = PositiveNumber()

    def __init__(self, side):
        self.side = side

    def area(self):
        return round(self.side**2, 2)

class Rectangle:
    height = PositiveNumber()
    width = PositiveNumber()

    def __init__(self, height, width):
        self.height = height
        self.width = width

    def area(self):
        return self.height * self.width

이 코드에서 강조 표시된 줄은 PositiveNumber 설명자를 사용하여 관리 속성을 생성하는 방법을 보여줍니다. 관리되는 속성은 대응하는 인스턴스 속성이 있는 클래스 속성 이어야 합니다.

도형이 동작하는 방식은 다음과 같습니다.

In [ ]:
from shapes1 import Circle, Square, Rectangle

circle = Circle(-10)

모든 모양은 인스턴스화시 입력 매개변수의 유효성을 검사합니다. 이는 모두가 자신의 매개변수를 관리하기 위해 귀하의 PositiveNumber 사용하기 때문입니다. 이렇게 하면 모든 클래스에서 유효성 검사 코드를 재사용할 수 있습니다.

Iterator 및 Iterable을 통한 반복 지원

Python에서 반복자와 반복 가능 항목은 서로 다르지만 데이터 스트림이나 컨테이너를 반복해야 할 때 유용한 두 가지 관련 도구입니다. Iterator는 반복 프로세스를 지원하고 제어하는 ​​반면, Iterable은 일반적으로 반복할 수 있는 데이터를 보유합니다.

반복자와 반복 가능 항목은 Python 프로그래밍의 기본 구성 요소입니다. 거의 모든 프로그램에서 직접 또는 간접적으로 사용하게 됩니다. 다음 섹션에서는 특수 메서드를 사용하여 사용자 정의 클래스를 반복자 및 반복 가능자로 전환하는 방법에 대한 기본 사항을 배웁니다.

반복자 만들기

Python에서 반복자를 만들려면 두 가지 특별한 메서드가 필요합니다. 이러한 메서드를 구현하면 반복 프로세스를 제어할 수 있습니다. for 루프나 다른 반복 구성 에서 클래스를 사용할 때 Python이 항목을 검색하는 방법을 정의합니다.

아래 표는 반복자를 구성하는 메서드를 보여줍니다. 이는 반복자 프로토콜로 알려져 있습니다 .

방법설명
.__iter__() 반복자를 초기화하기 위해 호출됩니다. 반복자 객체를 반환해야 합니다.
.__next__() 반복자를 반복하기 위해 호출됩니다. 데이터 스트림의 다음 값을 반환해야 합니다.

보시다시피 두 가지 방법 모두 각자의 책임이 있습니다. .iter()에서는 일반적으로 self를 반환합니다. .next()에서는 데이터 스트림의 다음 값을 순서대로 반환해야 합니다. 이 메서드는 데이터 스트림이 소진되면 topIteration를 발생시켜야 합니다. 이런 식으로 Python은 반복이 끝났다는 것을 알고 있습니다.

사용자 정의 클래스에 이 두 가지 메서드를 구현하면 해당 클래스를 반복자로 전환할 수 있습니다. 예를 들어, 피보나치 수열의 숫자에 대한 반복자를 제공하는 클래스를 생성한다고 가정해 보겠습니다. 이러한 상황에서는 다음 솔루션을 작성할 수 있습니다.

(참고) 아래 코드를 fibonacci.py로 저장하세요.

In [ ]:
class FibonacciIterator:
    def __init__(self, stop=10):
        self._stop = stop
        self._index = 0
        self._current = 0
        self._next = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self._index < self._stop:
            self._index += 1
            fib_number = self._current
            self._current, self._next = (
                self._next,
                self._current + self._next,
            )
            return fib_number
        else:
            raise StopIteration

이 FibonacciIterator 클래스에서는 먼저 몇 가지 속성을 초기화합니다. ._stop 속성은 클래스가 반환할 값의 수를 정의합니다. 기본값은 10입니다. ._index 속성은 시퀀스에서 현재 피보나치 값의 인덱스를 보유합니다. ._current 및 ._next 속성은 각각 피보나치 수열의 현재 값과 다음 값을 나타냅니다.

.__iter__()에서는 현재 객체인 self를 반환하기만 하면 됩니다. 이 방법은 Python에서 반복자를 만들 때 일반적입니다.

.__next__() 메서드는 좀 더 정교합니다. 이 방법에는 현재 시퀀스 인덱스가 ._stop 값에 도달하지 않았는지 확인하는 조건이 있으며, 이 경우 현재 인덱스를 증가시켜 반복 프로세스를 제어합니다. 그런 다음 현재 인덱스에 해당하는 피보나치 수를 계산하여 결과를 .__next__() 호출자에게 반환합니다.

._index가 ._stop값으로 증가 하면, StopIteration 예외가 발생하여 반복 프로세스가 종료됩니다.

이제 클래스를 반복할 준비가 되었습니다. 동작 방식은 다음과 같습니다.

In [ ]:
from fibonacci import FibonacciIterator

for fib_number in FibonacciIterator():
    print(fib_number)

FibonacciIterator 클래스는 실시간으로 새 값을 계산하여 루프에서 클래스를 사용할 때 요청에 따라 값을 생성합니다. 피보나치 값의 수가 10 까지 커지면 클래스는 StopIteration 예외를 발생시키고 Python은 루프를 종료합니다. 귀하의 클래스는 이제 동작하는 반복자입니다.

Iterable 빌드

Iterable은 Iterator와 약간 다릅니다. 일반적으로 컬렉션이나 컨테이너는 .__iter__()특수 메서드를 구현할 때 반복 가능합니다.

예를 들어, Python 내장 컨테이너 유형(예: 목록, 튜플, 사전 및 세트)은 반복 가능한 객체입니다. 반복할 수 있는 데이터 스트림을 제공합니다. 그러나 .__next__() 반복 프로세스를 구동하는 방법은 제공하지 않습니다. 따라서 for 루프를 사용하여 반복 가능한 항목을 반복하기 위해 Python은 암시적으로 반복자를 생성합니다.

예를 들어, Stack class로 돌아가세요. 클래스를 반복 가능하도록 아래 코드로 업데이트합니다.

(참고) 아래 코드를 stack.py 파일에 추가하세요.

In [ ]:
class Stack:
    # ...

    def __iter__(self):
        return iter(self.items[::-1])

.__iter__() 구현은 현재 객체인 self를 반환하지 않습니다. 대신, 이 메서드는 내장 iter() 함수를 사용하여 스택의 항목에 대한 반복자를 반환합니다. 이 iter() 함수는 입력 반복 가능 항목에 기본 .__next__() 메서드를 제공하여 완전한 기능을 갖춘 반복자가 될 수 있도록 합니다.

스택에서 요소가 팝되는 순서와 일치하도록 반복하기 전에 스택의 항목 목록을 반대로 바꿉니다. Python 대화형 세션에서 다음 코드를 실행하여 Stack 새로운 기능을 사용해 보세요.

In [ ]:
from stack import Stack

stack = Stack([1, 2, 3, 4])

for value in stack:
    print(value)

이 코드 조각에서는 먼저 숫자 4개로 Stack의 새 인스턴스를 만듭니다. 그런 다음 인스턴스에 대해 for 루프를 시작합니다. 루프는 각 반복에서 현재 값을 인쇄합니다. 이는 .pop()을 반복적으로 호출하면 순서 요소가 팝되는 것을 보여줍니다. 이제 클래스 Stack가 반복을 지원합니다.

'python > advanced' 카테고리의 다른 글

Python의 마법 방법 VII  (0) 2024.04.13
Python의 마법 방법 V  (0) 2024.04.11
Python의 마법 방법 IV  (0) 2024.04.11
Python의 마법 방법 III  (0) 2024.04.09
Python의 마법 방법 II  (0) 2024.04.08