Flicker1985's Blog

Everything should be made as simple as possible, but not simpler.
[python] Bound method or Function

我们先看一段代码

#!/usr/bin/env python
# encoding: utf-8

class Foo(object):
	pass
	
def func():
	pass	
		
if __name__ == "__main__":
	Foo.method = func
	f = Foo()
	print Foo.method
	print f.method
	print func

代码非常简单,结果如下:

<unbound method Foo.func>

<bound method Foo.func of <__main__.Foo object at 0x100475f90>>

<function func at 0x1004347d0>

看到这个结果,我们就纳闷了,同样调用一个方法有的输出unbound method/bound method,而有的输出function!

如果要了解这其中的缘由,我们就得从python得descriptor说起。


首先,什么是descriptor?


“The following methods only apply when an instance of the class containing the method (a so-called descriptor class) appears in the class dictionary of another new-style class, known as the owner class. ”


这里的following methods是指下面三个方法:

  • object.__get__(self, instance, owner) Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). owner is always the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner. This method should return the (computed) attribute value or raise an AttributeError exception.
  • object.__set__(self, instance, value) Called to set the attribute on an instance instance of the owner class to a new value, value.
  • object.__delete__(self, instance) Called to delete the attribute on an instance instance of the owner class.

而对于descriptor ,我们又分为两种:对于指包含有_get__方法的descriptor , 我们称之为non-overriding descriptor ; 对于同时包含有__get__,__set__, 我们则称之为overriding descriptor.  


descriptor 有什么用呢?

class OverridingDescriptor(object):
	
	def __init__(self, value= "Data value"):
		self._value = value
		
	def __get__(self, obj, type = None):
		print self, obj, type
		return self._value
		
	def __set__(self, obj, value):
		print self, obj, value
		self._value = value
	
class Foo(object):
	name = OverridingDescriptor()	

if __name__ == "__main__":
	print Foo.name
	Foo.name ="smith"
	print Foo.name

首先我们看第一句 print Foo.name 的输出 :

<__main__.Descriptor object at 0x100510510> None <class '__main__.Foo'>
No-data value

很明显直接调用了直接调用了Descriptor的__get__方法。我们都知道python中的property是提供了对object属性提供了封装,那这里我们推断会不会descriptor提供了对object自身的封装呢?我们接着分析,这时候我们发现第二句的代码没有输出,而最后一句代码的输出是:

smith

也就是说这里没有调用descriptor的__set__方法,这又是为什么呢?这里又涉及到python对于descriptor的查找策略,  而python对于descriptor中的__get__/__set__又采取了两种不同的策略:

  • 对于__get__, 假如我们要访问obj.v(这里的obj可以是class,也可以是instance)。

 

  1. python首先会检查obj.class__dict__是否包含v(注意这里必须明白,在python中class也是对象,而class对象的__class__一般都是type),如果找到v,而且v是一个overriding descriptor,则调用overriding descriptor的__get__方法。如果没有找到,则会在obj.__class__的父类中查找。如果还是没有没有找到进行下一步
  2. 这一步python会分别对待class和instance。对于instance,python会检查object.__dict__是否存在,如果找到直接返回就可以了。而对于class,python会依次检查object.__dict__和object的父类的__dict__是否存在v,如果存在且v是descriptor则调用其__get__, 否则返回object.__dict__['v'],如果此时还是没有找到,则接着下一步
  3. 这时python又回到obj.__class__.__dict__中, 依然会依次查找obj._class__的父类的__dict__, 不过这次找的是descriptor而不是overriding descriptor(也不可能是overriding descriptor,否则第一步就找到了),  如果找到v是descriptor则调用其__get__,如果找到是普通属性,则直接返回。如果此时还找不到就会AttributeError
  • 对于__set__,我们同样假设要给obj.v赋值

 

  1. python首先还是会依次检查obj.__class__.__dict__和obj.__class__父类的__dict__, 如果找到v,且v是一个overriding descriptor则调用其__set__, 否则继续进行下一步
  2. 这时,python会obj.__dict__[‘v’]直接赋值
而对于这些查找策略的验证鉴于篇幅,不作验证。有兴趣的朋友可以自己验证一下。提示一下,一定要注意,对于这些策略的执行在你第一次使用时就开始应用,也就是在class的定义时(比如__init__)就要应用,而不是在class定义完成之后在使用class的时候才应用。

说了,这么多的descriptor。到底descriptor和文初的问题有什么关系呢?function本身就是一个descriptor(python中function也是对象)。那么当我访问一个instance或者class的方法时,是不是调用function的__get__呢?

	print func.__get__(None, Foo)
	print func.__get__(f, Foo)

这时我们发现输出的结果和Foo.method/f.method的一致,证明了我们想法是对的。

我们进一步分析,


print id(Foo.method)
print id(f.method)
print id(func)

输出如下:

4299638576
4299638576
4299376592

我们看到Foo.method和f.method返回的对象id和function的对象id不一样。那么这个__get__到底做了什么,会使得对象id都变了呢?

print func.__get__

输出:<method-wrapper '__get__' of function object at 0x1004347d0>

原来这个function的__get__是一个method-wrapper啊,那么就是说当我们通过instance或者class调用其method时,返回了一个function的wrapper。

再进一步:

print Foo.__dict__['method']

我们不让python调用function descriptor的__get__, 果然返回的结果和直接print func一模一样。

那么这个method-wrapper到底和instance,class以及所调用的function如何关联呢?

print f.method.im_func
print f.method.im_self
print f.method.im_class

这时输出:

<function func at 0x1004347d0>
<__main__.Foo object at 0x100475f90>
<class '__main__.Foo'>

清楚的看到im_func指向function,im_self指向调用的instanc(如果我们时通过class来执行这三个语句会 发现Foo.method.im_self返回None),而im_class则指向class。

  通过对bound method/unbound method 和function的分析,我们也了解了python中的descriptor这个非常重要的概念。我们在python的学习指路有迈出了坚实的一步。

posted on 2011-02-01 22:56  Fei He  阅读(6771)  评论(1编辑  收藏  举报