本文主要讲述类(python3.6)属性访问的魔法方法: __get__, __getattr__, __getattribute__, ___set__, __setattr__
(本文对类属性和实例属性不加严谨的表述)
先定义一个类:
class Person(object):
name = ''
def __getattr__(self, item):
# 当获取这个属性不存在就会访问这个方法
print('__getattr__ is called')
# raise AttributeError('class Person don\' have the attr {0}'.format(item))
return 'hello'
if __name__ == '__main__':
p = Person()
print(p.name)
p.name = 'Mike'
print(p.name)
print(p.age)
"""
输出:
(空)
Mike
__getattr__ is called
hello
"""
可以看出__getattr__只有在属性不存在的时候才会被访问,建议返回值3个种情况:
接下来我们看看__setattr__, 继续使用上面的类
class Person(object):
name = ''
def __getattr__(self, item):
# 当获取这个属性不存在就会访问这个方法
print('__getattr__ is called')
# raise AttributeError('class Person don\'t have the attr {0}'.format(item))
return 'hello'
def __setattr__(self, key, value):
print('__setattr__ is called')
value += str(int(time.time())) # 时间戳
# setattr(self, key, value)
super(Person, self).__setattr__(key, value)
if __name__ == '__main__':
p = Person()
print(p.name)
p.name = 'Mike'
print(p.name)
"""
(空)
__setattr__ is called
Mike1542524409
"""
看输出结果就很容易明白这个方法的作用,但给属性赋值时,会去调用这个方法,当自己实现了这个方法了要注意以下的点:
def __setattr__(self, key, value):
if not hasattr(self, key):
raise AttributeError
print('__setattr__ is called')
value += str(int(time.time())) # 时间戳
# setattr(self, key, value)
super(Person, self).__setattr__(key, value)
class Person(object):
name = ''
def __getattr__(self, item):
print('__getattr__ is called')
raise AttributeError('Person don\'t have the attr {0}'.format(item))
def __getattribute__(self, item):
print('__getattribute__ is called')
return super(Person, self).__getattribute__(item)
if __name__ == '__main__':
p = Person()
p.name = 'Mike'
print(p.name)
print(p.name2)
"""
# print(p.name)
__getattribute__ is called
Mike
# print(p.name2)
__getattribute__ is called
__getattr__ is called
...
AttributeError: Person don't have the attr name2
"""
可以看出__getattribute__无论属性存在与否都会被调用, 和之前都一样,要注意无限嵌套调用, 它的优先级在属性访问的时候级别是最高的。
把这个方法放到最后来说,是因为这个方法的行为十分特别,实现了__get__我们称为Descriptor,直接看例子:
class Person(object):
name = ''
def __get__(self, instance, typed):
print('__get__ is called')
return self.name
class Blog:
per = Person()
per.name = 'Mike'
if __name__ == '__main__':
b = Blog()
print(b.per)
"""
输出:
__get__ is called
Mike
"""
当一个类的属性赋予成了Descirptor(类),当这个类再去访问这个属性当时候,就会去访问这个Descriptor的__get__方法,如上面,当去访问b的per属性,会直接返回name的值。
__get__除了self还有必须两个参数:
需要注意的时候,如上面,__get__方法直接返回了self.name的值,所以当你想这样去b.per.name是不可行的,因为b.per已经不是Person的实例了。如果需要这样访问,可以直接return self;
同时实现了__get__和__set__则称为Data Descriptor,如果只实现了__get__则称为Non-data Descriptor。
# 省略其他
def __set__(self, instance, value):
print('__set__ is called')
self.name = value + '123'
if __name__ == '__main__':
b = Blog()
print(b.per)
print('-------------')
b.per = 'Ben'
print(b.per)
"""
__get__ is called
Mike
-------------
__set__ is called
__get__ is called
Ben123
"""
__set__除了self还有2个必须参数:
这两个方法再某些场合十分有用,下面是类Flask的Config配置时的一个简化代码,它的设计巧妙运用Descriptor
class ConfigAttr:
def __init__(self, name):
self._name = name
def __get__(self, obj, typed=None):
if obj is None:
return 'None of app'
return obj.config[self._name]
def __set__(self, obj, value):
obj.config[self._name] = value
class App:
default_config = {
'name': 'app',
'date': '2018-11-15',
'lang': 'zh',
}
name = ConfigAttr('name')
date = ConfigAttr('date')
def __init__(self, name=None):
self.config = dict()
self.config.update(self.default_config)
if name is not None:
self.config['name'] = name
if __name__ == '__main__':
# --- 测试__get__
app = App('hello')
print(app.name)
app2 = App()
print(app2.name)
print(App.name)
"""
输出
hello
app
None of app
"""
self. name = self.config['name']
上述代码,如果name只是一个只用于读(访问)的属性,那么与使用Descriptor区别不大,但如果name可以被改变,但执行app.name = ‘xxx’, 那么name属性和config将彻底失去关联。
__getattr__在访问实例属性不存在的时候,会被调用
__setattr__当实例属性被赋值时会被调用
__getattribute__在访问任何存在与否的属性都会被调用
谨慎使用这三个魔法方法, 使用不当会产生很多负面后果,如无限嵌套
实现了__get__的类被称为Descriptior,通过其他类(或者实例)去访问这个类实例的时候会被调用
__set__, 与__get__一起使用,通过其他类(或者实例)去赋值这个类实例的时候会被调用
通过类访问属性,通过A.attr访问属性的规则为:
通过类设置属性, 通过A.attr=val给属性赋值时: