Python descriptor

动态添加对象属性

一次偶然发现,Python的对象竟然可以在运行期动态添加类定义时没有的属性,这又颠覆了我对Python OO机制的理解。Google了一把,顺着__dict__属性一路找到descriptor,揭开了隐藏在Python对象之后的内幕。

本文主要记录Python的descriptor机制,以及其在Python对象的属性、方法绑定上的作用。

先从本文的始作俑者,运行期动态添加对象属性开始讲起。

class A:
    def __init__(self):
        self.value = ‘value‘
    def f(self):
        print(‘function f‘)
a = A()
a.attr = 1
print(a.attr)

以上代码奇迹般的没有报错,而且还输出了1。这肯定会让写过C++/Java代码的童鞋表示吃惊,Python变量类型动态也就不稀奇了,对象属性还能动态添加的?Python到底在背后做了什么?

神奇的__dict__

a.attr = 1前后分别加上一行print(a.__dict__)就会得到如下结果:

{‘value‘: ‘value‘}
1
{‘value‘: ‘value‘, ‘attr‘: 1} 

显而易见,我们在运行期定义的属性和类定义时定义的属性都被放在了__dict__里。

到这里有人可能就有疑问了,Python里的一切不都是对象麽?为什么成员函数__init__f不在这个字典里?

看看A.__dict__里有什么就明白了:

{‘__dict__‘: <attribute ‘__dict__‘ of ‘A‘ objects>,
 ‘__doc__‘: None,
 ‘__init__‘: <function __main__.__init__>,
 ‘__module__‘: ‘__main__‘,
 ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘A‘ objects>,
 ‘f‘: <function __main__.f>}

这时才恍然大悟,如果成员变量看做是对象的属性,那么成员函数就应该看成是类的属性,被全部对象共享嘛。

更精确地讲,以object.attribute访问一个对象的属性时,属性的搜索顺序为:

对象自身,object.__dict__

对象类型,object.__class__.__dict__

对象类型的基类,object.__class__.__bases__中的所有__dict__

注意,当多重继承的情况下有菱形继承的时候,Python会根据MRO确定的顺序进行搜索。关于MRO(Method Resolution Order)是什么有时间专门写一篇文章总结一下。

当以上三个步骤都没有找到要访问的属性的时候Python就只能抛出AttributeError异常了。

Descriptor是什么

讲了这么多,貌似跟descriptor半毛钱关系没有嘛。别急,接着往下看。

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

来测试一下这个类

>>> class MyClass(object):
    x = RevealAccess(10, ‘var "x"‘)
    y = 5

>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5

这个RevealAccess的对象就是一个descriptor,其作用就是在存取变量的时候做了一个hook。访问属性m.x就是调用__get__方法,设置属性值就是调用__set__方法。还可以有一个__delete__方法,在del m.x时被调用。

只要一个类定义了以上三种方法,其对象就是一个descriptor。我们把同时定义__get____set__方法的descriptor叫做data descriptor,把只定义__get__方法的叫non-data descriptor

Method binding

有了以上两个概念,我们就能讨论Python的方法绑定了。

还记得讨论__dict__时的成员函数f吗?按照我们的推测,A.__dict__[‘f‘]应该和a.f是一个东西。但是!!!

>>> A.__dict__[‘f‘]
<function __main__.f>
>>> a.f
<bound method A.f of <__main__.A object at 0x7f9d69cc5950>>

这两个显然不是一个东西,一个是function,一个是bound method。这是什么情况?淡定,看下面

>>> A.__dict__[‘f‘].__get__(a, A)
<bound method A.f of <__main__.A object at 0x7f9d69cc5950>>
>>> a.f
<bound method A.f of <__main__.A object at 0x7f9d69cc5950>>

这下放心了吧:D

其实,类的成员函数就是一个descriptor,在实例化对象a的时候,Python就做了这么一个过程

(伪码,详见Objects/funcobject.c):
a.f = A.__dict__[‘f‘].__get__(a, A)

纯Python模拟的函数对象就像这样:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        return types.MethodType(self, obj, objtype)

然后就好理解staticmethod和classmethod这两个decorator了吧。

staticmethod无视了传入的第一个self参数,classmethod手工加了一个类对象参数进去。它们的纯Python模拟就像下面所示:

class StaticMethod(object):
 "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

 def __init__(self, f):
      self.f = f

 def __get__(self, obj, objtype=None):
      return self.f

class ClassMethod(object):
     "Emulate PyClassMethod_Type() in Objects/funcobject.c"

     def __init__(self, f):
          self.f = f

     def __get__(self, obj, klass=None):
          if klass is None:
               klass = type(obj)
          def newfunc(*args):
               return self.f(klass, *args)
          return newfunc

研究Python的底层实现是个很有意思的事

至少能让我在使用Python时更加放心:)

全文完

参考资料:

How-To Guide for Descriptors

Python Attributes and Methods

《Expert Python Programming》,Tarek Ziadé

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-09-28 20:47:56

Python descriptor的相关文章

python Descriptor (描述符)

简介: python 描述符是新式类(继承自object)中的语言协议,基于描述符可以提供更佳优雅的解决方案. python的classmethod, staticmethod, property都是基于描述符建立的. 描述符的协议: 定义了__set__, __get__, __delete__3个方法中任何一个方法的object可以作为描述符. 描述符分类: 同时定义了__set__,__get__被叫做data descriptor. 只定义了__get__被叫做no-data descr

python中基于descriptor的一些概念(上)

@python中基于descriptor的一些概念(上) python中基于descriptor的一些概念(上) 1. 前言 2. 新式类与经典类 2.1 内置的object对象 2.2 类的方法 2.2.1 静态方法 2.2.2 类方法 2.3 新式类(new-style class) 2.3.1 __init__方法 2.3.2 __new__静态方法 2.4. 新式类的实例 2.4.1 Property 2.4.2 __slots__属性 2.4.3 __getattribute__方法

python中基于descriptor的一些概念(下)

@python中基于descriptor的一些概念(下) 3. Descriptor介绍 3.1 Descriptor代码示例 3.2 定义 3.3 Descriptor Protocol(协议) 3.4 Descriptor调用方法 4. 基于Descriptor实现的功能 4.1 property 4.2 函数和方法,绑定与非绑定 4.3 super 5. 结尾 3. Descriptor介绍 3.1 Descriptor代码示例 class RevealAccess(object):   

python学习笔记-类的descriptor

descriptor应用背景 所谓描述器,是实现了描述符协议,即get, set, 和 delete方法的对象. 简单说,描述符就是可以重复使用的属性. 比如以下代码: f = Foo() b = f.bar f.bar = c del f.bar 在解释器执行上述代码时,当发现你试图访问属性(b = f.bar).对属性赋值(f.bar = c)或者删除一个实例变量的属性(del f.bar)时,就会去调用自定义的方法. 为什么把对函数的调用伪装成对属性的访问?有什么好处? 从property

Python描述符(descriptor)解密

Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些"中级"的语言特性有着完善的文档,并且易于学习. 但是这里有个例外,那就是描述符.至少对于我来说,描述符是Python语言核心中困扰我时间最长的一个特性.这里有几点原因如下: 有关描述符的官方文档相当难懂,而且没有包含优秀的示例告诉你为什么需要编写描述符(我得为Raymond Hettinger辩护一

python描述符descriptor(二)

python内置的描述符 python有些内置的描述符对象,property.staticmethod.classmethod,python实现如下: class Property(object): def __init__(self,getf,setf,delf,doc): self.getf=getf self.setf=setf self.delf=delf self.doc=doc def __set__(self,instance,own=None): if instance is N

Python描述符(descriptor)解密(转)

原文:http://www.geekfan.net/7862/ Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些“中级”的语言特性有着完善的文档,并且易于学习. 但是这里有个例外,那就是描述符.至少对于我来说,描述符是Python语言核心中困扰我时间最长的一个特性.这里有几点原因如下: 有关描述符的官方文档相当难懂,而且没有包含优秀的示例告诉你为什么需要编写

Python——描述符(descriptor)解密

本文由 极客范 - 慕容老匹夫 翻译自 Chris Beaumont.欢迎加入极客翻译小组,同我们一道翻译与分享.转载请参见文章末尾处的要求. Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些“中级”的语言特性有着完善的文档,并且易于学习. 但是这里有个例外,那就是描述符.至少对于我来说,描述符是Python语言核心中困扰我时间最长的一个特性.这里有几点原因

python 中关于descriptor的一些知识问题

这个问题从早上日常扫segmentfault上问题开始 有个问题是 class C(object): @classmethod def m(): pass m()是类方法,调用代码如下: C.m() 但我想当成属性的方式调用,像这样: C.m 请问该怎么弄呢? 请最好提供个简单的例子, 多谢! 这里我开始误会了他的意思,以为他是想直接使用C().m调用这个方法,如果是这样,直接将装饰器@classmathod改成@property就可以达到效果了. 但是这里他想要达到的效果是C.m 也就是说在不