摘要:之所以是这样是因为当访问一个实例描述符对象时,会将转换为。而类的字典中则有描述符对象。这主要就是因为描述符优先。此外,非数据描述符的优先级低于实例属性。参考以上就是本人对描述符的一些理解,有什么不正确的地方还请不吝指出,谢谢
什么是描述符
python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。
描述符的调用描述符作为属性访问是被自动调用的。
对于类属性描述符对象,使用type.__getattribute__,它能把Class.x转换成Class.__dict__["x"].__get__(None, Class)。
对于实例属性描述符对象,使用object.__getattribute__,它能把object.x转换为type(object).__dict__["x"].__get__(object, type(object))。
下面我们具体通过实例来详细说明描述符的使用
先定义一个描述符
class RevealAccess(object): 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
上面实现了__get__和__set__。所以这是一个描述符对象。而且是一个数据描述符对象,非数据描述符对象只实现__get__方法。这2者之间有一些区别,下面会讲到。
再定义一个调用描述符对象的类
class MyClass(object): x = RevealAccess(10, "var "x"") y = 5 print MyClass.x
访问 MyClass.x 输出
Retrieving var "x" 10
发现访问x会去调用描述符的__get__方法。这就达到了描述符的作用,可以改变对象属性的访问,使用描述符的方法。因为如果解析器发现x是一个描述符的话,其实在内部是通过type.__getattribute__(),它能把MyClass.x转换为MyClass.__dict__[“x”].__get__(None,MyClass)来访问。
print MyClass.__dict__["x"].__get__(None, MyClass) # 输出 Retrieving var "x" 10
描述符的对象定义为类属性,如果定义成对象属性会有什么不同吗?下面我们试验一下
class MyClass(object): x = RevealAccess(10, "var "x"") def __init__(self): self.y = RevealAccess(11, "var "y"") print type(MyClass.x) # 输出 """ Retrieving var "x"; """ test = MyClass() print test.y # 输出 """ <__main__.RevealAccess object at 0x1004da410>; """
从上面的输出,可以看到访问类属性的确调用了描述符的__get__方法,看到输出的结果是int类型。而调用实例属性并没有访问__get__方法。而是直接返回描述符的实例对象。之所以是这样是因为当访问一个实例描述符对象时,object.__getattribute__会将test.y转换为type(test).__dict__[‘y’].__get__(test,type(test))。
而MyClass类中没有“y”属性,所以无法访调用到_get__方法,这里会有一个判断的过程。但这个实例对象仍然是一个描述符对象。所以最好定义描述符对象为类属性。当然不是不可以定义为实例属性,请看下面
当定义的类属性描述符对象和实例属性有相同的名字时
class MyClass(object): x = RevealAccess(10, "var "x"") def __init__(self, x): self.x = x
然后调用
test = MyClass(100) print test.x # 输出 """ Updating var "x" Retrieving var "x" 100 """
可见依然调用了描述符的方法。按照常理,应该访问 test.__dict__["x"],然后是type(test).__dict__["x"]。由于我们定义了实例属性x。应该只输出100。可这里从输出结果看的的确确的访问了描述符的方法。那么这是为什么呢?
其实这里主要是因为当python发现实例对象的字典中有与定义的描述符有相同名字的对象时,描述符优先,会覆盖掉实例属性。python会改写默认的行为,去调用描述符的方法来代替。我们可以输出类和实例对象的字典看看
test = MyClass(100) print test.__dict__ """ 输出 {} """ print MyClass.__dict__ """ 输出 {"__module__": "__main__", "__dict__":, "x": <__main__.RevealAccess object at 0x1004da350>, "__weakref__": , "__doc__": None, "__init__": } """
从输出中发现实例对象的字典中根本就没有x对象,即使我们在类中定义了self.x。而类的字典中则有x描述符对象。这主要就是因为描述符优先。
上面我们定义的描述符有__get__和__set__2个方法,所以是一个数据描述符,非数据描述符只有一个__get__方法,通常用于方法。此外,非数据描述符的优先级低于实例属性。下面看一个例子,我们去掉__set__方法。
class RevealAccess(object): def __init__(self, initval=None, name="var"): self.val = initval self.name = name def __get__(self, obj, objtype): print "Retrieving", self.name # self.val="test" return self.val # def __set__(self, obj, val): # print "Updating", self.name # self.val = val class MyClass(object): x = RevealAccess(10, "var "x"") def __init__(self, x): self.x = x test = MyClass(100) print test.x “”“ 100 “”“ print test.__dict__ “”“ {"x": 100} “”“ print MyClass.__dict__ “”“ {"__module__": "__main__", "__dict__":, "x": <;__main__.RevealAccess object at 0x1005da310>, "__weakref__": , "__doc__": None, "__init__": } “”“ print MyClass.x """ Retrieving var "x" 10 """
从上面的输出,可以看出非数据描述符不会覆盖掉实例属性。而且优先级比实例属性低。这也是和数据描述符的一个区别。
综上所述,对于描述符的调用有以下几点需要注意
描述符被 getattribute 方法调用
覆盖__getattribute__会让描述符无法自动调用
描述符只适用于新式类,即继承object的类
object . getattribute 和 type . getattribute 调用__get__方法不一样
数据描述符优先于实例的字典,对于相同名字的会覆盖
实例的字典优先于非数据描述符。但不会覆盖。
对于数据描述符,python中property就是一个典型的应用。
对于非数据描述符,其主要用于方法。如静态方法和类方法。看源码可以看到只实现了描述符协议中的__get__方法,而没有实现__set__和__del__。
如下面这样模拟静态方法
class StaticMethod(object): def __init__(self, f): self.f = f def __get__(self, obj, objtype=None): return self.f class MyClass(object): @StaticMethod def get_x(x): print("static") return x print MyClass.get_x(100) """ static 100 “”“
调用MyClass.get_x(100)相当于
MyClass.__dict__["get_x"].__get__(None, MyClass)(100)
我们知道在python中,一切皆是对象。每一个定义的方法其实都是一个对象。在这里我们可以通过dir()查看每一个方法里的属性和方法。看下面
class Desc(object): def test1(self): print("test1") def test2(): print("test2") print(dir(test2)) """输出太长不贴了,但从输出中可以看到有__get__""" print(dir(Desc.test1)) """ ["__call__", "__class__", "__cmp__", "__delattr__", "__doc__", "__format__", "__func__", "__get__", "__getattribute__", "__hash__", "__init__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__self__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "im_class", "im_func", "im_self"] """
从dir的输出,可以看到,每个方法对象都包含一个__get__方法。因此可以说每一个方法都是一个非数据描述符。通常我们通过点操作符调用方法时,内部都是调用这个__get__方法。
参考 https://docs.python.org/2.7/h...
以上就是本人对描述符的一些理解,有什么不正确的地方还请不吝指出,谢谢!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/38242.html
摘要:最近在阅读微型框架的源码,发现了中有一个既是装饰器类又是描述符的有趣实现。所以第三版的代码可以这样写第三版的代码没有使用装饰器,而是使用了描述符这个技巧。更大的问题来自如何将描述符与装饰器结合起来,因为是一个类而不是方法。 最近在阅读Python微型Web框架Bottle的源码,发现了Bottle中有一个既是装饰器类又是描述符的有趣实现。刚好这两个点是Python比较的难理解,又混合在...
摘要:近日,他开通了账号,并发表了第一篇文章,透露出要替换的核心部件解析器的想法。这篇文章分析了当前的解析器的诸多缺陷,并介绍了解析器的优点,令人振奋。但问题是,如果你这样写语法,解析器不会起作用,将会罢工。 showImg(https://segmentfault.com/img/remote/1460000019893712?w=3936&h=2624); 花下猫语: Guido van...
摘要:下面我们用描述符来实现中的动态属性和特性中提及的订单结算代码第四版使用描述符实现订单结算功能描述符基于协议实现,无需创建子类。特性是覆盖型描述符。非覆盖型描述符没有实现方法的描述符属于非覆盖型描述符。类中定义的方法是非覆盖型描述符。 导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、了解描述符...
摘要:由上面的注释,可以看出其实就相当于一个描述符类,而在此刻变成了一个描述符。调用这个方法可以知道,每调用一次,它都会经过描述符类的。基于描述符如何实现同样的也是一样。我想你应该对描述符在中的应用有了更深的理解。好吧,我承认我标题党了。但是这篇文章的知识点,你有极大的可能并不知道。 前段时间,我写了一篇描述符的入门级文章,从那些文章里你知道了如何定义描述符,且明白了描述符是如何工作的。 如果你还...
阅读 2007·2021-11-24 09:39
阅读 1141·2021-09-10 11:25
阅读 1769·2021-09-08 10:42
阅读 3732·2021-09-06 15:00
阅读 2498·2019-08-30 15:54
阅读 3115·2019-08-29 17:08
阅读 3270·2019-08-29 11:26
阅读 2840·2019-08-28 18:27