一、描述符是什么
描述符:是一个类,只要内部定义了方法__get__, __set__, __delete__中的一个或者多个。描述符,属性,方法绑定等内部机制都是描述符在起作用。描述符以单个属性出现,并针对该属性的不同访问行为做出响应。最重要的是,描述符能“感知”通过什么引用该属性,从而和目标建立绑定关联。
二、描述符的实现
class Descriptor: """ 描述符 """ def __set_name__(self, owner, name): """ 描述符属性必须定义为类型成员,所以其自身不适合存储实例相关的状态,在创建属性时,__set_name__方法被调用,并可以通过参数获知目标类型(owner),以及属性名称 :param owner: :param name: :return: """ print(self, owner, name) self.name = f"__{name}__" def __get__(self, instance, owner): """ 以类型或实例访问描述符属性时,__get__被自动调用,且会接收到类型和实例引用 :param instance: :param owner: :return: """ print(self,instance,owner) return getattr(instance,self.name, None) def __set__(self, instance, value): """ 仅在实例引用时被调用。以类型引用进行赋值,会导致描述符属性被替换 :param instance: :param value: :return: """ print(self, instance, value) setattr(instance, self.name, value) def __delete__(self, instance): """ 仅在实例被引用时调用。以类型引用进行删除操作,会导致描述符属性被删除 :param instance: :return: """ print(self, instance) raise AttributeError("delete is disabled") class X: data = Descriptor() x = X() # 执行__set_name__ <__main__.Descriptor object at 0x0000026DEB4E8438> <class '__main__.X'> data x.data = 100 # 执行__set__ <__main__.Descriptor object at 0x0000018A54408470> <__main__.X object at 0x0000018A54408908> 100 print(x.data) # 执行__get__ <__main__.Descriptor object at 0x0000020685688438> <__main__.X object at 0x00000206856888D0> <class '__main__.X'> print(x.__dict__) # {'__data__': 100} print(X.__dict__) # {'__module__': '__main__', 'data': <__main__.Descriptor object at 0x000001E841598438>, '__dict__': <attribute '__dict__' of 'X' objects>, '__weakref__': <attribute '__weakref__' of 'X' objects>, '__doc__': None} X.data = 2 # 以类型引用进行赋值,会导致描述符属性被替换 print(x.data) # 2 print(X.data) # 2 print(x.__dict__) # {'__data__': 100} print(X.__dict__) # {'__module__': '__main__', 'data': 2, '__dict__': <attribute '__dict__' of 'X' objects>, '__weakref__': <attribute '__weakref__' of 'X' objects>, '__doc__': None}
三、数据描述符
如果定义了__set__或__delete__方法,那么我们便称其为数据描述符(data descriptor),而仅有__get__的则是非数据描述符(non-data descriptor)。这两者的区别在于,数据描述符属性的优先级高于实例名字空间中的同名成员。
class Descriptors: """ 数据描述符 """ def __set_name__(self, owner, name): self.name = name # 获取Descriptors 实例对象名字 def __get__(self, instance, owner): print("执行Descriptors的get") return self.name def __set__(self, instance, value): self.name = value print("执行Descriptors的set") def __delete__(self, instance): print("执行Descriptors的delete") class Light: # 使用描述符 name = Descriptors() def __init__(self, name, price): self.name = name self.price = price # 使用类的实例对象来测试 light = Light("电灯泡", 60) # 执行描述符的set内置属性 light.name # 执行描述符的get内置属性 print(light.__dict__) # 查看实例的字典,不存在name {'price': 60} print(Light.__dict__) # 查看类的字典,存在name(为描述符的对象) # {'__module__': '__main__', 'name': <__main__.Descriptors object at 0x000002261E7D8438>, # '__init__': <function Light.__init__ at 0x00000226257FED90>, '__dict__': <attribute '__dict__' of 'Light' objects>, # '__weakref__': <attribute '__weakref__' of 'Light' objects>, '__doc__': None} del light.name # 执行描述符的delete内置属性 del Light.name # 以类型引用进行删除操作,会导致描述符属性被删除 print(Light.__dict__) # {'__module__': '__main__', '__init__': <function Light.__init__ at 0x000001CBC197EEA0>, '__dict__': <attribute '__dict__' of 'Light' objects>, '__weakref__': <attribute '__weakref__' of 'Light' objects>, '__doc__': None} print(light.name) # 报错,描述符属性被删除
如果注释掉__set__,就成为了非数据描述符。
描述符的优先级问题:类属性>数据描述符>实例属性>非数据描述符>找不到属性触发__getattr__()
说明问题一:类属性>数据描述符
class Descriptor: def __get__(self, instance, owner): print("__get__") return self.name def __set__(self, instance, value): print("开始赋值:", value) self.name = value print("__set__") class X: data = Descriptor() x = X() x.data = 100 # 调用__set__赋值 print(x.__dict__) # {} x.data = 3 print(x.data) # 3 print(x.__dict__) # {} print(X.data) # 调用__get__ print(X.__dict__) # {'__module__': '__main__', 'data': <__main__.Descriptor object at 0x0000015EE4A38438>, '__dict__': <attribute '__dict__' of 'X' objects>, '__weakref__': <attribute '__weakref__' of 'X' objects>, '__doc__': None} X.data = 44444 # 语句没有触发set的执行,说明类属性的优先级大于数据描述符的优先,此时相当于类属性覆盖了数据描述符,从而说明对类属性的一切操作都与描述符无关 print(x.data) # 4 print(x.__dict__) # {} print(X.__dict__) # {'__module__': '__main__', 'data': 44444, '__dict__': <attribute '__dict__' of 'X' objects>, '__weakref__': <attribute '__weakref__' of 'X' objects>, '__doc__': None}
说明问题二:数据描述符>实例属性 参考“描述符代码” ,数据描述符的优先级大于实例属性的优先级,此时实例属性name被数据描述符所覆盖,而price没有描述符代理,所以它任然是实例属性。
说明问题三:实例属性>非数据描述符
class Descriptors: """ 非数据描述符 """ def __get__(self, instance, owner): print("执行Descriptors的get") def __delete__(self, instance): print("执行Descriptors的delete") class X: data = Descriptors() x = X() x.data = 3 # 报错,AttributeError: __set__
四、方法绑定
因为函数默认实现了描述符协议,所以当以实例或类型访问方法时,__get__首先被调用。类型和实例作为参数被传入__get__,从而截获绑定目标(self),如此就将函数包装称绑定方法对象返回。实际被执行的,就是这个会隐式传入第一个参数的包装品。
class Person: def __init__(self, name, age): self.name = name self.age = age def print_info(self): print("my name is %s ,my age is %s" % (self.name, self.age)) p = Person("ways", 13) print(p.print_info) # <bound method Person.print_info of <__main__.Person object at 0x000002092DBE8438>> print(p.print_info.__get__(p,Person)) # <bound method Person.print_info of <__main__.Person object at 0x000002092DBE8438>> m = p.print_info.__get__(p,Person) Person.print_info(m.__self__,) # my name is ways ,my age is 13 print(m.__self__, m.__func__) # <__main__.Person object at 0x000002092DBE8438> <function Person.print_info at 0x0000020934BEED08> """ 方法执行分成了两个步骤: p.print_info(): #1. m = p.print_info.__get__(p,Person) 将函数包装成绑定方法 #2. m()等价Person.print_info(m.__self__,) 执行时,隐式将self/cls参数传递给目标函数 """
五、描述符的使用例子
1、模拟property
class My_Property: """ 使用描述符模拟property """ def __init__(self, func): self.func = func print(self.func) # <function Test.my_area at 0x000001671607ED90> def __get__(self, instance, owner): res = self.func(instance) # 回调传入的函数,将运行结果保存在res中 setattr(instance,self.func.__name__,res) # 为函数名func.__name__ 设置值为res,存入对象的字典 return res class Test: def __init__(self,weight,height): self.weight = weight self.height = height @My_Property def my_area(self): return self.weight*self.height test = Test(3, 4) print(test.my_area) # 12 print(test.__dict__) # {'weight': 3, 'height': 4, 'my_area': 12}
六、描述符的使用总结
1、描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
2、描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
参考:https://www.cnblogs.com/Lynnblog/p/9033455.html 和《python3学习笔记上》