Python高级用法之动态属性

基本用法:

1,使用__getattr__实现惰性/按需生成属性

Python中普通的实例属性, @property属性和描述符属性都必须预先定义好。但是有些场景下,例如ORM系统,我们事先并不知道类是否含有属性,需要根据需求动态生成属性。这种场景下,我们可以通过Python的__getattr__来实现动态按需生成属性。

如果类中定义了__getattr__,同时系统在类中对该类实例的__dict__中找不到待查询的属性,就会自动调用__getattr__方法。

举例如下:

class LazyDB(object):
    def __init__(self):
        self.exist = 1

    def __getattr__(self, item):
        print('__getattr__ is called')
        value = ' '.join(['default value: ', item])
        setattr(self, item, value)
        return value

data = LazyDB()
print("Before: ", data.__dict__)
print(data.foo) ##类LazyDB并没有预先定义属性foo,此处动态生成了属性foo
print("After: ", data.__dict__)
print(data.foo) ##此时类LazyDB已经有属性foo,直接返回了foo,不会再次调用__getattr__

###输出如下:
Before:  {'exist': 1}
__getattr__ is called
default value:  foo
After:  {'exist': 1, 'foo': 'default value:  foo'}
default value:  foo

2,使用__getattribute__对属性的访问做额外处理

假设我们需要在数据库中实现事物(transaction)处理,即每次在访问属性时,需要额外调用特殊方法检查数据库中对应的行是否有效,以及相关的事务是否依然开放。此时使用__getattr__无法实现这种功能,因为第二次访问属性时,Python会直接返回上首次调用时存储在__dict__中的属性值,而不会再次调用__getattr__插寻属性的状态。此种情况下我们需要使用__getattribute__,该方法在用户每次访问属性是都会被调用。

class LazyDB(object):
    def __init__(self):
        self.exist = 1

    def __getattribute__(self, item):
        print('__getattribute__ (%s) called' % item)
        try:
            return super().__getattribute__(item)
        except AttributeError:
            value = ' '.join(['default value: ', item])
            setattr(self, item, value)
            return value

data = LazyDB()
print(data.foo) ##每次访问类属性时都会被调用,此处是第1次调用
print(data.foo) ##每次访问类属性时都会被调用,此处是第2次调用
print(data.__dict__)  ##每次访问类属性时都会被调用,此处是第3次待用

###输出如下:
__getattribute__ (foo) called
default value:  foo
__getattribute__ (foo) called
default value:  foo
__getattribute__ (__dict__) called
{'exist': 1, 'foo': 'default value:  foo'}

3,使用__setattr__给属性赋值时,对属性赋值操作做一些额外的处理

如果类中定义了__setattr__,那么对类对象的每一次赋值,无论是直接赋值还是通过内置函数setattr()函数赋值,都会触发__setattr__方法。

class SavingDB(object):
    def __setattr__(self, key, value):
        print("Called __setattr__")
        super().__setattr__(key, value)

data = SavingDB()
data.foo = 134  ##首次调用
print(data.foo)  
data.__setattr__('foo', 'hello')  ##第2次调用
print(data.foo)

###输出
Called __setattr__
134
Called __setattr__
hello

4,注意事项

在使用__getattribute__和__setattr__时,因为每次访问属性都会被调用,有可能产生反复递归。例如:在查询对象的属性时,从对象的内部字典中,搜寻与待查询属性的关联值时

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

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

data = LazyDB({'foo': 3})
print(data.foo)

##输出:
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
.......
RecursionError: maximum recursion depth exceeded while calling a Python object

出现反复递归原因在于,__getattribute__里面访问self._data,这意味着会再次调用__getattribute__,然后会继续访问self._data,并无限次循环。解决办法是采用 super().__getattribute__('_data'),从实例的属性字典里面直接获取_data属性值

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

    def __getattribute__(self, item):
        print('__getattribute__ (%s) called' % item)
        dt = super().__getattribute__('_data')
        print(dt)
        return dt[item]

data = LazyDB({'foo': 3})
print(data.foo)

##输出
__getattribute__ (foo) called
{'foo': 3}
3

项目实践:

1,根据属性赋值,动态生成url

class URLGenerator(object):
    def __init__(self, root_url):
        self.url = root_url

    def __getattr__(self, item):
        method = ["get", "post", "put"]
        if item.lower() in method:
            return self.url
        else:
            return URLGenerator('{}/{}'.format(self.url, item))

url_gen = URLGenerator("http://XXXXX.com")
url_data = url_gen.show.users.list.get
print(url_data)

##输出
http://XXXXX.com/show/users/list

 

你可能感兴趣的:(Python)