[Python] Python 之 function, unbound method 和 bound method
首先看一下以下示例。(Python 2.7)
#!/usr/bin/env python
# -*- coding: utf-8 -*- class C(object):
def foo(self):
pass c = C() print 'C.foo id:', id(C.foo), type(C.foo)
print 'c.foo id:', id(c.foo), type(c.foo) a = C.foo
b = c.foo print 'a = C.foo id:', id(a), type(a)
print 'b = c.foo id:', id(b), type(b)
输出:
C.foo id: 2582008 <type 'instancemethod'>
c.foo id: 2582008 <type 'instancemethod'>
a = C.foo id: 2582008 <type 'instancemethod'>
b = c.foo id: 2485624 <type 'instancemethod'>
为什么第1到3行输出id相同,而第4行id则变了呢?
当你通过句号标记法查找一个对象的方法时,例如class.name或者instance.name,Python会自动为该对象创建一个新的method对象。Python使用描述器来将一个function对象封装为method对象。
因此,当你使用id()函数查找C.foo的id时,Python创建了一个新的method对象,当你读取了这个method对象的id(其实就是内存地址)后,这个method对象就被销毁了,因此它所占用的内存被释放出来。接着你用同样的方法查找c.foo的id,一个新的method对象在刚才被释放的内存中被创建并随之销毁,因为前后两个对象都是在同一个内存地址创建,因此你看到的id(内存地址)是一样的。为什么两个method对象都会在读取完id后被销毁呢?这与Python的垃圾回收机制有关,Python使用引用计数器来控制垃圾回收,当一个对象引用次数为0时就会被回收销毁,因此当id()函数执行完毕后,再没有任何其他对象引用这两个method对象,因此它们被销毁释放出内存。
接下来的第13、14行,你将待绑定方法(unbound method)C.foo的一个索引(或者称引用)保存到一个变量a中,因为此时刚刚新建的C.foo对象的索引计数器值为1,不是0,因此它没有被当作垃圾回收并销毁,因此也就不会释放它所占用的内存。随后你使用c.foo创建了第二个method对象,它被指派在另一个新的内存地址上,因此最后你看到的id(a)和id(b)的值是不一样的。
当你使用id()函数查看对象的id时,应该知道以下官方关于id()的描述:
Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value. CPython implementation detail: This is the address of the object in memory. 返回一个对象的唯一标识。该标识为一个整数(或者long类型整数),该整数保证了唯一性,并且伴随所标识的对象的整个生命周期。两个生命周期没有重叠的对象可能具有相同的id值。 CPython的实现细节:充当对象身份标识的整数是对象在内存中的地址。
你也可以使用类的__dict__属性通过索引直接指向目标函数,然后调用目标函数的__get__方法创建一个method对象。
Python 2.7.3 (default, Feb 27 2014, 21:38:55)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class C(object):
... def foo(self):
... pass
...
>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x2ec5b0>
>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>
>>> C.__dict__['foo'].__get__(C(), C)
<bound method C.foo of <__main__.C object at 0x2ef550>>
以上示例中,第4行创建了一个类C,第8行创建了一个待绑定方法对象(unbound method)C.foo,第10行引用了一个函数对象(function),第12行创建了一个待绑定方法对象C.foo,第14行创建了一个已绑定方法对象(bound method)。
其中第10行所引用的函数对象每次被调用都是引用同一个函数对象,而第8行、12行、14行每次调用都是新创建一个method对象。
function对象的__get__方法负责将函数封装为一个method对象,这个method对象可以是bound method或者unbound method。
>>> def b(x):print "Argument x is ", x
...
>>> b.__get__
<method-wrapper '__get__' of function object at 0x55d770>
>>> b.__get__(None, C)
<unbound method C.b>
>>> b.__get__(C, C)()
Argument x is <class '__main__.C'>
>>> b.__get__(None, C)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method b() must be called with C instance as first argument (got nothing instead)
函数b通过调用__get__方法可以将自己封装为一个method,如果使用一个class对象来调用函数的__get__方法,得到的就是unbound method,使用instance对象来调用函数的__get__方法,得到的就是bound method。函数的__get__方法所实行的封装过程,其实就是将函数绑定到一个对象上,无论是unbound method还是bound method,最终都是绑定到一个类或者实例上,因此这里所谓的unbound和bound其实是针对该方法是否是绑定到实例而言。
对于在类中定义的实例方法,例如上述示例中的class C中的foo方法,c.foo()等同于C.foo(c),不同的是前者的foo是bound method,后者foo是unbound method。
在Python 3K中,已经去除unbound method和bound method两个概念,取而代之的是,C.foo将得到一个函数对象,而c.foo将得到一个方法。新的定义更加合理清晰,不容易使陌生人感到困惑。
之所以做出这样的修正,是因为Python对方法的理解与大多数语言不同,Python 3K对方法的理解是:方法是一个预置了一个实例对象作为第一个参数的函数。预置一个实例对象使得函数变成了一个已绑定方法(bound method)。
而Python 2K对于方法的理解是:一个方法如果没有和一个实例进行绑定,那么它就是一个unbound method,一个unbound method对象可以视为一种函数,这种函数受到一种限制,就是必须将一个实例作为第一个参数传入。于是unbound method就是处于一种待命状态,一旦被绑定就成了一个bound method。
但是,随着时间的脚步,人们发现unbound method就是一个function,而对这个函数作出首个参数必须是正确的实例的限制意义不大,因此在Python 3中就去除了这个限制,使之更加符合鸭子理论。
了解更多《First-class Everything (Python缔造者Guido van Rossum关于bound/unbound method的来历叙述)》
[Python] Python 之 function, unbound method 和 bound method的更多相关文章
- Bound Method and Unbound Method - 绑定方法 与 非绑定方法
Bound Method and Unbound Method 通常有两种方法对类的方法(instance.method)/属性(class.attribute)进行引用, 一种称做 Bound Me ...
- [转载]Python方法绑定——Unbound/Bound method object的一些梳理
本篇主要总结Python中绑定方法对象(Bound method object)和未绑定方法对象(Unboud method object)的区别和联系.主要目的是分清楚这两个极容易混淆的概念,顺便将 ...
- python tips:类的绑定方法(bound)和非绑定方法(unbound)
类属性只有类及其实例能够访问,可以理解为一个独立的命名空间. Python中类属性的引用方式有两种: 1. 通过类的实例进行属性引用,称为绑定方法(bound method),可以理解为方法与实例绑定 ...
- Python OOP(2)-static method,class method and instance method
静态方法(Static Method): 一种简单函数,符合以下要求: 1.嵌套在类中. 2.没有self参数. 特点: 1.类调用.实例调用,静态方法都不会接受自动的self参数. 2.会记录所有实 ...
- python --- Python中的callable 函数
python --- Python中的callable 函数 转自: http://archive.cnblogs.com/a/1798319/ Python中的callable 函数 callabl ...
- Micro Python - Python for microcontrollers
Micro Python - Python for microcontrollers MicroPython
- 从Scratch到Python——python turtle 一种比pygame更加简洁的实现
从Scratch到Python--python turtle 一种比pygame更加简洁的实现 现在很多学校都开设了Scratch课程,学生可以利用Scratch创作丰富的作品,然而Scratch之后 ...
- 从Scratch到Python——Python生成二维码
# Python利用pyqrcode模块生成二维码 import pyqrcode import sys number = pyqrcode.create('从Scratch到Python--Pyth ...
- [Python]Python 使用 for 循环的小例子
[Python]Python 使用 for 循环的小例子: In [7]: for i in range(5): ...: print "xxxx" ...: print &quo ...
随机推荐
- git之移除.idea
有时候不小心提交了.idea目录,git会一直track这个目录,可以通过一下命令移除: mv .idea ../.idea_backup rm -r .idea git rm -r .idea gi ...
- 解决IE6下浮层遮盖select刺穿的问题
图一未解决刺穿问题: 图二已解决 解决方法使用iframe间接挡住层,具体方法见源码 源码一(未解决刺穿): <html xmlns=" ...
- Maven依赖机制
案例分析 让我们看一个案例研究,以了解它是如何工作的.假设你想使用 Log4j 作为项目的日志.这里你要做什么? 1.在传统方式 访问 http://logging.apache.org/log4j/ ...
- 获取Java正在执行的方法
new Object(){}.getClass().getEnclosingMethod().getName(); Thread.currentThread().getStackTrace()
- 绚丽而实用的jQuery/CSS3应用及源码
HTML5加入WEB以后,网页世界就变得丰富绚丽起来了,但是我们在项目应用中,不仅需要绚丽的动画效果,而且更需要有实用的价值.今天分享的一些jQuery/CSS3应用不仅绚丽,而且还比较实用,如果感兴 ...
- kafka基本概念
介绍 Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计. 这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统术语:Kafka将消息以to ...
- 关于WSDL的理解
WSDL是由types, message, portType组成. 而binding, service则是具体的绑定. portType有哪些operation. 然后是portType的Endpoi ...
- 浅谈你感兴趣的 CLR GC 机制底层
本文内容是学习CLR.via C#的21章后个人整理,有不足之处欢迎指导. 昨天是1024,coder的节日,我为自己coder之路定下一句准则--保持学习,保持自信,保持谦逊,保持分享,越走越远. ...
- [Learn AF3]第五章 App Framework 3组件之Drawer——Side Menu
Drawer——Side menu 组件名称:Drawer 说明:af3中的side menu和af2中有很大变化,af3中的side menu实际上是通过插件$.afui.drawer来实现 ...
- (转)Java程序员应该知道的10个调试技巧
(转自 酷勤网 – 程序员的那点事!http://www.kuqin.com/) 试可以帮助识别和解决应用程序缺陷,在本文中,作者将使用大家常用的的开发工具Eclipse来调试Java应用程序.但这里 ...