从字节码入手
a = 1
编译:
LOAD_CONST 0 (1)
STORE_NAME 0 (a)
根据官方文档的字节码解释:
LOAD_CONST从codeobject的co_consts这个列表中读取对应索引的值到栈顶,这里是列表的第0个元素,值为longobject(1)
STORE_NAME将栈顶元素绑定到codeobject的co_names这个列表的第i个元素,这里i是0,也就是stringobject(‘a’)
这时我有一个问题:STORE_NAME后是否弹出了栈顶元素?
在ceval中找到对应代码:
TARGET(STORE_NAME) {
PyObject *name = GETITEM(names, oparg);
PyObject *v = POP();
PyObject *ns = f->f_locals;
int err;
if (ns == NULL) {
PyErr_Format(PyExc_SystemError,
"no locals found when storing %R", name);
Py_DECREF(v);
goto error;
}
if (PyDict_CheckExact(ns))
err = PyDict_SetItem(ns, name, v);
else
err = PyObject_SetItem(ns, name, v);
Py_DECREF(v);
if (err != 0)
goto error;
DISPATCH();
}
看看TARGET这个宏:
#define TARGET(op) \
case op:
好吧,其实就是一个case。
让我们看一下STORE_NAME做了些啥:
1. 首先获取到要保存的名字
2. 获取要保存的栈顶变量
注意,这里用了POP,猜都能猜到,栈顶已经被删除了。。。。。答案已经找到了,但是我想继续看看后面怎么做
3. 栈帧的locals,也就是局部变量,保存到ns
4. 然后检测ns是否是一个字典,如果是就当作字典,否则就当做一般对象,将名字和对应的值保存到当前栈帧的f_locals,完事!
这里:
if (PyDict_CheckExact(ns))
err = PyDict_SetItem(ns, name, v);
else
err = PyObject_SetItem(ns, name, v);
一般对象的setitem和字典对象的setitem引起了我的兴趣,我决定一探究竟,看看他们有什么区别
首先看PyDict_SetItem:
int
PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
{
PyDictObject *mp;
Py_hash_t hash;
if (!PyDict_Check(op)) {
PyErr_BadInternalCall();
return -1;
}
assert(key);
assert(value);
mp = (PyDictObject *)op;
if (!PyUnicode_CheckExact(key) ||
(hash = ((PyASCIIObject *) key)->hash) == -1)
{
hash = PyObject_Hash(key);
if (hash == -1)
return -1;
}
/* insertdict() handles any resizing that might be necessary */
return insertdict(mp, key, hash, value);
}
不得不说看python源码就跟看小说一样,一眼就能看清代码的意图,你看一进来先是三个参数的检查,然后计算key的hash值,然后把dict,key,hash,value传给insertdict去进行最后的插入操作,下一步就不去跟进了,要看了字典的具体实现才能理解,毕竟python3.4以后字典做了较大的改动,比较麻烦。
回过头来,再看看PyObject_SetItem的实现:
int
PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)
{
PyMappingMethods *m;
if (o == NULL || key == NULL || value == NULL) {
null_error();
return -1;
}
m = o->ob_type->tp_as_mapping;
if (m && m->mp_ass_subscript)
return m->mp_ass_subscript(o, key, value);
if (o->ob_type->tp_as_sequence) {
if (PyIndex_Check(key)) {
Py_ssize_t key_value;
key_value = PyNumber_AsSsize_t(key, PyExc_IndexError);
if (key_value == -1 && PyErr_Occurred())
return -1;
return PySequence_SetItem(o, key_value, value);
}
else if (o->ob_type->tp_as_sequence->sq_ass_item) {
type_error("sequence index must be "
"integer, not '%.200s'", key);
return -1;
}
}
type_error("'%.200s' object does not support item assignment", o);
return -1;
}
其中有几个不知道是干嘛的方法
mp_ass_subscript
PySequence_SetItem
不过可以猜到,前者是字典的setitem,而后者是序列对象的setitem,key必须是Py_ssize_t。
源码分析任重而道远,本篇只是一个开头