[python] Bound method or Function
我们先看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/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 有什么用呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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)。
- python首先会检查obj.class__dict__是否包含v(注意这里必须明白,在python中class也是对象,而class对象的__class__一般都是type),如果找到v,而且v是一个overriding descriptor,则调用overriding descriptor的__get__方法。如果没有找到,则会在obj.__class__的父类中查找。如果还是没有没有找到进行下一步
- 这一步python会分别对待class和instance。对于instance,python会检查object.__dict__是否存在,如果找到直接返回就可以了。而对于class,python会依次检查object.__dict__和object的父类的__dict__是否存在v,如果存在且v是descriptor则调用其__get__, 否则返回object.__dict__['v'],如果此时还是没有找到,则接着下一步
- 这时python又回到obj.__class__.__dict__中, 依然会依次查找obj._class__的父类的__dict__, 不过这次找的是descriptor而不是overriding descriptor(也不可能是overriding descriptor,否则第一步就找到了), 如果找到v是descriptor则调用其__get__,如果找到是普通属性,则直接返回。如果此时还找不到就会AttributeError
- 对于__set__,我们同样假设要给obj.v赋值
- python首先还是会依次检查obj.__class__.__dict__和obj.__class__父类的__dict__, 如果找到v,且v是一个overriding descriptor则调用其__set__, 否则继续进行下一步
- 这时,python会obj.__dict__[‘v’]直接赋值
1 2 | print func.__get__( None , Foo) print func.__get__(f, Foo) |
这时我们发现输出的结果和Foo.method/f.method的一致,证明了我们想法是对的。
我们进一步分析,
1 2 3 | print id (Foo.method) print id (f.method) print id (func) |
输出如下:
4299638576
4299638576
4299376592
我们看到Foo.method和f.method返回的对象id和function的对象id不一样。那么这个__get__到底做了什么,会使得对象id都变了呢?
1 | print func.__get__ |
输出:<method-wrapper '__get__' of function object at 0x1004347d0>
原来这个function的__get__是一个method-wrapper啊,那么就是说当我们通过instance或者class调用其method时,返回了一个function的wrapper。
再进一步:
1 | print Foo.__dict__[ 'method' ] |
我们不让python调用function descriptor的__get__, 果然返回的结果和直接print func一模一样。
那么这个method-wrapper到底和instance,class以及所调用的function如何关联呢?
1 2 3 | 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的学习指路有迈出了坚实的一步。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述