python 描述符解析

JorgFries 8年前
   <h2>什么是描述符</h2>    <p>python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。</p>    <h2>描述符的调用</h2>    <p>描述符作为属性访问是被自动调用的。</p>    <p>对于类属性描述符对象,使用type.__getattribute__,它能把Class.x转换成Class.__dict__[‘x’].__get__(None, Class)。</p>    <p>对于实例属性描述符对象,使用object.__getattribute__,它能把object.x转换为type(object).__dict__[‘x’].__get__(object, type(object))。</p>    <h2>描述符讲解</h2>    <p>下面我们具体通过实例来详细说明描述符的使用</p>    <p>先定义一个描述符</p>    <pre>  <code class="language-python">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  </code></pre>    <p>上面实现了__get__和__set__。所以这是一个描述符对象。而且是一个数据描述符对象,非数据描述符对象只实现__get__方法。这2者之间有一些区别,下面会讲到。</p>    <p>再定义一个调用描述符对象的类</p>    <pre>  <code class="language-python">class MyClass(object):      x = RevealAccess(10, 'var "x"')      y = 5     printMyClass.x  </code></pre>    <p>访问 MyClass.x 输出</p>    <pre>  <code class="language-python">Retrievingvar "x"  10  </code></pre>    <p>发现访问x会去调用描述符的__get__方法。这就达到了描述符的作用,可以改变对象属性的访问,使用描述符的方法。因为如果解析器发现x是一个描述符的话,其实在内部是通过type.__getattribute__(),它能把MyClass.x转换为 MyClass.__dict__[“x”].__get__(None,MyClass) 来访问。</p>    <pre>  <code class="language-python">printMyClass.__dict__["x"].__get__(None, MyClass)  # 输出  Retrievingvar "x"  10  </code></pre>    <p>描述符的对象定义为类属性,如果定义成对象属性会有什么不同吗?下面我们试验一下</p>    <pre>  <code class="language-python">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>;  """  </code></pre>    <p>从上面的输出,可以看到访问类属性的确调用了描述符的__get__方法,看到输出的结果是int类型。而调用实例属性并没有访问__get__方法。而是直接返回描述符的实例对象。之所以是这样是因为当访问一个实例描述符对象时,object.__getattribute__会将test.y转换为 type(test).__dict__[‘y’].__get__(test,type(test)) 。</p>    <p>而MyClass类中没有“y”属性,所以无法访调用到_get__方法,这里会有一个判断的过程。但这个实例对象仍然是一个描述符对象。所以最好定义描述符对象为类属性。当然不是不可以定义为实例属性,请看下面</p>    <p>当定义的类属性描述符对象和实例属性有相同的名字时</p>    <pre>  <code class="language-python">class MyClass(object):            x = RevealAccess(10, 'var "x"')         def__init__(self, x):          self.x = x  </code></pre>    <p>然后调用</p>    <pre>  <code class="language-python">test = MyClass(100)  printtest.x  # 输出  """  Updating var "x"  Retrieving var "x"  100  """  </code></pre>    <p>可见依然调用了描述符的方法。按照常理,应该访问 test.__dict__[‘x’],然后是type(test).__dict__[‘x’]。由于我们定义了实例属性x。应该只输出100。可这里从输出结果看的的确确的访问了描述符的方法。那么这是为什么呢?</p>    <p>其实这里主要是因为当python发现实例对象的字典中有与定义的描述符有相同名字的对象时,描述符优先,会覆盖掉实例属性。python会改写默认的行为,去调用描述符的方法来代替。我们可以输出类和实例对象的字典看看</p>    <pre>  <code class="language-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>}  """  </code></pre>    <p>从输出中发现实例对象的字典中根本就没有x对象,即使我们在类中定义了self.x。而类的字典中则有x描述符对象。这主要就是因为描述符优先。</p>    <p>上面我们定义的描述符有__get__和__set__2个方法,所以是一个数据描述符,非数据描述符只有一个__get__方法,通常用于方法。此外,非数据描述符的优先级低于实例属性。下面看一个例子,我们去掉__set__方法。</p>    <pre>  <code class="language-python">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__': <attribute '__dict__' of 'MyClass' objects>,  'x': <;__main__.RevealAccessobject at 0x1005da310>,   '__weakref__': <attribute '__weakref__' of 'MyClass' objects>,   '__doc__': None, '__init__': <function__init__at 0x1005ccd70>}  “”“  print MyClass.x  """  Retrieving var "x"  10  """  </code></pre>    <p>从上面的输出,可以看出非数据描述符不会覆盖掉实例属性。而且优先级比实例属性低。这也是和数据描述符的一个区别。</p>    <p>综上所述,对于描述符的调用有以下几点需要注意</p>    <ol>     <li>描述符被 <strong>getattribute</strong> 方法调用</li>     <li>覆盖__getattribute__会让描述符无法自动调用</li>     <li>描述符只适用于新式类,即继承object的类</li>     <li>object . <strong>getattribute</strong> 和 type . <strong>getattribute</strong> 调用__get__方法不一样</li>     <li>数据描述符优先于实例的字典,对于相同名字的会覆盖</li>     <li>实例的字典优先于非数据描述符。但不会覆盖。</li>     <li>对于数据描述符,python中property就是一个典型的应用。</li>    </ol>    <p>对于非数据描述符,其主要用于方法。如静态方法和类方法。看源码可以看到只实现了描述符协议中的__get__方法,而没有实现__set__和__del__。</p>    <p>如下面这样模拟静态方法</p>    <pre>  <code class="language-python">class StaticMethod(object):      def__init__(self, f):          self.f = f         def__get__(self, obj, objtype=None):          return self.f     class MyClass(object):         @StaticMethod      defget_x(x):          print("static")          return x     printMyClass.get_x(100)  """  static  100  “”“  </code></pre>    <p>调用MyClass.get_x(100)相当于</p>    <pre>  <code class="language-python">MyClass.__dict__["get_x"].__get__(None, MyClass)(100)  </code></pre>    <p>我们知道在python中,一切皆是对象。每一个定义的方法其实都是一个对象。在这里我们可以通过dir()查看每一个方法里的属性和方法。看下面</p>    <pre>  <code class="language-python">class Desc(object):      deftest1(self):          print("test1")     deftest2():      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']  """  </code></pre>    <p>从dir的输出,可以看到,每个方法对象都包含一个__get__方法。因此可以说每一个方法都是一个非数据描述符。通常我们通过点操作符调用方法时,内部都是调用这个__get__方法。</p>    <p> </p>    <p>来自:http://python.jobbole.com/86953/</p>    <p> </p>