基础概念
实现
Python 的官方实现是 CPython. 另外还有其他实现, 如:
- Pypy(使用 rPython 实现, 附有编译器, 有 JIT).
- Jython
- IronPython
- Pyston
本文讨论 CPython.
虚拟机
与一些 做成对象的虚拟机 相比, CPython 的没有做成对象, 而是做成全局变量. 这是 Python 所谓 全局锁导致无法多线程运行 的缘由.
这是 CPython 初始化:
Py_Initialize();
这是 Lua 的初始化:
lua_State* L = lua_newstate(luaalloc, this);
根基
所有变量的读取, 设置都基于一个模块之上, 通常这个模块叫 __main__
. 定义一个全局变量, 一个函数等, 实际上这些对象都"挂"在了这个 __main__
模块之上.
如果需要设置一个变量如 a = 233
, 实际上就是给 __main__
模块设置了属性 __main__.a = 233
.
对象
Python 中一切实体都是对象, 并且有一些统一的操作接口, 如一切对象都可以取 __doc__
属性, 一切对象都有其所属类型, 这个类型也是对象.
一切对象原则上都是不可修改的, 除了 list
和 dict
.
一个对象有了 __dict__
属性后, 即可以随意挂其他对象上去. 表面看起来像修改了对象, 实际上只是给对象的 __dict__
属性下的字典加了一个条目. 如 __main__.a = 233
实际上是 __main__.__dict__['a'] = 233
.
自定义的类的实例一般其 __dict__
属性下都自带有字典. 有些类则没有 __dict__
属性, 如 int
, float
等.
引用计数
参考资料: 扩展Python模块系列(四)----引用计数问题的处理
查看 PyObject 的结构体, 可以发现有个 ob_refcnt 成员. 这是计算引用数的变量. 使用 API 获取对象时, 有些函数会修改变量的引用, 有些不会. 丢弃变量时注意确定好是否要调用 Py_DECREF
将引用数减一.
下面是操作引用数的宏:
- Py_INCREF, 不可传入NULL
- Py_XINCREF, 可传入NULL
- Py_DECREF, 不可传入NULL
- Py_XDECREF, 可传入NULL
释放对象, 并清零指针可以用 Py_CLEAR(可传入NULL).
获取引用数可以用 Py_REFCNT(不可传入NULL)
从 API 可以获取 PyObject 和传递 PyObject
获取对象时有两种结果
- 新引用 (New Reference), API 内部对对象调用了
Py_INCREF
之后才传出来的对象, C 方面需要承担管理对象的职责, 并在不使用对象时, 调用Py_DECREF
或将管理权转移. - 借引用 (Borrowed Reference), API 内部只是复制了对象地址出来, 除非 C 方面调用
Py_INCREF
, 否则时刻有对象被回收的风险.
传递对象时有两种结果
- 新引用 (默认), API 接收到对象后会对其调用
Py_INCREF
, 之后会在 API 内部其他地方调用Py_DECREF
如此自行管理对象, C 方面还需继续承担管理对象的职责. - 偷引用 (“steals” a reference), API 接收对象时不调用
Py_INCREF
, 但依旧会在内部其他地方调用Py_DECREF
, C 方面可以转移管理权给 API, 之后 C 方面不能调用Py_DECREF
.
注意, 用 新引用 得来的对象不能直接传给 新引用 接口, C 方面还需调用 Py_DECREF
或转移管理权, 否则会造成内存泄漏.
借引用 得来的对象也不能直接传给 偷引用 接口.
Qt
Python.h 在 Qt 中编译时会报错. 原因是 Python.h 第448行, PyType_Spec 结构体使用了 slots 作为成员名. 解决方法是在该行前 #undef slots, 该行后重新 #define slots Q_SLOTS
初始化
全局虚拟机
void Py_Initialize(); // 初始化
int Py_IsInitialized(); // 是否初始化成功
void Py_Finalize(); // 释放
变量
官方文档见: 具体的对象层
据文档, 主要有六大类对象, 分别为:
- 基本对象, 包括 Type, None.
- 数值对象, 包括 int, bool, float, complex.
- 序列对象, 包括 bytes, 字节数组, str, tuple, list, 结构体序列
- 容器对象, 包括 dict, set
- 函数对象, 可调用有函数, 方法, 实例方法, 不可调用有 Cell, 代码.
- 其他对象
构建对象可以用 Python 代码构建, 也可以在 C 用 API 构建. 下面介绍用 API 构建的方法.
通用构建
其中前四大类大部分对象可以用 PyObject* Py_BuildValue(const char* format, ...);
完成构建, 返回 新引用. 其构造思路类似于 printf, 参数 format 使用类似 Python 语法的字符串, 其中一些值用指示字符代替, 在变参部分传入 C 的数据, 按顺序替换掉 format 中的指示字符, 以构造 Python 对象.
例:
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) 'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
指示字符及其应传入的 C 的数据类型的转换表如下:
数字
指示字符 | Pyhton对象 | C | 备注 |
---|---|---|---|
B | int | unsigned char | |
b | int | char | |
H | int | unsigned short | |
h | int | short | |
I | int | unsigned int | |
i | int | int | |
k | int | unsigned long | |
l | int | long | |
K | int | unsigned long long | |
L | int | long long | |
n | int | Py_ssize_t | ssize_t |
f | float | float | |
d | float | double |
字符串
指示字符 | Pyhton对象 | C | 备注 |
---|---|---|---|
c | bytes | char | 长度为1 |
C | str | int | 长度为1 |
y | bytes 或 None | const char* | |
y# | bytes 或 None | const char*, int | |
s z U | str 或 None | const char* | utf-8 |
s# z# U# | str 或 None | const char*, int | utf-8 |
u | str 或 None | const wchar_t* | UTF-16 or UCS-4 |
u# | str 或 None | const wchar_t*, int | UTF-16 or UCS-4 |
对象引用
指示字符 | Pyhton对象 | C | 备注 |
---|---|---|---|
O S U | object | PyObject* | 对象引用计数加一, 新引用 |
N | object | PyObject* | 对象引用计数不变, 偷引用 |
O& | object | converter, anything | 调用converter(anything)创建对象 |
容器
指示字符 | Pyhton对象 | C | 备注 |
---|---|---|---|
(…) | tuple | … | 用多个对象创建元组 |
[…] | list | … | 用多个对象创建列表 |
{…: …, …} | dict | … | 用多对对象创建字典 |
具体构建
官方文档见: 具体的对象层
C函数
给 Python 调用的 C 函数有形参要求. 通常会作为 C API 的包装(wrapper)函数, 也可以直接用这些函数写业务逻辑.
C 函数有两种方法提供给 Python. 一是作为自定义的模块的函数加载入 Python. 二是用 PyCFunction_New
生成独立的C函数对象.
官方文档: 使用 C 或 C++ 扩展 Python, Common Object Structures PyMethodDef
这里介绍用 PyCFunction_New
生成独立的C函数对象(新引用).
- 函数实现
函数类型依调用约定的不同, 有如下几种:
PyObject *(*PyCFunction)(PyObject *self, PyObject *args);
PyObject *(*_PyCFunctionFast) (PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames);
PyObject *(*PyCFunctionWithKeywords)(PyObject *self, PyObject *args, PyObject *kwargs);
PyObject *(*PyNoArgsFunction)(PyObject *self);
可以选择任意一种进行实现. 参数传来的对象是 借引用, 无需调用 Py_DECREF
.
一个使用 METH_VARARGS
调用约定的做加法的函数的例子:
PyObject* myaddfunc(PyObject* self, PyObject* args) {
int ok;
int a = -1, b = -1;
ok = PyArg_ParseTuple(args, "ii", &a, &b);
if(ok) {
return PyLong_FromLong(a + b);
} else {
printf("失败\n");
}
Py_RETURN_NONE;
}
- 填充 函数描述结构体
PyMethodDef myaddmethoddef = {
"add", // 内部方法名
(PyCFunction*)myaddfunc, // 函数地址, 但无论函数原型如何, 都要转为PyCFunction*型, 但函数真正原型要遵守调用约定的规则
METH_VARARGS, // 调用约定
"add function." // __doc__
};
其中调用约定有如下位可选(相互间即可用 |
组合):
METH_VARARGS
, 要求函数形参为PyCFunction
, 传给函数的第一个参数是模块对象(模块函数)或对象实例(对象方法), 下同. 第二个是tuple
类型的实参元组. 通常对应的 py 函数形参如:func(*args)
.METH_KEYWORDS
, 要求函数形参为PyCFunctionWithKeywords
, 第二个参数同上, 第三个是dict
类型的实参字典. 通常对应的 py 函数形参如:func(*args, **kwargv)
.METH_NOARGS
, 要求函数形参为PyCFunction
或PyNoArgsFunction
. 使用前者时, 第二个参数值为 NULL.METH_O
, 要求函数形参为PyCFunction
. 该位假定函数只有一个形参, 第二个参数直接即为传入的对象. 已知参数只有一个时, 相对于使用METH_VARARGS
, 使用METH_O
可以避免对传入的tuple
解析以提高效率.METH_CLASS
, 为类方法使用.METH_STATIC
, 为类静态方法使用.METH_COEXIST
.
详见文档.
- 创建 Python 对象
PyObject* myaddmethodobj = PyCFunction_New(&myaddmethoddef, NULL);
到此创建结束.
但是关于函数至少还有以下内容, 笔者还未解决: 闭包, 异常的抛出.
目前 PyCFunction_New
没有官方文档, 这里 有对该函数使用姿势的讨论.
交互
从 C 设值到 Python
设值 指的是这样的操作:
obj1.name = obj2
从 C 设值到 Python, 核心是调用 PyObject_SetAttrString
. (两个对象都是 新引用)
PyObject* obj1;
PyObject* obj2; // 已预先将对象取到 C 了
int ok = PyObject_SetAttrString(obj1, "name", obj2);
而 设全局变量 指的是这样的操作:
name = obj2
实际上相当于
__main__.name = obj2
PyObject* main_mod = PyImport_ImportModule("__main__");
// 导入模块 __main__, 不过这个模块本身就存在(在 sys.modules), 所以不会真的导入, 而直接返回模块对象, 但是引用计数会加一
PyObject* obj2;
int ok = PyObject_SetAttrString(main_mod, "name", obj2); // main_mod 和 obj2 都是新引用传递
Py_DECREF(main_mod); // 减引用, 释放对象
从 Python 取值到 C
取值, 指的是这样的操作
obj2 = obj1.name
从 Python 取值到 C, 核心是调用 PyObject_GetAttrString
. 返回 新引用.
PyObject* obj1;
PyObject* obj2 = PyObject_GetAttrString(obj1, "name");
Py_DECREF(obj2);
从全局取值亦类同上节.
PyObject* main_mod = PyImport_ImportModule("__main__");
PyObject* obj2 = PyObject_GetAttrString(main_mod, "name");
Py_DECREF(obj2);
Py_DECREF(main_mod);