'Better way 47. 지연 계산 애트리뷰트가 필요하면, __getattr__, __getattribute__, __setattr__을 사용하라' 정리

Effective Python 2nd 파이썬 코딩의 기술 (교보문고 링크)을 제대로 이해하고자 블로그에 정리합니다.



현재 위치

Note
<6. Metaclasses and Attributes>
Item 47: Use __getattr__, __getattribute__, and __setattr__ for Lazy Attrubutes
Better way 47. 지연 계산 애트리뷰트가 필요하면, __getattr__, __getattribute__, __setattr__을 사용하라



__getattr__, __getattribute__, __setattr__ 를 사용하면, 애트리뷰트 접근 및 세팅때 특별한 일을 수행할 수 있습니다.
요런 애들을 object hook 이라고 한다는군요.



객체의 인스턴스 딕셔너리에 없는 애트리뷰트에 접근할 때 뭔가 처리해야 한다면 __getattr__를 구현합니다.

# Example 1
class LazyRecord:
    def __init__(self):
        self.exists = 5

    def __getattr__(self, name):
        print(f'* Called __getattr__({name!r}), populating instance dictionary')
        value = f'Value for {name}'
        setattr(self, name, value)
        print(f'* Returning {value!r}')
        return value


# Example 2
data = LazyRecord()
print('Before:', data.__dict__)  # Before: {'exists': 5}
print('First foo:   ', data.foo)
print('After: ', data.__dict__)  # After:  {'exists': 5, 'foo': 'Value for foo'}
print('Second foo:   ', data.foo)
print('Has peach: ', hasattr(data, 'peach'))  # Has peach:  True

Before: {'exists': 5}

* Called __getattr__('foo'), populating instance dictionary
* Returning 'Value for foo'
First foo:    Value for foo

After:  {'exists': 5, 'foo': 'Value for foo'}

Second foo:    Value for foo

* Called __getattr__('peach'), populating instance dictionary
* Returning 'Value for peach'
Has peach:  True

First foo 때만 __getattr__가 호출되었고, 인스턴스 딕셔너리에 ‘foo’가 추가 되었습니다.
hasattr를 호출해도 __getattr__가 호출되었습니다.


애트리뷰트에 접근할 때마다 뭔가 처리해야 한다면 __getattribute__를 구현합니다.

# Example 4
class ValidatingRecord:
    def __init__(self):
        self.exists = 5

    def __getattribute__(self, name):
        print(f'* Called __getattribute__({name!r})')
        try:
            value = super().__getattribute__(name)  # 실제 값을 가져올 때는 super() 로
            # value = self.name  # 요런식이면 또 __getattribute__를 불러스 무한 재귀에 빠짐
            print(f'* Found {name!r}, returning {value!r}')
            return value
        except AttributeError:
            value = f'Value for {name}'
            print(f'* Setting {name!r} to {value!r}')
            setattr(self, name, value)
            return value


data = ValidatingRecord()
print('exists:     ', data.exists)  # exists:      5
print('First foo:  ', data.foo)  # First foo:   Value for foo
print('Second foo: ', data.foo)  # Second foo:  Value for foo
print('Has foo: ', hasattr(data, 'foo'))  # Has foo:  True

실제 값을 가져올 때는 value = super().__getattribute__(name) 요렇게 super 를 사용해야 합니다.

* Called __getattribute__('exists')
* Found 'exists', returning 5
exists:      5

* Called __getattribute__('foo')
* Setting 'foo' to 'Value for foo'
First foo:   Value for foo

* Called __getattribute__('foo')
* Found 'foo', returning 'Value for foo'
Second foo:  Value for foo

* Called __getattribute__('foo')
* Found 'foo', returning 'Value for foo'
Has foo:  True

애트리뷰트에 접근할 때마다 __getattribute__가 호출됩니다.
hasattr()도 마찬가지 입니다.


인스턴스의 애트리뷰트 값을 설정할 때마다 뭔가 처리해야 한다면 __setattr__를 구현합니다.

# Example 9
class LoggingSavingRecord:
    def __setattr__(self, name, value):
        print(f'* Called __setattr__({name!r}, {value!r})')
        super().__setattr__(name, value)


data = LoggingSavingRecord()
print('Before: ', data.__dict__)  # Before:  {}
data.foo = 5
print('After:  ', data.__dict__)  # After:   {'foo': 5}
data.foo = 7
print('Finally:', data.__dict__)  # Finally: {'foo': 7}

실제 값을 세팅할 때는 super().__setattr__(name, value) 요렇게 super 를 사용해야 합니다.

Before:  {}
* Called __setattr__('foo', 5)
After:   {'foo': 5}
* Called __setattr__('foo', 7)
Finally: {'foo': 7}


책에서 챕터 마지막 부분에 적혀있는 내용입니다.

Tip
Use __getattr__ and __setattr__ to lazily load and save attributes for an object.
__getattr____setattr__을 사용해 객체의 애트리뷰트를 지연해 가져오거나 저장할 수 있다.
Tip
Understand that __getattr__ only gets called when accessing a missing attribute, whereas __getattribute__ gets called every time any attribute is accessed.
__getattr__은 애트리뷰트가 존재하지 않을 때만 호출되지만, __getattribute__는 애트리뷰트를 읽을 때마다 항상 호출된다는 점을 이해하라.
Tip
Avoid infinite recursion in __getattribute__ and __setattr__ by using methods from super() (i.e., the object class) to access instance attributes.
__getattribute____setattr__에서 무한 재귀를 피하려면 super()에 있는(즉, object 클래스에 있는) 메서드를 사용해 인스턴스 애트리뷰트에 접근하라.




Related Content