Python虚拟机类机制之从class对象到instance对象(五)
从class对象到instance对象
现在,我们来看看如何通过class对象,创建instance对象
demo1.py
class A(object):
name = "Python" def __init__(self):
print("A::__init__") def f(self):
print("A::f") def g(self, aValue):
self.value = aValue
print(self.value) a = A()
a.f()
a.g(10)
在Python虚拟机类机制之自定义class(四)这一章中,我们看到了Python虚拟机是如何执行class A语句的,现在,我们来看看,当我们实例化一个A对象,Python虚拟机又是如何执行的
a = A()
//字节码指令
22 LOAD_NAME 1 (A)
25 CALL_FUNCTION 0
28 STORE_NAME 2 (a)
在前面一节Python虚拟机类机制之自定义class(四),我们看到在创建class对象的最后,Python执行引擎通过STORE_NAME指令,将创建好的class对象放入到local名字空间,所以在实例化class A的时候,指令"22 LOAD_NAME 1 (A)"会重新将class A对象取出,压入到运行时栈中。之后,又是通过一个CALL_FUNCTION指令来创建instance对象。在创建完instance对象之后,再次通过STORE_NAME指令将实例对象a放入到local名字空间中。所以,这段字节码指令序列完成之后,local名字空间如图1-1所示
图1-1 创建instance对象后的local名字空间
在CALL_FUNCTION中,Python同样会沿着call_function->do_call->PyObject_Call的调用路径进入到PyObject_Call中。前面说过,所谓“调用”,就是执行对象的type所对应的class对象的tp_call操作。所以,在PyObject_Call中,Python执行引擎会寻找class对象<class A>的type中定义的tp_call操作。<class A>的type为<type 'type'>,所以,最终将调用tp_call,在PyType_Type.tp_call中又调用了A.tp_new是用来创建instance对象
这里需要特别注意,在创建<class A>这个class对象时,Python虚拟机调用PyType_Ready对<class A>进行了初始化,其中的一项动作就是继承基类的操作,所以A.tp_new会继承自object.tp_new。在PyBaseObject_Type中,这个操作被定义为object_new。创建class对象和创建instance对象的不同之处正是在于tp_new不同,创建class对象,Python虚拟机使用的是tp_new,而对于instance对象,Python虚拟机使用的object_new
在object_new中,调用了A.tp_alloc,这个操作也是从object继承而来的,是PyType_GenericAlloc。前面我们提到,PyType_GenericAlloc最终将申请A.tp_basicsize+A.tp_itemsize大小的内存空间。上一节,这两个量的计算结果为A.tp_basicsize=PyBaseObject_Type.tp_basicsize+8=sizeof(PyObject)+8=24;A.tp_itemsize=PyBaseObject_Type.tp_itemsize=0。原来,object_new的所有工作就是申请一个24字节的内存空间
在申请了24字节的内存空间,回到type_call之后,由于创建的不是class对象,而是instance对象,type_call会尝试进行初始化的动作
typeobject.c
static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *obj; obj = type->tp_new(type, args, kwds);
if (obj != NULL) {
if (type == &PyType_Type &&
PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
(kwds == NULL ||
(PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))
return obj;
if (!PyType_IsSubtype(obj->ob_type, type))
return obj;
type = obj->ob_type;
if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) &&
type->tp_init != NULL &&
type->tp_init(obj, args, kwds) < 0) {
Py_DECREF(obj);
obj = NULL;
}
}
return obj;
}
基于<class A>创建的instance对象obj,其ob_type当然也在PyType_GenericAlloc中被设置为指向<class A>,其tp_init在PyType_Ready时会继承PyBaseObject_Type的object_init操作,因为A的定义中重写了__init__,所以在fix_slot_dispatchers中,tp_init会指向slotdefs中指定的__init__对应的slot_tp_init
typeobject.c
static int slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
{
static PyObject *init_str;
PyObject *meth = lookup_method(self, "__init__", &init_str);
PyObject *res; if (meth == NULL)
return -1;
res = PyObject_Call(meth, args, kwds);
Py_DECREF(meth);
if (res == NULL)
return -1;
if (res != Py_None) {
PyErr_Format(PyExc_TypeError,
"__init__() should return None, not '%.200s'",
res->ob_type->tp_name);
Py_DECREF(res);
return -1;
}
Py_DECREF(res);
return 0;
}
在执行slot_tp_init时,Python虚拟机会首先通过lookup_method在class对象及其mro列表中搜索属性__init__对应的操作,然后通过PyObject_Call调用该操作。在定义class时,重写__init__操作,那么搜索的结果就是我们写的操作,如果没有重写,那么最终的结果将是调用object._init,在object_init中,Python虚拟机什么也不做,直接返回,所以,当我们用a = A()创建一个instance对象时,实际上没有进行任何初始化的动作
到这里,我们稍微小结一下从class对象到instance对象的两个步骤:
- instance = class.__new__(class, args, kwds)
- class.__init__(instance, args, kwds)
其中,args为一个tupple对象,里面包含着创建instance对象的各个参数,而kwds通常为NULL。需要注意的是,这两个步骤也适用于从metaclass对象创建class对象。从metaclass对象创建class对象的过程也是从一个从class对象创建instance对象
访问instance对象中的属性
在Python中,形如x.y或x.y()形式的表达式称为“属性引用”,其中x为对象,y为对象的属性。这个属性,有可能只是简单的数据,比如字符串或整数,也有可能是成员函数这类比较复杂的东西。在class A中一共有两个函数,一个是不需要参数的成员函数,一个是需要参数的成员函数,这里,我们先来看看,对于不需要参数的成员函数,其调用过程是怎样的
a.f()
//字节码指令
31 LOAD_NAME 2 (a)
34 LOAD_ATTR 3 (f)
37 CALL_FUNCTION 0
40 POP_TOP
Python虚拟机通过指令LOAD_NAME会将local名字空间与符号a对应的instance对象压入运行时栈中,随后执行指令"34 LOAD_ATTR 3"是属性访问机制的关键所在,它会从<instance a>中获得与符号f对应的对象,这是个PyFunctionObject对象
ceval.c
case LOAD_ATTR:
w = GETITEM(names, oparg);
v = TOP();
x = PyObject_GetAttr(v, w);
Py_DECREF(v);
SET_TOP(x);
if (x != NULL)
continue;
break;
其中,w为PyStringObject对象f,而v为运行时栈中的那个instance对象<instance a>,从<instance a>中获得f对应对象的关键就在PyObject_GetAttr中
object.c
PyObject * PyObject_GetAttr(PyObject *v, PyObject *name)
{
PyTypeObject *tp = v->ob_type;
//[1]:通过tp_getattro获得属性对应对象
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);
//[2]:通过tp_getattr获得属性对应对象
if (tp->tp_getattr != NULL)
return (*tp->tp_getattr)(v, PyString_AS_STRING(name));
//[3]:属性不存在,抛出异常
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%.400s'",
tp->tp_name, PyString_AS_STRING(name));
return NULL;
}
在Python的class对象中,定义了两个与访问属性相关的操作:tp_getattro和tp_getattr。其中的tp_getattro是首选的属性访问操作,而tp_getattr在Python中已不再推荐使用,它们之间的区别主要是在属性名的使用上,tp_getattro所使用的属性名必须是一个PyStringObject对象,而tp_attr所使用的属性名必须是一个C中的原生字符串。如果某个类型同时定义了tp_getattr和tp_getattro两种属性访问操作,那么PyObject_GetAttr将优先使用tp_getattro操作
在Python虚拟机创建<class A>时,会从PyBaseObject_Type中继承tp_getattro——PyObject_GenericGetAttr,所以Python虚拟机在这里会进入PyObject_GenericGetAttr。在PyObject_GenericGetAttr中,有一套复杂地确定访问属性的算法,下面以a.f为例,我们用伪代码看一下是如何确定这个属性的
# 首先寻找'f'对应的descriptor(descriptor在之后会细致剖析)
# 注意:hasattr会在<class A>的mro列表中寻找符号'f'
if hasattr(A, 'f'):
descriptor = A.f
type = descriptor.__class__
if hasattr(type, '__get__') and (hasattr(type, '__set__') or 'f' not in a.__dict__):
return type.__get__(descriptor, a, A) # 通过descriptor访问失败,在instance对象自身__dict__中寻找属性
if 'f' in a.__dict__:
return a.__dict__['f'] # instance对象的__dict__中找不到属性,返回a的基类列表中某个基类里定义的函数
# 注意:这里的descriptor实际上指向了一个普通的函数
if descriptor:
return descriptor.__get__(descriptor, a, A)
我们通过一段代码来验证这个伪代码的描述:
class A(object):
def func(self):
pass a = A()
a.func = 1
print(a.func)
这段代码很直观,最后会输出1,看上去与上面的伪代码描述的不对啊。实际上,上面的伪代码中有一个关键的概念——descriptor。在一个class中,并不是随意定义一个函数就是descriptor了,所以导致输出结果为1。那么,究竟什么才是descriptor呢?这个会在下章解答
Python虚拟机类机制之从class对象到instance对象(五)的更多相关文章
- Python虚拟机类机制之instance对象(六)
instance对象中的__dict__ 在Python虚拟机类机制之从class对象到instance对象(五)这一章中最后的属性访问算法中,我们看到“a.__dict__”这样的形式. # 首先寻 ...
- Python虚拟机类机制之绑定方法和非绑定方法(七)
Bound Method和Unbound Method 在Python中,当对作为属性的函数进行引用时,会有两种形式,一种称为Bound Method,这种形式是通过类的实例对象进行属性引用,而另一种 ...
- Python虚拟机类机制之descriptor(三)
从slot到descriptor 在Python虚拟机类机制之填充tp_dict(二)这一章的末尾,我们介绍了slot,slot包含了很多关于一个操作的信息,但是很可惜,在tp_dict中,与__ge ...
- Python虚拟机类机制之填充tp_dict(二)
填充tp_dict 在Python虚拟机类机制之对象模型(一)这一章中,我们介绍了Python的内置类型type如果要完成到class对象的转变,有一个重要的步骤就是填充tp_dict对象,这是一个极 ...
- Python虚拟机类机制之对象模型(一)
Python对象模型 在Python2.2之前,Python中存在着一个巨大的裂缝,就是Python的内置类type,比如:int和dict,这些内置类与程序员在Python中自定义的类并不是同一级别 ...
- Python虚拟机类机制之自定义class(四)
用户自定义class 在本章中,我们将研究对用户自定义class的剖析,在demo1.py中,我们将研究单个class的实现,所以在这里并没有关于继承及多态的讨论.然而在demo1.py中,我们看到了 ...
- Python虚拟机函数机制之位置参数的默认值(五)
位置参数的默认值 在Python中,允许函数的参数有默认值.假如函数f的参数value的默认值是1,在我们调用函数时,如果传递了value参数,那么f调用时value的值即为我们传递的值,如果调用时没 ...
- Python虚拟机函数机制之扩展位置参数和扩展键参数(六)
扩展位置参数和扩展键参数 在Python虚拟机函数机制之参数类别(三)的例3和例4中,我们看到了使用扩展位置参数和扩展键参数时指示参数个数的变量的值.在那里,我们发现在函数内部没有使用局部变量时,co ...
- Python虚拟机函数机制之参数类别(三)
参数类别 我们在Python虚拟机函数机制之无参调用(一)和Python虚拟机函数机制之名字空间(二)这两个章节中,分别PyFunctionObject对象和函数执行时的名字空间.本章,我们来剖析一下 ...
随机推荐
- mysql对库,表,数据类型的操作以及完整性约束
一丶对库的操作 求救语法: help create database; 1.创建数据库 CREATE DATABASE 数据库名 charset utf8; 2.数据库的命名规则: 可以由字母.数字. ...
- URL最大长度问题
在http协议中,其实并没有对url长度作出限制,往往url的最大长度和用户浏览器和Web服务器有关,不一样的浏览器,能接受的最大长度往往是不一样的,当然,不一样的Web服务器能够处理的最大长度的UR ...
- zabbix 监控项
监控项 概述 监控项是从主机收集的数据信息. 配置主机后,你需要添加一些监控项以开始获取实际数据. 一个监控项是一个独立的指标.快速添加多个监控项的一种方法是将一个预定义的模板附加到主机.然而,为了优 ...
- w3cschool中jQuery测试结果总结
1.jQuery 是 W3C 标准. 2.$("div#intro .head") 选择器选取:class="intro" 的任何 div 元素中的首个 id= ...
- LeetCode Word Ladder 找单词变换梯
题意:给出两个单词,以及一个set集合,当中是很多的单词.unordered_set是无序的集合,也就是说找的序列也是无序的了,是C++11的标准,可能得升级你的编译器版本了.要求找出一个从start ...
- linux 命令——9 touch (转)
linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件. 1.命令格式: touch [选项]... 文件... 2.命令参数: -a ...
- geoWithin查询 多边形查询
$geoWithin查询 形状的表示 .$box:矩形,使用 {$box:[[<x1>,<y1>],[<x2>,<y2>]]}表示 都是坐标,第一个坐标 ...
- 2018.6.7. 云服务器Centos系统使用yum或者rpm安装包时出现问题,安装时报出错误:
当我向终端输入 sudo yum groupinstall chinese-support 语言安装包的时候显示下面的错误 error: rpmdb: BDB0113 Thread/process 3 ...
- Linux命令安装vnc服务端与vnc的客户端
第一歩:运行命令 yum install tigervnc-server -y 第二歩:安装telnet 第三歩:运行vncserver,创建桌面 vncserver -kill :1 删除桌面1的 ...
- 1305: [CQOI2009]dance跳舞
Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 4169 Solved: 1804[Submit][Status][Discuss] Descripti ...