Python虚拟机类机制之绑定方法和非绑定方法(七)
Bound Method和Unbound Method
在Python中,当对作为属性的函数进行引用时,会有两种形式,一种称为Bound Method,这种形式是通过类的实例对象进行属性引用,而另一种则是通过类进行属性引用,称为Unbound Method。当然,对Bound Method和Unbound Method的调用形式是不同的,其原因可以追溯到LOAD_ATTR中
demo2.py
class A(object):
def g(self, value):
self.value = value
print(self.value) a = A()
A.g(a, 10)
其对应的字节码指令序列:
>>> source = open("demo2.py").read()
>>> co = compile(source, "demo2.py", "exec")
>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 ('A')
3 LOAD_NAME 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 1 (<code object A at 0x7f3e67870d50, file "demo2.py", line 1>)
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME 1 (A)
7 22 LOAD_NAME 1 (A)
25 CALL_FUNCTION 0
28 STORE_NAME 2 (a)
8 31 LOAD_NAME 1 (A)
34 LOAD_ATTR 3 (g)
37 LOAD_NAME 2 (a)
40 LOAD_CONST 2 (10)
43 CALL_FUNCTION 2
46 POP_TOP
47 LOAD_CONST 3 (None)
50 RETURN_VALUE
其中的关键就在于"34 LOAD_ATTR 3 (g)"指令,之前我们已经解释过,这里的LOAD_ATTR指令最终会调用type_getattro。在type_getattro中,会在<class A>的tp_dict中发现"g"对应的PyFunctionObject,同样,因为它是一个descriptor,因此也会调用其__get__函数进行转变。在Python虚拟机类机制之从class对象到instance对象(五)这一章剖析a.f时,我们看到这个转变是通过func_descr_get(A.f, a, A)完成的,这里对"g"的转变则是通过func_descr_get(A.g, NULL, A)完成。因此,虽然A.g也得到一个PyMethodObject,但是其中的im_self确实NULL
在Python中,在对Unbound Method尽心调用时,我们必须显示地提供一个instance对象作为函数的第一个位置参数,因为g无论如何都需要一个self参数。所以才会有A.g(a, 10)这样的形式。而无论是对Unbound Method进行调用,还是对bound Method进行调用,Python虚拟机的动作在本质上都是一样的,都是调用带位置参数的一般函数,区别只在于:当调用Bound Method时,Python虚拟机帮我们完成了PyFunctionObject对象和instance对象的绑定,instance对象将自动成为self参数,而调用Unbound Method时,则没有这个绑定,需要我们自己传入self参数
下面的代码展示出Bound Method和Unbound Method的不同
>>> class A(object):
... def f(self):
... pass
...
>>> a = A()
>>> bound = a.f
>>> unbound = A.f
>>>
>>> bound
<bound method A.f of <__main__.A object at 0x7f3e677c7310>>
>>> unbound
<unbound method A.f>
>>>
>>> bound.im_self
<__main__.A object at 0x7f3e677c7310>
>>> unbound.im_self
>>>
在输出Unbound method对象的im_self域时,没有任何东西输出,因为这个域本身就是NULL
对于成员函数的调用,或者说对于成员函数的绑定过程,有一点值的注意的是,每一次函数调用都会激发一次绑定过程。其原因在于,每次进行属性引用时,都会重新获得属性对应的PyFunctionObject(descritpro),进而创建新的PyMethodObject对象,这一点的开销实在是有些大,下面两段代码展示了不同函数调用方式的绑定次数
class A(object):
def f(self):
pass a = A()
# 函数绑定100次
for i in range(100):
a.f()
class A(object):
def f(self):
pass a = A()
func = a.f
# 函数绑定1次
for i in range(100):
func()
千变万化的descriptor
当我们调用instance对象的函数时,最关键的一个动作就是从PyFunctionObject对象向PyMethodObject对象的转变,而这个关键的转变被Python中descriptor概念很自然地融入到Python的类机制中。当我们访问对象中的属性时,由于descriptor的存在,这种转换自然而然地发生了。将这种descriptor的思想推而广之,其实在访问属性时,我们不光能实现从PyFunctionObject到PyMethodObject对象的转变,实际上我们可以做任何事情。在Python内部,也存在着各式各样的descriptor,这些descriptor的存在给Python的类机制赋予了更多的能力。现在,我们来看看Python是如何使用descriptor实现static method的
demo3.py
class A(object):
def g(value):
print(value) g = staticmethod(g)
demo3.py对应字节码指令序列:
>>> source = open("demo3.py").read()
>>> co = compile(source, "demo3.py", "exec")
>>> co.co_consts
('A', <code object A at 0x7f3e677c9198, file "demo3.py", line 1>, None)
>>> A_co = co.co_consts[1]
>>> import dis
>>> dis.dis(A_co)
1 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
2 6 LOAD_CONST 0 (<code object g at 0x7f3e677c90a8, file "demo3.py", line 2>)
9 MAKE_FUNCTION 0
12 STORE_NAME 2 (g)
4 15 LOAD_NAME 3 (staticmethod)
18 LOAD_NAME 2 (g)
21 CALL_FUNCTION 1
24 STORE_NAME 2 (g)
27 LOAD_LOCALS
28 RETURN_VALUE
在为A创建动态元信息的过程中,Python虚拟机首先会执行一个def语句,将符号"g"和一个PyFunctionObject对象关联起来,但随后的g = staticmethod(g)则会将"g"与一个staticmethod对象关联起来,从而将属性"g"改造为一个static method
Python虚拟机在执行"15 LOAD_NAME 3 (staticmethod)"指令时,会从builtin名字空间中获得一个与符号"staticmethod"对应的对象,这个对象在Python启动并初始化时设置,它其实是一个class对象
>>> staticmethod
<type 'staticmethod'>
所以,执行staticmethod(g)的过程就是一个从class对象创建instance对象的过程,最终将调用PyObject_GenericAlloc申请一段内存,内存空间的大小由staticmethod结构体决定:
typedef struct {
PyObject_HEAD
PyObject *sm_callable;
} staticmethod;
申请完内存之后,Python虚拟机还会调用__init__进行初始化操作,<type 'staticmethod'>在Python内部对应的是PyStaticMethod_Type,而其中的tp_init设置为sm_init:
static int
sm_init(PyObject *self, PyObject *args, PyObject *kwds)
{
staticmethod *sm = (staticmethod *)self;
PyObject *callable; if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable))
return -1;
if (!_PyArg_NoKeywords("staticmethod", kwds))
return -1;
Py_INCREF(callable);
sm->sm_callable = callable;
return 0;
}
在初始化时,原来的参数"g"对应的PyFunctionObject被赋给staticmethod对象中的sm_callable。最后,Python虚拟机通过指令"24 STORE_NAME 2 (g)"将符号"g"和这个staticmethod对象关联起来
在仔细考察PyStaticMethod_Type,发现这里创建的staticmethod对象实际上也是个descriptor,因为在PyStaticMethod_Type中,tp_descr_get指向了sm_descr_get
static PyObject * sm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
staticmethod *sm = (staticmethod *)self; if (sm->sm_callable == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"uninitialized staticmethod object");
return NULL;
}
Py_INCREF(sm->sm_callable);
return sm->sm_callable;
}
当我们访问属性"g"时,不论是通过instance对象访问a.g,还是通过class对象访问A.g,由于"g"是一个位于class对象<class A>的tp_dict中的descriptor,所以会调用其__get__操作(sm_descr_get),直接了当地返回其中保存的最开始与"g"对应的PyFunctionObject对象
Python虚拟机类机制之绑定方法和非绑定方法(七)的更多相关文章
- Python虚拟机类机制之instance对象(六)
instance对象中的__dict__ 在Python虚拟机类机制之从class对象到instance对象(五)这一章中最后的属性访问算法中,我们看到“a.__dict__”这样的形式. # 首先寻 ...
- Python虚拟机类机制之descriptor(三)
从slot到descriptor 在Python虚拟机类机制之填充tp_dict(二)这一章的末尾,我们介绍了slot,slot包含了很多关于一个操作的信息,但是很可惜,在tp_dict中,与__ge ...
- Python虚拟机类机制之填充tp_dict(二)
填充tp_dict 在Python虚拟机类机制之对象模型(一)这一章中,我们介绍了Python的内置类型type如果要完成到class对象的转变,有一个重要的步骤就是填充tp_dict对象,这是一个极 ...
- Python虚拟机类机制之从class对象到instance对象(五)
从class对象到instance对象 现在,我们来看看如何通过class对象,创建instance对象 demo1.py class A(object): name = "Python&q ...
- Python虚拟机类机制之自定义class(四)
用户自定义class 在本章中,我们将研究对用户自定义class的剖析,在demo1.py中,我们将研究单个class的实现,所以在这里并没有关于继承及多态的讨论.然而在demo1.py中,我们看到了 ...
- Python虚拟机类机制之对象模型(一)
Python对象模型 在Python2.2之前,Python中存在着一个巨大的裂缝,就是Python的内置类type,比如:int和dict,这些内置类与程序员在Python中自定义的类并不是同一级别 ...
- 1.面向过程编程 2.面向对象编程 3.类和对象 4.python 创建类和对象 如何使用对象 5.属性的查找顺序 6.初始化函数 7.绑定方法 与非绑定方法
1.面向过程编程 面向过程:一种编程思想在编写代码时 要时刻想着过程这个两个字过程指的是什么? 解决问题的步骤 流程,即第一步干什么 第二步干什么,其目的是将一个复杂的问题,拆分为若干的小的问题,按照 ...
- 全面解析python类的绑定方法与非绑定方法
类中的方法有两类: 绑定方法 非绑定方法 一.绑定方法 1.对象的绑定方法 首先我们明确一个知识点,凡是类中的方法或函数,默认情况下都是绑定给对象使用的.下面,我们通过实例,来慢慢解析绑定方法的应用. ...
- 类的封装,property特性,类与对象的绑定方法和非绑定方法,
类的封装 就是把数据或者方法封装起来 为什么要封装 封装数据的主要原因是:保护隐私 封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了,比如你 ...
随机推荐
- DB2常用函数详解
(一) 字符串函数 VALUE函数 语法:VALUE(EXPRESSION1,EXPRESSION2) VALUE函数是用返回一个非空的值,当其第一个参数非空,直接返回该参数的值,如果第一个参数为空 ...
- How to Install Apache Solr 4.5 on CentOS 6.4
By Shay Anderson on October 2013 Knowledge Base / Linux / How to Install Apache Solr 4.5 on Cent ...
- border实现矩形中斜线分割 切换按钮
思路:将该矩形分为三个div,中间的div使用border的特性 代码实现如下:
- Jackson使用手册
引用jar:jackson-core,jackson-databind,jackson-annotations http://central.maven.org/maven2/com/fasterxm ...
- python使用浮点类型float计算后,数值不对_20180827
在练习时,输入如下代码: 结果不准确. 原因:https://blog.csdn.net/bitcarmanlee/article/details/51179572 浮点数一个普遍的问题就是在计算机的 ...
- ArcGIS10.1的安装问题
注:必须用3个带0的文件夹里面的东西安装 1.先装Pre-release_license_manager ,然后停掉. 2.然后安装0Desktop/ArcGIS_Desktop, 3.打开0Ke ...
- LNA与PA
LNA是低噪声放大器,主要用于接收电路设计中.因为接收电路中的信噪比通常是很低的,往往信号远小于噪声,通过放大器的时候,信号和噪声一起被放大的话非常不利于后续处理,这就要求放大器能够抑制噪声.PA(功 ...
- linux 命令——61 wget(转)
Linux系统中的wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,我们经常要下载一些软件或从远程服务器恢复备份到本地服务器.wget支持HTTP,HTTPS和FTP协 ...
- 【转】IOS基础:深入理解Objective-c中@class的含义
objective-c中,当一个类使用到另一个类时,并且在类的头文件中需要创建被引用的指针时, 如下面代码: A.h文件 #import "B.h" @interface A : ...
- python redis基本操作
#!/usr/bin/env python #-*- coding:utf-8 -*- # author:leo # datetime:2019/4/24 16:26 # software: PyCh ...