python 描述符解析

什么是描述符

python描述符是一个“绑定行为”的对象属性,在描述符协议中,它能够经过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。若是这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。html

描述符的调用

描述符做为属性访问是被自动调用的。python

对于类属性描述符对象,使用type.__getattribute__,它能把Class.x转换成Class.__dict__['x'].__get__(None, Class)。
对于实例属性描述符对象,使用object.__getattribute__,它能把object.x转换为type(object).__dict__['x'].__get__(object, type(object))。ssh

描述符讲解

下面咱们具体经过实例来详细说明描述符的使用code

先定义一个描述符orm

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者之间有一些区别,下面会讲到。htm

再定义一个调用描述符对象的类对象

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)来访问。ip

print MyClass.__dict__["x"].__get__(None, MyClass)
# 输出
Retrieving var "x"
10

描述符的对象定义为类属性,若是定义成对象属性会有什么不一样吗?下面咱们试验一下get

class MyClass(object):

    x = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.y = RevealAccess(11, 'var "y"')

print type(MyClass.x)
# 输出
"""
Retrieving var "x"
<type 'int'>;
"""
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__': <attribute '__dict__' of 'MyClass' objects>, 
'x': <__main__.RevealAccess object at 0x1004da350>, 
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, 
'__doc__': None, '__init__': <function __init__ at 0x1004cce60>}
"""

从输出中发现实例对象的字典中根本就没有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__': &lt;attribute '__dict__' of 'MyClass' objects&gt;,
'x': <;__main__.RevealAccess object at 0x1005da310>, 
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, 
'__doc__': None, '__init__': <function __init__ at 0x1005ccd70>}
“”“
print MyClass.x
"""
Retrieving var "x"
10
"""

从上面的输出,能够看出非数据描述符不会覆盖掉实例属性。并且优先级比实例属性低。这也是和数据描述符的一个区别。

综上所述,对于描述符的调用有如下几点须要注意

  1. 描述符被 getattribute 方法调用

  2. 覆盖__getattribute__会让描述符没法自动调用

  3. 描述符只适用于新式类,即继承object的类

  4. object . getattribute 和 type . getattribute 调用__get__方法不同

  5. 数据描述符优先于实例的字典,对于相同名字的会覆盖

  6. 实例的字典优先于非数据描述符。但不会覆盖。

  7. 对于数据描述符,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...

以上就是本人对描述符的一些理解,有什么不正确的地方还请不吝指出,谢谢!