미리 정의되어 있지 않은 속성 값에 접근하려면 어떻게 해야할까?
다시 말해, 아래와 같은 클래스를 통해 인스턴스를 만들고, data.foo 와 같이 존재하지 않는 속성에 액세스 하려고 하면 에러가 날 것이다.
class LazyDB:
def __init__(self):
self.exists = 5
만약 이런 상황에서, data.foo와 같은 액세스를 했을 때도 에러 없이 동작하도록 하려면 어떻게 해야할까?
@property 메서드, 디스크립터로는 이렇게 할 수 없다.
이럴때 __getattr__ 매직 메소드를 사용하면 된다.
class LazyDB:
def __init__(self):
self.exists = 5
def __getattr__(self, name):
value = f'Value for {name}'
setattr(self, name, value)
return value
이랬을 때 아래와 같이 동작한다.
data = LazyDB()
print(f'Before: {data.__dict__}')
print(f'foo: {data.foo}')
print(f'After: {data.__dict__}')
>>>
Before: {'exists': 5}
foo: Value for foo
After: {'exists': 5, 'foo': 'Value for foo'}
__getattr__ 메소드는 인스턴스 속성으로 존재하지 않을때 (즉, 인스턴스 딕셔너리에 없을때)만 호출된다.
다시말해, 존재하는 속성에 접근할 때는 호출되지 않는다.
만약, 속성이 존재하든, 존재하지 않든 매번 호출되게 하려면 __getattribute__ 메소드를 이용하면 된다.
class ValidatingDB:
def __init__(self):
self.exists = 5
def __getattribute__(self, name):
print(f'Called __getattribute__{name}')
try:
return super().__getattribute__(name)
except AttributeError:
value = f'Value for {name}'
setattr(self, name, value)
return value
data = ValidatingDB()
print('exists: {data.exists}')
print('foo: {data.foo}')
print('foo: {data.foo}')
>>>
Called __getattribute__(exists)
exists: 5
Called __getattribute__(foo)
foo: Value for foo
Called __getattribute__(foo)
foo: Value for foo
파이썬 코드로 범용적인 기능을 구현할 때 종종 내장 함수 hasattr로 프로퍼티가 있는지 확인하고 내장 함수 getattr로 프로퍼티 값을 가져온다. 이 함수들도 __getattr__을 호출하기 전에 인스턴스 딕셔너리에서 속성 이름을 찾는다.
또한, __getattribute__를 구현한 클래스인 경우, hasattr이나 getattr을 호출할 때마다 __getattribute__가 실행된다.
이러한 특성 때문에, __getattribute__와 __setattr__을 사용할 때 부딪히는 문제는 객체의 속성에 접근할 때마다 (심지어 원하지 않을 때도) 호출된다는 점이다. 예를 들어 객체의 속성에 접근하면 실제로 딕셔너리에서 키를 찾게 하고 싶다고 해보자.
class BrokenDictionaryDB:
def __init__(self, data):
self._data = {}
def __getattribute__(self, name):
print(f'Called __getattribute__{name}')
return self._data[name]
그러려면, 위와 같이 __getattribute__ 메서드에서 self._data에 접근해야한다.
하지만 실제로 시도해보면 파이썬이 스택의 한계에 도달할 때까지 재귀 호출을 하게 되어 결국 프로그램이 중단된다.
문제는 __getattribute__가 self._data에 접근하면 __getattribute__가 다시 실행되고, 다시 self._data에 접근한다는 점이다.
해결책은 인스턴스에서 super().__getattribute__ 메서드로 인스턴스 속성 딕셔너리에서 값을 얻어오는 것이다.
이렇게 하면 재귀 호출을 피할 수 있다.
class DictionaryDB:
def __init__(self, data):
self._data = {}
def __getattribute__(self, name):
data_dict = super().__getattribute__('_data')
return data_dict[name]
마찬가지의 이유로 객체의 속성을 수정하는 __setattr__ 메서드에서도 super().__setattr__을 사용해야 한다.
'Programming Language > Python' 카테고리의 다른 글
Effective Python. 메타클래스로 클래스 속성에 주석을 달자. (0) | 2021.03.03 |
---|---|
Effective Python. 메타클래스로 서브클래스를 검증하자. (0) | 2021.03.03 |
Effective Python. 재사용 가능한 @property 메서드에는 디스크립터를 사용하자. (0) | 2021.03.02 |
Effective Python. @property, @{property}.setter 사용 (0) | 2021.03.02 |
Effective Python. 메타클래스와 속성 (0) | 2021.03.02 |