理解 python 的 method 和 function 兼谈 descriptor

总是看到有人对 python 中的 methodfunction 之间关系的困惑,其实初学 python 时我也困惑过,不过现在自认为对这个问题还是基本清楚了 ;-)。

我在前面写过的 selfless python 里面说过 method 本质上就是 function,这个从它们的形式上也看得出来,呵呵,而让人困惑的问题主要就是那个隐式传入的 self 参数。这其实是利用了descriptor 机制,请看代码:

>>> class Temp(object):
... def test(self, a):
... print self, a
...
>>> func = Temp.__dict__['test']
>>> func
<function at="" test="">
>>> func(1, 2)
1 2
</function>
由此可见 test 就是个不折不扣的函数!
>>> Temp.test
<unbound temp.test="" method="">
>>> t = Temp()
>>> t.test
<bound of="" temp.test="" method=""><__main__.Temp object at 0x00B46CD0>>
</bound></unbound>
但是这又是怎么回事了?哪里冒出个 bound/unbound method 来了?
>>> dir(func)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__ge
tattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__r
educe__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure',
'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_na
me']
请注意其中的 __get__ 方法,这就是 descriptor 的标志(任何定义了 __get__, __set__, __delete__ 三个方法中的一个或几个的对象都是 descriptor ,这几个方法的意思大家应该能猜到了)

根据对象 attribute 的查找策略,当 t.test 时,首先根据 attribute查找策略找到这个函数对象,然后会发现它有 __get__ 属性,则调用之,并把它的返回值当作该 attribute 的值。
Temp.test 等价于 Temp.__dict__['test'].__get__(None, Temp)
t.test 等价于 Temp.__dict__['test'].__get__(t, Temp)

其实你可以把 func.__get__ 的实现想象成下面这个等价物:

>>> class Function(object):
... def __get__(self, obj, objtype=None):
... import types
... return types.MethodType(self, obj, objtype)

到这里事情已经比较清楚了,不过还有一点可能仍然会让你感到困惑:

>>> Temp.test = test

>>> t.test(1)
<__main__.Temp object at 0x00B46E90> 1
>>> t.test = test
>>> t.test(1)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: test() takes exactly 2 arguments (1 given)
>>> t.test
<function at="" test="">
</function></stdin>

咦?不是说 functiondescriptor 的吗?怎么这里没有去调用它的 __get__ 方法呢?

另外:

>>> class Meta(type):pass
...
>>> class Temp(object):
... __metaclass__ = Meta
...
>>> class Desc(object):
... def __get__(self, instance, type):
... print instance, type
...
>>> desc = Desc()
>>> Meta.d = desc
>>> Meta.d
None <class __main__.meta="">

>>> Temp.d
<class __main__.temp=""> <class __main__.meta="">
>>> Temp.d = desc
>>> Temp.d
None <class __main__.temp="">
>>> t = Temp()
>>> t.d
<__main__.Temp object at 0x00B46DD0> <class __main__.temp="">

>>> t.d = desc
>>> t.d
<__main__.Desc object at 0x00B46D30>
</class></class></class></class></class>

注意到,到最后一步 t.d 的时候也没有对 descriptor 求值。这个道理和上面那个是一样的,仔细看一下 attribute 查找策略 就可以找到答案了, descriptor 只有绑定在 type object 上才有效。

这里我们涉及到了 python对象一种分类: type object type object ,这两种对象在 attribute 查找过程中的待遇是不一样的。

简单地说 type object 包括 type, type 的子类( 也就是 metaclass 了 )、 type 的实例( 也就是 class 了 )

一般来说 type object type object 不光在 attribute 受到不平等待遇,而且非 type object 还不能成为其它对象的基类型,想成为 metaclass 更是痴心妄想了。

不过就像我以前说过的那样,python 中的对象本质上都是平等的,区分它们的唯一方法是它们的接口,所以我相信所谓 type object type object 的区别也只在于接口而已。也就是说只要实现 type object 所需的接口,任何对象都可以成为 type object

参考:

How-To Guide for Descriptors

Python Attributes and Methods

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值