Pythonizations#
自动绑定生成大多能完成任务,但除非 C++ 库在设计时考虑到了表达性和交互性,否则使用起来会感觉生硬。因此,如果您不是一组绑定的最终用户,实施 pythonizations 是有益的。其中一些已经默认提供,例如对于 STL 容器。请考虑以下代码,使用裸露的绑定(即“C++方式”)遍历 STL 映射:
from cppyy.gbl import std
m = std.map[int, int]()
for i in range(10):
m[i] = i*2
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 1
----> 1 from cppyy.gbl import std
2 m = std.map[int, int]()
3 for i in range(10):
ModuleNotFoundError: No module named 'cppyy'
b = m.begin()
while b != m.end():
print(b.__deref__().second, end=' ')
b.__preinc__()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 b = m.begin()
2 while b != m.end():
3 print(b.__deref__().second, end=' ')
NameError: name 'm' is not defined
是的,这是完全可行的,但也非常笨拙。与此相比,(自动)pythonization如下:
for key, value in m:
print(value, end=' ')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[3], line 1
----> 1 for key, value in m:
2 print(value, end=' ')
NameError: name 'm' is not defined
这样的pythonization可以完全使用绑定的C++方法用Python编写,不需要中间语言。由于它是基于抽象特性编写的,因此也只有一个这样的pythonization适用于所有STL映射实例。
cppyy
Python 回调#
由于绑定的 C++ 实体是功能齐全的 Python 实体,pythonization 可以在面向最终用户的 Python 模块中显式完成。然而,这将阻止 pythonization 的延迟安装,因此提供了一个回调机制。
回调是一个函数或可调用对象,接受两个参数:要进行 pythonization 的 Python 代理类及其 C++ 名称。后者提供了简单的过滤功能。然后通过 cppyy.py.add_pythonization
安装此回调,理想情况下只针对相关命名空间(支持为全局命名空间中的类安装回调,但请注意名称冲突)。
对于结构良好并具有惯用行为的 C++ 库,pythonization 最为有效。使用 Python 反射编写规则就变得简单明了。例如,考虑这个回调,它寻找传统的 C++ 函数 GetLength 并以 Python 的 __len__
替换:
import cppyy
def replace_getlength(klass, name):
try:
klass.__len__ = klass.__dict__['GetLength']
del klass.GetLength
except KeyError:
pass
cppyy.py.add_pythonization(replace_getlength, 'MyNamespace')
cppyy.cppdef("""
namespace MyNamespace {
class MyClass {
public:
MyClass(int i) : fInt(i) {}
int GetLength() { return fInt; }
private:
int fInt;
};
}""")
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[4], line 1
----> 1 import cppyy
3 def replace_getlength(klass, name):
4 try:
ModuleNotFoundError: No module named 'cppyy'
m = cppyy.gbl.MyNamespace.MyClass(42)
len(m)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[5], line 1
----> 1 m = cppyy.gbl.MyNamespace.MyClass(42)
2 len(m)
NameError: name 'cppyy' is not defined
m.GetLength()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[6], line 1
----> 1 m.GetLength()
NameError: name 'm' is not defined
如果 MyClass.GetLength
和 MyClass.__len__
都应该有效,则可以使用 del
省略删除 GetLength
方法。
cppyy C++ 回调#
如果您熟悉 Python C-API,那么有时为您的 C++ 类添加独特的优化可能会对 pythonization 层有所帮助。cppyy 会寻找两个常规函数(无需注册回调):
static void __cppyy_explicit_pythonize__(PyObject* klass, const std::string&);
仅在声明它的类中调用。以及:
static void __cppyy_pythonize__(PyObject* klass, const std::string&);
这也适用于所有的派生类。
就像 Python 回调一样,第一个参数将是 Python 类代理,第二个参数是 C++ 名称,以便于过滤。当被调用时,cppyy
将完全完成对类代理的处理,因此任何和所有更改都是允许的,包括替换迭代或缓冲协议等底层更改。
使用 C++ 回调将 MyClass.GetLength
方法替换为 Python 的 __len__
的pythonization示例:
import cppyy
cppyy.cppdef("""
#include <Python.h>
namespace MyNamespace {
class MyClassCPP {
public:
MyClassCPP(int i) : fInt(i) {}
int GetLength() { return fInt; }
private:
int fInt;
// pythonizations
public:
static void __cppyy_pythonize__(PyObject* klass, const std::string&){
auto cppName = "GetLength";
auto pythonizationName = "__len__";
auto* methodObject = PyObject_GetAttrString(klass, cppName);
PyObject_SetAttrString(klass, pythonizationName, methodObject);
Py_DECREF(methodObject);
PyObject_DelAttrString(klass, cppName);
}
};
}""")
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[7], line 1
----> 1 import cppyy
3 cppyy.cppdef("""
4 #include <Python.h>
5
(...)
25 };
26 }""")
ModuleNotFoundError: No module named 'cppyy'
m = cppyy.gbl.MyNamespace.MyClassCPP(42)
len(m)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[8], line 1
----> 1 m = cppyy.gbl.MyNamespace.MyClassCPP(42)
2 len(m)
NameError: name 'cppyy' is not defined
m.GetLength()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[9], line 1
----> 1 m.GetLength()
NameError: name 'm' is not defined