about 3 years ago

Use getattr, getattribute, and setattr for Lazy Attributes

這個條款在講 __getattr____getattribute__ 以及 __setattr__

__getattr____getattribute__ 都可以動態地存取 attributes ,不同點在於如果 __dict__ 找不到才會呼叫 __getattr__,而 __getattribute__ 每次都會被呼叫到。

class LazyDB(object):
    def __init__(self):
        self.exists = 5

    def __getattr__(self, name):
        value = 'Value for %s' % name
        setattr(self, name, value)
        return value

class LoggingLazyDB(LazyDB):
    def __getattr__(self, name):
        print('Called __getattr__(%s)' % name)
        return super().__getattr__(name)

data = LoggingLazyDB()
print('exists:', data.exists)
print('foo:   ', data.foo)
print('foo:   ', data.foo)
class ValidatingDB(object):
    def __init__(self):
        self.exists = 5

    def __getattribute__(self, name):
        print('Called __getattribute__(%s)' % name)
        try:
            return super().__getattribute__(name)
        except AttributeError:
            value = 'Value for %s' % name
            setattr(self, name, value)
            return value

data = ValidatingDB()
print('exists:', data.exists)
print('foo:   ', data.foo)
print('foo:   ', data.foo)

可以控制什麼 attributes 不該被使用到,記得要丟 AttributeError

try:
    class MissingPropertyDB(object):
        def __getattr__(self, name):
            if name == 'bad_name':
                raise AttributeError('%s is missing' % name)
            value = 'Value for %s' % name
            setattr(self, name, value)
            return value

    data = MissingPropertyDB()
    data.foo  # Test this works

    data.bad_name
except:
    logging.exception('Expected')
else:
    assert False

__setattr__ 每次都會被呼叫到。

class SavingDB(object):
    def __setattr__(self, name, value):
        # Save some data to the DB log

        super().__setattr__(name, value)

class LoggingSavingDB(SavingDB):
    def __setattr__(self, name, value):
        print('Called __setattr__(%s, %r)' % (name, value))
        super().__setattr__(name, value)

很重要的一點是 __setattr__ 以及 __getattribute__ 一定要呼叫父類的 __getattribute__ ,避免無窮遞迴下去。

這個會爆掉,因為存取 self._data 又會呼叫 __getattribute__

class BrokenDictionaryDB(object):
    def __init__(self, data):
        self._data = {}

    def __getattribute__(self, name):
        print('Called __getattribute__(%s)' % name)
        return self._data[name]

呼叫 super().__getattribute__('_data')

class DictionaryDB(object):
    def __init__(self, data):
        self._data = data

    def __getattribute__(self, name):
        data_dict = super().__getattribute__('_data')
        return data_dict[name]
← Effective Python 心得筆記: Item 31 Effective Python 心得筆記: Item 33 →
 
comments powered by Disqus