Python3 描述符 (__get__/__set__/__delete__/__getattr__/__getattribute__/__setattr__/__delattr__)
描述符
Python 中,通过使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。
本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类。
描述符是 Python 中复杂属性访问的基础,它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。
描述符类基于以下 3 个特殊方法(魔法方法),换句话说,这 3 个方法组成了描述符协议:
- 方法的原型为:
1. __get__(self, instance, owner),调用一个属性时,触发
2. __set__(self, instance, value),为一个属性赋值时,触发
3. __delete__(self, instance),采用del删除属性时,触发
参数:
self : 描述符实例 instance:相当于例子中的实例book value: 就是要赋予的值
owner:是所有者的类
#描述符类 class revealAccess: def __init__(self, initval = None, name = 'var'): self.val = initval self.name = name def __get__(self, obj, objtype): print("Retrieving",self.name) return self.val def __set__(self, obj, val): print("updating",self.name) self.val = val class myClass: x = revealAccess(10,'var "x"') y = 5 m = myClass() # 访问m.x。会先触发__getattribute__方法 # 由于x属性的值是一个描述符,会触发它的__get__方法 print(m.x) # 设置m.x的值。对描述符进行赋值,会触发它的__set__方法 # 在__set__方法中还会触发__setattr__方法(self.val = val) m.x = 20 print(m.x) print(m.y) """ # 查看m和MyClass的__dict__,发现这与对描述符赋值之前一样。 # 这一点与一般属性的赋值不同,可参考上述的__setattr__方法。 # 之所以前后没有发生变化,是因为变化体现在描述符对象上, # 而不是实例对象m和类MyClass上。 >>> m.__dict__ {} >>> MyClass.__dict__ dict_proxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>, '__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, 'x': <__main__.RevealAccess at 0x5130080>, 'y': 5}) """ """ 运行结果 Retrieving var "x" 10 updating var "x" Retrieving var "x" 20 5 """
"""
其中RevealAccess
类的实例是作为MyClass
类属性x
的值存在的。而且RevealAccess
类定义了__get__
、__set__
方法,它是一个描述符对象。
注意,描述符对象的__get__
、__set__
方法中使用了诸如self.val
和self.val = val
等语句,这些语句会调用__getattribute__
、__setattr__
等方法,
这也说明了__getattribute__
、__setattr__
等方法在控制访问对象属性上的一般性(一般性是指对于所有属性它们的控制行为一致),
以及__get__
、__set__
等方法在控制访问对象属性上的特殊性(特殊性是指它针对某个特定属性可以定义不同的行为)。
"""
class Desc(): def __get__(self, instance, owner): print("__get__") print("self=",self) print("instance=",instance) print("owner=",owner) print("=="*50,"\n") class People(): x = Desc() p1 = People() p1.x print(People.__dict__) print('-'*100) print(p1.__dict__) """ 执行结果如下 __get__ self= <__main__.Desc object at 0x02C40130> instance= <__main__.People object at 0x02C400F0> owner= <class '__main__.People'> ==================================================================================================== {'__module__': '__main__', 'x': <__main__.Desc object at 0x02C40130>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None} ---------------------------------------------------------------------------------------------------- {} """
实现了 __set__ 和 __get__ 方法的描述符类被称为数据描述符;
反之,如果只实现了 __get__ 方法,则称为非数据描述符。
如果这些方法中任何一个被定义在一个对象中,这个对象就是一个描述符。
Python给出的方案是:__getattribute__
、__getattr__
、__setattr__
、__delattr__
等方法用来实现属性查找、设置、删除的一般逻辑,而对属性的控制行为就由描述符来解决。
实际上,在每次查找属性时,描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用(注意不要和 __getattr__() 弄混)。
也就是说,每次使用类对象.属性(或者 getattr(类对象,属性值))的调用方式时,都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:
- 1.类属性
- 2.数据描述符
- 3.实例属性
- 4.非数据描述符
- 5.__getattr__()方法。
上图是查找b.x
这样一个属性的过程。在这里要对此图进行简单的介绍:
- 查找属性的第一步是搜索基类列表,即
type(b).__mro__
,直到找到该属性的第一个定义,并将该属性的值赋值给descr
; - 判断
descr
的类型。它的类型可分为数据描述符、非数据描述符、普通属性、未找到等类型。若descr
为数据描述符,则调用desc.__get__(b, type(b))
,并将结果返回,结束执行。否则进行下一步; - 如果
descr
为非数据描述符、普通属性、未找到等类型,则查找实例b的实例属性,即b.__dict__
。如果找到,则将结果返回,结束执行。否则进行下一步; - 如果在
b.__dict__
未找到相关属性,则重新回到descr
值的判断上。- 若
descr
为非数据描述符,则调用desc.__get__(b, type(b))
,并将结果返回,结束执行; - 若
descr
为普通属性,直接返回结果并结束执行; - 若
descr
为空(未找到),则最终抛出 AttributeError 异常,结束查找。
- 若
由描述符类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
描述符的对象 x 其实是类People的类属性,那么可不可以把它变成实例属性呢
class Desc(): def __init__(self,key): self.key = key def __get__(self, instance, owner): print("__get__") print("self.key==",self.key) def __set__(self, instance, value): print("__set__") def __delete__(self, instance): print("__delete__") instance.__dict__.pop(self.key) #删除 class People(): x = Desc('x') def __init__(self): self.age =Desc('age') p1 = People() print(p1.x) print(p1.age) print(p1.__dict__) print(People.__dict__)
#结果为 __get__ self.key== x None <__main__.Desc object at 0x012F09B0> {'age': <__main__.Desc object at 0x012F09B0>} {'__module__': '__main__', 'x': <__main__.Desc object at 0x012F00F0>, '__init__': <function People.__init__ at 0x013197C8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
说到低还是属性查找顺序
如果是实例属性,则没有调用__get__方法。
所以解释器将该方法转化为People.__dict__[‘age’].__get__(p1, People), 但是People 并没有 age这个属性,也就是类属性中没有age。接着看实例对象__dict__中有没有,有则返回,没有按上图继续。
返回的是Desc对象。
扩展:类X和实例x,调用x.foo,等效于调用:
type
(x).__dict__[
'foo'
].__get__(x,
type
(x))
调用X.foo,等效于调用:
type
(X).__dict__[
'foo'
].__get__(None,X
)
如果 类属性的描述符对象 和 实例属性描述符的对象 同名时(实例属性访问不到)
class Desc(): def __init__(self,key): self.key = key def __get__(self, instance, owner): print("__get__") print("get里面的self",self) print("get里面的instance",instance) print("get里面的owner",owner) print("self.key==",self.key) print("=="*50,"\n") return self.key def __set__(self, instance, value): print("__set__") print("set里面的self",self) print("set里面的instance",instance) print("set里面的owner",value) # instance.__dict__[self.key]=value class People(): name = Desc('name') #age = Desc('age') def __init__(self,name,age): self.name = name #self.name = Desc('name') #self.age = age p1 = People('susu',200) print(p1.name) print(p1.__dict__) """ 执行结果如下: __set__ set里面的self <__main__.Desc object at 0x031909D0> set里面的instance <__main__.People object at 0x031909F0> set里面的owner susu __get__ get里面的self <__main__.Desc object at 0x031909D0> get里面的instance <__main__.People object at 0x031909F0> get里面的owner <class '__main__.People'> self.key== name ==================================================================================================== name {} """
每次属性查找,这个协议的方法实际上是由对象的特殊方法getattribute()调用。每次通过点号(ins.attribute)或者getattr(ins, ‘attribute’)函数调用都会隐式的调用getattribute(),它的属性查找的顺序如下:
1. 验证属性是否为实例的类对象的数据描述符
2. 查看该属性是否能在实例对象的dict中找到
3. 查看该属性是否为实例的类对象的非数据描述符
对象属性的访问顺序: 类属性>数据描述符>实例属性>非数据描述符>找不到的属性触发__getattr__()
上面例子中,同名要能访问到,在__set__中要加一行。
class Desc(): def __get__(self, instance, owner): print("get") def __set__(self, instance, value): # print('self==',self) print('instance==', instance) print('value==', value) instance.__dict__['_x'] = value class People(): _x = Desc() def __init__(self,x): self._x = x p1 = People(10) print(People.__dict__) print(p1._x) # {'_x': 10}
类属性>数据描述符
class Desc(): def __init__(self,name): self.name = name def __get__(self, instance, owner): print("__get__") # print("name==",self.name) # print("=="*50,"\n") return self.name def __set__(self, instance, value): # print('self==',self) print('instance==', instance) print('value==', value)class People(): x = Desc('susu') #x 是一个数据描述符 p1 = People() p1.x #触发了 __get__方法 People.x = 10 #重新设置类属性 p1.x #就没有调用了 __get__方法 print(p1.x) #覆盖了数据描述符 类属性>数据描述符
数据描述符>实例属性
class Desc(): def __init__(self,name): self.name = name def __get__(self, instance, owner): print("__get__") # print("name==",self.name) # print("=="*50,"\n") return self.name def __set__(self, instance, value): # print('self==',self) print('instance==', instance) print('value==', value) instance.__dict__['_x'] = value class People(): x = Desc('susu') #x 是一个数据描述符 p1 = People() p1.x =1 #调用了__set__方法 print(p1.x) #调用__get__方法 susu print(p1.__dict__) #{'_x': 1}
实例属性>非数据描述符
class Desc(): def __init__(self,name): self.name = name def __get__(self, instance, owner): print("__get__") # print("name==",self.name) # print("=="*50,"\n") return self.name class People(): x = Desc('susu') #x 是一个数据描述符 p1 = People() p1.x =1 print(p1.x) #返回的是 1 print(p1.__dict__)
非数据描述符>找不到
class Desc(): def __init__(self,name): self.name = name def __get__(self, instance, owner): print("__get__") # print("name==",self.name) # print("=="*50,"\n") return self.name class People(): x = Desc('susu') #x 是一个数据描述符 def __getattr__(self, item): print('找不到') p1 = People() p1.x # 会调用get方法 p1.y # 找不到才调用__getattr__方法 """ 执行结果: __get__ 找不到 """
描述符应用
class Desc(): def __init__(self,key): self.key = key def __get__(self, instance, owner): print("__get__")return instance.__dict__[self.key] #获取 def __set__(self, instance, value): print("__set__") instance.__dict__[self.key]=value #写入 def __delete__(self, instance): print("__delete__") instance.__dict__.pop(self.key) #删除 class People(): name = Desc('name') age = Desc('age') def __init__(self,name,age): self.name = name self.age = age p1 = People('susu',200) print(p1.name) print(p1.age) print(p1.__dict__) del p1.age print(p1.__dict__) """ __set__ __set__ __get__ susu __get__ 200 {'name': 'susu', 'age': 200} __delete__ {'name': 'susu'} """
#name 输入的类型是str类型,如果不是str类型就报错 #age 输入的类型是int类型,如果不是int类型就报错 class Desc(): def __init__(self,key,expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): print("__get__") return instance.__dict__[self.key] #获取 def __set__(self, instance, value): print("__set__") if not isinstance(value,self.expected_type): raise TypeError("%s 类型必须输入%s类型"%(self.key,self.expected_type)) instance.__dict__[self.key]=value #写入 def __delete__(self, instance): print("__delete__") instance.__dict__.pop(self.key) class People(): name = Desc('name',str) age = Desc('age',int) def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary p1 = People('susu',28,75222) print(p1.name) print(p1.age) print(p1.__dict__) print(People.__dict__)
注意:
必须把描述符定义成这个类的类属性,不能为定义到构造函数中。按照顺序,定义在构造方法中,不能实现,上面有代码
利用描述符实现@property
拓展
1. __get__ 和 __getattr__ 和 __getattribute__ 和getattr()的区别
“object.__getattribute__(self, name)”
__getattribute__
是实例对象查找属性或方法的入口。自动调用。实例对象访问属性或方法时都需要调用到__getattribute__
,之后才会根据一定的规则在各个__dict__
中查找相应的属性值或方法对象,若没有找到则会调用__getattr__。
object.__getattr__(self, name)
__getattr__
可以用来在当用户试图访问一个根本不存在(或者暂时不存在)的属性时,来定义类的行为。前面讲到过,当__getattribute__
方法找不到属性时,最终会调用__getattr__
方法。它可以用于捕捉错误的以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用。返回一个值或 AttributeError 异常。(此处属性指方法和属性)
object.__get__(self, instance, owner)
如果类中重写了它,则这个类就可以称为“描述符”,上面有详细介绍
getattr()
用于返回一个对象属性值,getattr(instance,'c')和instance.c是一样的。看一下反射。方法和属性都可以获取。
class A: att = 'abc' def __getattribute__(self, item): print("触发了 __getattribut__()") return object.__getattribute__(self, item) + ' from getattribute' def __getattr__(self, item): print("触发了 __getattr__() 方法") return item + " form getatter" def __get__(self, instance, owner): print("触发了 __get__()方法", instance, owner) return self class B: a1 = A() if __name__ == '__main__': a2 = A() b = B() print('————————————————————————————————————————') print(a2.att) print('————————————————————————————————————————') print(getattr(a2,'att')) print('————————————————————————————————————————') print(a2.ppppppppp) print('————————————————————————————————————————') b.a1 print('————————————————————————————————————————') print(b.a1.att)
———————————————————————————————————————— 触发了 __getattribut__() abc from getattribute ———————————————————————————————————————— 触发了 __getattribut__() abc from getattribute ———————————————————————————————————————— 触发了 __getattribut__() 触发了 __getattr__() 方法 ppppppppp form getatter ———————————————————————————————————————— 触发了 __get__()方法 <__main__.B object at 0x000001BADAB38940> <class '__main__.B'> ———————————————————————————————————————— 触发了 __get__()方法 <__main__.B object at 0x000001BADAB38940> <class '__main__.B'> 触发了 __getattribut__() abc from getattribute Process finished with exit code 0
每次通过实例访问属性,都会经过__getattribute__函数。而当属性不存在时,仍然需要访问__getattribute__,不过接着要访问__getattr__。这就好像是一个异常处理函数。
每次访问descriptor(即实现了__get__的类),都会先经过__get__函数。
需要注意的是,当使用类访问不存在的变量是,不会经过__getattr__函数。而descriptor不存在此问题,只是把instance标识为none而已。
-
__setattr__(self, name, value)
__setattr__
方法允许你自定义某个属性的赋值行为,不管这个属性存在与否,都可以对任意属性的任何变化都定义自己的规则。关于__setattr__
有两点需要说明:第一,使用它时必须小心,不能写成类似self.name = "Tom"
这样的形式,因为这样的赋值语句会调用__setattr__
方法,这样会让其陷入无限递归;第二,你必须区分 对象属性 和 类属性 这两个概念。后面的例子中会对此进行解释。 -
__delattr__(self, name)
__delattr__
用于处理删除属性时的行为。和__setattr__
方法要注意无限递归的问题,重写该方法时不要有类似del self.name
的写法。
class Animal(object): run = True class Dog(Animal): fly = False def __init__(self, age): self.age = age def __getattr__(self, name): print "calling __getattr__\n" if name == 'adult': return True if self.age >= 2 else False else: raise AttributeError def __setattr__(self, name, value): print "calling __setattr__" super(Dog, self).__setattr__(name, value) #self.__dict__['name'] = value def __delattr__(self, name): print "calling __delattr__" super(Dog, self).__delattr__(name) ”“” # 创建实例对象dog >>> dog = Dog(1) calling __setattr__ # 检查一下dog和Dog的__dict__ >>> dog.__dict__ {'age': 1} >>> Dog.__dict__ dict_proxy({'__delattr__': <function __main__.__delattr__>, '__doc__': None, '__getattr__': <function __main__.__getattr__>, '__init__': <function __main__.__init__>, '__module__': '__main__', '__setattr__': <function __main__.__setattr__>, 'fly': False}) # 获取dog的age属性 >>> dog.age 1 # 获取dog的adult属性。 # 由于__getattribute__没有找到相应的属性,所以调用__getattr__。 >>> dog.adult calling __getattr__ False # 调用一个不存在的属性name,__getattr__捕获AttributeError错误 >>> dog.name calling __getattr__ Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 10, in __getattr__ AttributeError # 给dog.age赋值,会调用__setattr__方法 >>> dog.age = 2 calling __setattr__ >>> dog.age 2 # 先调用dog.fly时会返回False,这时因为Dog类属性中有fly属性; # 之后再给dog.fly赋值,触发__setattr__方法。 >>> dog.fly False >>> dog.fly = True calling __setattr__ # 再次查看dog.fly的值以及dog和Dog的__dict__; # 可以看出对dog对象进行赋值,会在dog对象的__dict__中添加了一条对象属性; # 然而,Dog类属性没有发生变化 # 注意:dog对象和Dog类中都有fly属性,访问时会选择哪个呢? >>> dog.fly True >>> dog.__dict__ {'age': 2, 'fly': True} >>> Dog.__dict__ dict_proxy({'__delattr__': <function __main__.__delattr__>, '__doc__': None, '__getattr__': <function __main__.__getattr__>, '__init__': <function __main__.__init__>, '__module__': '__main__', '__setattr__': <function __main__.__setattr__>, 'fly': False}) 实例对象的__setattr__方法可以定义属性的赋值行为,不管属性是否存在。当属性存在时,它会改变其值;当属性不存在时,它会添加一个对象属性信息到对象的__dict__中,然而这并不改变类的属性。从上面的例子可以看出来。 # 由于上面的例子中我们为dog设置了fly属性,现在删除它触发__delattr__方法 >>> del dog.fly calling __delattr__ # 再次查看dog对象的__dict__,发现和fly属性相关的信息被删除 >>> dog.__dict__ {'age': 2} “”“
这个代码不错
class Quantity:#改进版描述符类 __counter = 0 def __init__(self): cls = self.__class__ prefix = cls.__name__ index = cls.__counter self.storage_name = '_{}#{}'.format(prefix, index) #每个描述符实例的属性名称都是独一无二的。 cls.__counter += 1 def __get__(self, instance, owner): #此处owner参数是托管类LineItem。 return getattr(instance, self.storage_name) #从instance中获取储存属性的值。 def __set__(self, instance, value): if value > 0: setattr(instance, self.storage_name, value) #使用setattr把值储存在instance中。 else: raise ValueError('value must be > 0') class LineItem:#托管类 weight = Quantity() price = Quantity() def __init__(self,description,weight,price): self.description=description self.weight=weight self.price=price def subtotal(self): return self.weight*self.price
http://fanchunke.me/Python/Python%E4%B8%AD%E7%9A%84%E5%B1%9E%E6%80%A7%E8%AE%BF%E9%97%AE%E4%B8%8E%E6%8F%8F%E8%BF%B0%E7%AC%A6/
https://www.cnblogs.com/sugh/articles/11912608.html