python类属性访问魔法方法
python类属性访问魔法方法
本文主要讲述类(python3.6)属性访问的魔法方法: __get__, __getattr__, __getattribute__, ___set__, __setattr__
(本文对类属性和实例属性不加严谨的表述)
1. __getattr__, __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个种情况:
- 不返回(None)
- 返回某种值 但需要注意的是,千万不能返回可能不存在的属性,否则会造成无限递归, 如上面return self.age 。除此之外, p.age虽然输出了’hello’, 但实际上,p实例依然是没有age这个属性的
- (建议)直接引发异常
接下来我们看看__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
"""
看输出结果就很容易明白这个方法的作用,但给属性赋值时,会去调用这个方法,当自己实现了这个方法了要注意以下的点:
- 必须去给属性赋值,可靠的方法是直接继承父类方法,否则像p.name='Mike’将会失去作用,p.name的值永远是空
- 千万不要尝试自己去给属性赋值,如上面的注释的setattr(self, key, value),这样会导致无限嵌套调用
- __setattr__的一个作用,可以限制给类实例动态增加属性,python允许给实例动态增加属性,如上属例子,我们尝试p.name2 = ‘Ben’,一样可以给赋值成功,我们可以通过这个方法进行限制:
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)
2. 、__getattribute__
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__无论属性存在与否都会被调用, 和之前都一样,要注意无限嵌套调用, 它的优先级在属性访问的时候级别是最高的。
3. __get__、__set__
把这个方法放到最后来说,是因为这个方法的行为十分特别,实现了__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还有必须两个参数:
- instance: 表示调用Descirptor的类实例或None。如上面当实例访问就是b;如果是直接Blog.per这样访问,那么它的值为None
- typed: 表示调用Descirptor的类,如上面,就是Blog,但直接使用类访问时
需要注意的时候,如上面,__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个必须参数:
- instance: 表示调用Descirptor的类实例,如上面,就是b
- value:要赋予的值,如上面的’Ben’
4. __get__、__set__案例
这两个方法再某些场合十分有用,下面是类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
"""
- 将App的name和date属性和Config的name和date绑定
- 当访问或者操作app.name实际上是访问或者操作app.config[‘name’],与以下相比:
self. name = self.config['name']
上述代码,如果name只是一个只用于读(访问)的属性,那么与使用Descriptor区别不大,但如果name可以被改变,但执行app.name = ‘xxx’, 那么name属性和config将彻底失去关联。
5 . 总结
-
__getattr__在访问实例属性不存在的时候,会被调用
-
__setattr__当实例属性被赋值时会被调用
-
__getattribute__在访问任何存在与否的属性都会被调用
谨慎使用这三个魔法方法, 使用不当会产生很多负面后果,如无限嵌套 -
实现了__get__的类被称为Descriptior,通过其他类(或者实例)去访问这个类实例的时候会被调用
-
__set__, 与__get__一起使用,通过其他类(或者实例)去赋值这个类实例的时候会被调用
-
通过类访问属性,通过A.attr访问属性的规则为:
- 如果MetaClass中有__getattribute__,则直接返回该__getattribute__的结果。
- 如果attr是个Descriptor,则直接返回Descriptor的__get__的结果。
- 如果attr是通过属性,则直接返回attr的值
- 如果类中没有attr,且MetaClass中定义了__getattr__,则调用MetaClass中的__getattr__
如果类中没有attr,且MetaClass中没有定义__getattr__,则抛出异常AttributeError
-
通过类设置属性, 通过A.attr=val给属性赋值时:
- 如果MetaClass中定义了__setattr__,则执行该__setattr__
- 如果该属性是Descriptor,且定义了__set__,则执行Descriptor的__set__
- 如果是普通属性或None-data Descriptor,则直接令attr=val
- 如果属性不存在,则动态给类添加该属性,然后进行赋值