Python 化

Automatic bindings generation mostly gets the job done, but unless a C++ library was designed with expressiveness and interactivity in mind, using it will feel stilted. Thus, if you are not the end-user of a set of bindings, it is beneficial to implement pythonizations . Some of these are already provided by default, e.g. for STL containers. Consider the following code, iterating over an STL map, using naked bindings (i.e. “the C++ way”):

>>> from cppyy.gbl import std
>>> m = std.map[int, int]()
>>> for i in range(10):
...     m[i] = i*2
...
>>> b = m.begin()
>>> while b != m.end():
...     print(b.__deref__().second, end=' ')
...     b.__preinc__()
...
0 2 4 6 8 10 12 14 16 18
>>>
					

Yes, that is perfectly functional, but it is also very clunky. Contrast this to the (automatic) pythonization:

>>> for key, value in m:
...    print(value, end=' ')
...
0 2 4 6 8 10 12 14 16 18
>>>
					

Such a pythonization can be written completely in Python using the bound C++ methods, with no intermediate language necessary. Since it is written on abstract features, there is also only one such pythonization that works for all STL map instantiations.

Python 回调

Since bound C++ entities are fully functional Python ones, pythonization can be done explicitly in an end-user facing Python module. However, that would prevent lazy installation of pythonizations, so instead a callback mechanism is provided.

A callback is a function or callable object taking two arguments: the Python proxy class to be pythonized and its C++ name. The latter is provided to allow easy filtering. This callback is then installed through cppyy.py.add_pythonization and ideally only for the relevant namespace (installing callbacks for classes in the global namespace is supported, but beware of name clashes).

Pythonization is most effective of well-structured C++ libraries that have idiomatic behaviors. It is then straightforward to use Python reflection to write rules. For example, consider this callback that looks for the conventional C++ function GetLength and replaces it with Python’s __len__ :

import cppyy
def replace_getlength(klass, name):
    try:
        klass.__len__ = klass.__dict__['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;
};
}""")
m = cppyy.gbl.MyNamespace.MyClass(42)
assert len(m) == 42
					

C++ 回调

If you are familiar with the Python C-API, it may sometimes be beneficial to add unique optimizations to your C++ classes to be picked up by the pythonization layer. There are two conventional function that cppyy will look for (no registration of callbacks needed):

static void __cppyy_explicit_pythonize__(PyObject* klass, const std::string&);
					

which is called only for the class that declares it. And:

static void __cppyy_pythonize__(PyObject* klass, const std::string&);
					

which is also called for all derived classes.

Just as with the Python callbacks, the first argument will be the Python class proxy, the second the C++ name, for easy filtering. When called, cppyy will be completely finished with the class proxy, so any and all changes, including such low-level ones such as the replacement of iteration or buffer protocols, are fair game.