본문 바로가기

Computer/Python

Property, Setter, Getter

반응형

다른 언어를 사용하다 보면 클래스의 attribute를 위한 getter, setter 메소드를 명시적으로 정의하는 경우가 많습니다. 하지만 이런 코드는 파이썬답지 않고 필드 값을 증가시키는 연산 등의 경우에는 이런 메서드를 사용하면 코드가 지저분해지게 됩니다. (e.g. r0.set_ohms(r0.get_ohms() - 40))

class OldResistor:
    def __init__(self, ohms):
        self._ohms = ohms

    def get_ohms(self):
        return self._ohms

    def set_ohms(self, ohms):
        self._ohms = ohms

r0 = OldResistor(50)
print(f'Before: {r0.get_ohms()}')
r0.set_ohms(10)
print(f'After: {r0.get_ohms()}')

 

이러한 유틸리티 메서드를 명시적으로 정의할 경우 클래스 인터페이스를 설계할 때 기능을 캡슐화하고, 필드 사용을 검증하고, 검증을 설정하기 쉬워지는 등의 도움이 되기도 합니다만 파이썬에서는 다음과 같이 단순한 공개 attribute로 구현해도 무방합니다. 따라서 필드 값을 변경시키는 코드가 더 자연스럽고 명확해집니다. (e.g. r1.ohms += 1e2)

class Resistor:
    def __init__(self, ohms):
        self.ohms = ohms
        self.voltage = 0
        self.current = 0

r1 = Resistor(50)
r1.ohms = 100

 

파이썬에서는 attribute가 설정될 때 특별한 기능을 수행해야 한다면 attribute를 @property 데코레이터와 대응하는 setter attribute로 옮겨갈 수 있습니다. 다음 코드는 Resistor 클래스를 상속하여 voltage 프로퍼티에 값을 대입하면 current 값이 바뀝니다. 코드가 제대로 작동하려면 세터와 게터의 이름이 우리가 의도한 프로퍼티 이름과 일치해야 합니다.

class VoltageResistance(Resistor):
    def __init__(self, ohms):
        super().__init__(ohms)
        self._voltage = 0

    @property
    def voltage(self):
        return self._voltage

    @voltage.setter
    def voltage(self, voltage):
        self._voltage = voltage
        self.current = self._voltage / self.ohms

r2 = VoltageResistance(100)
print(f'Before: {r2.current}')
r2.voltage = 10
print(f'After: {r2.current}')
  • r2.voltage = 10 으로 voltage 세터 메서드를 호출하기 전의 current 는 0이지만 voltage 세터 메서드 호출 이후에는 current 값은 0.1dl 이 됩니다.

 

프로퍼티에 대해 setter 메서드를 지정하면 타입을 검사하거나 클래스 프로퍼티에 전달된 값에 대한 검증을 수행할 수 있습니다. 다음 코드에서는 모든 저항값이 0 옴보다 큰지 확인하는 클래스를 정의합니다. r3.ohms=0 구문 실행 시 세터 메서드에서 ValueError가 발생합니다.

class BoundedResistance(Resistor):
    def __init__(self, ohms):
        super().__init__(ohms)

    @property
    def ohms(self):
        return self._ohms

    @ohms.setter
    def ohms(self, ohms):
        if ohms <= 0:
            raise ValueError(f'저항이 0보다 작습니다.')

        self._ohms = ohms

r3 = BoundedResistance(10)
r3.ohms = 0
  • 심지어 BoundedResistance(-5)와 같이 생성자에 잘못된 값을 넘기는 경우에도 에러가 발생합니다. 이는 생성자 실행 시 상위 클래스의 Resistance.__init__ 함수를 호출하고, 여기서 self.ohms=-5 대입문이 실행되어 ohms 세터 메서드가 호출되기 때문입니다.

더 나아가 @property를 사용해 부모 클래스에 정의된 attribute를 불변으로 만들 수도 있습니다.

class FixedResistance(Resistor):
    def __init__(self, ohms):
        super().__init__(ohms)

    @property
    def ohms(self):
        return self._ohms

    @ohms.setter
    def ohms(self, ohms):
        if hasattr(self, '_ohms'):
            raise AttributeError('Ohms is immutable')

        self._ohms = ohms

r4 = FixedResistance(10)
r4.ohms = 20

@property를 사용해 게터와 세터를 구현할 경우 게터 프로퍼티 메서드 안에서 다른 attribute를 설정하면 코드가 이상하게 작동할 수 있습니다. 따라서 관련히 있는 객체 상태를 @property.setter 메서드 안에서만 변경하는 것이 가장 좋고 느리거나 복잡한 작업의 경우 일반적인 메서드를 사용해서 이상한 부작용을 만들어내지 않는 것이 중요합니다. 또한, @property의 단점으로는 attribute를 처리하는 메서드가 하위 클래스 사이에서만 공유될 수 있고 서로 관련이 없는 클래스 사이에 같은 구현을 공유할 수는 없습니다.

반응형

'Computer > Python' 카테고리의 다른 글

파이썬의 Namespace와 Scope  (0) 2021.07.13
파이썬의 매개변수 전달 방식  (0) 2021.07.13
concurrent.futures를 이용한 병렬화  (0) 2021.06.27
데코레이터와 functools.wrap  (0) 2021.06.27
Public, Private Attributes  (0) 2021.06.27