Pythonizations

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.GetLengthMyClass.__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