Cppyy 教程#
首先,导入模块cppyy
。所有功能,包括使用绑定的类,总是从这个顶层开始。
import cppyy
/media/pc/data/lxw/envs/anaconda3x/envs/xxx/bin/python3.12: not an ELF file.
cppyy
有三个层次:
最顶层是模块
gbl
(即全局命名空间);一系列辅助函数;
以及一组子模块(如
py
),它们服务于特定目的。
让我们从使用辅助函数 cppdef
定义 C++ 小助手类开始,使示例更有趣:
cppyy.cppdef("""
class Integer1 {
public:
Integer1(int i) : m_data(i) {}
int m_data;
};""")
True
现在我们有了 Integer1
类。请注意,这个类存在于 C++ 端,必须遵循 C++ 规则。例如,在 Python 中我们可以简单地重新定义一个类,但在 C++ 中我们不能这样做。因此,我们将随着示例的进行为 Integer
类编号,以便能够根据需要扩展示例。
Python 类是动态构建的。它们的定义位置和方式无关紧要,无论是在 Python 脚本中、编译成 C 扩展模块中,还是其他方式。cppyy
利用这一事实来即时生成绑定。这为具有数千个 C++ 类的大库带来了性能优势;由于除了模块 cppyy
本身之外,没有代码依赖于任何特定版本的 Python,因此具有通用分发优势;并且通过 Cling 后端,它实现了对 C++ 的交互式访问。
要访问我们的第一个类,请在 gbl
(全局命名空间)中找到它:
print(cppyy.gbl.Integer1)
<class cppyy.gbl.Integer1 at 0x55d6b2bfa810>
命名空间与模块有相似之处,所以我们也可以导入该类。
绑定的 C++ 类是 Python 对象。我们可以实例化它们,使用正常的 Python 内省工具,调用 help()
,在失败时引发 Python 异常,通过 Python 的引用计数和垃圾回收进行内存管理等等。此外,我们可以将它们与其他 C++ 类结合使用。
# for convenience, bring Integer1 into __main__
from cppyy.gbl import Integer1
# create a C++ Integer1 object
i = Integer1(42)
# use Python inspection
print("Variable has an 'm_data' data member?", hasattr(i, 'm_data') and 'Yes!' or 'No!')
print("Variable is an instance of int?", isinstance(i, int) and 'Yes!' or 'No!')
print("Variable is an instance of Integer1?", isinstance(i, Integer1) and 'Yes!' or 'No!')
Variable has an 'm_data' data member? Yes!
Variable is an instance of int? No!
Variable is an instance of Integer1? Yes!
# pull in the STL vector class
from cppyy.gbl.std import vector
# create a vector of Integer1 objects; note how [] instantiates the template and () instantiates the class
v = vector[Integer1]()
# populate it
v += [Integer1(j) for j in range(10)]
# display our vector
print(v)
<cppyy.gbl.std.vector<Integer1> object at 0x55d6b2defd30>
嗯,这看起来不太好。然而,由于 Integer1
现在是一个 Python 类,我们可以使用自定义的 __repr__
函数来装饰它(我们将忽略 vector
,而是将其转换为 Python 的 list
以供打印)。
# add a custom conversion for printing
Integer1.__repr__ = lambda self: repr(self.m_data)
# now try again (note the conversion of the vector to a Python list)
print(list(v))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cppyy Python 化处理#
正如我们目前所看到的,自动绑定简单易用。然而,尽管它们是 Python 对象,它们仍然保留了一些 C++ 的粗糙边缘。在后台进行了一些 pythonization 处理:例如,vector
与 +=
运算符和列表转换配合得很好。但是,为了将您自己的类呈现给最终用户,特定的 pythonizations 是可取的。为了使这能够与懒加载正确工作,存在一个基于回调的 API。
现在,对于 Integer1
来说已经太晚了,所以让我们创建 Integer2
,它存在于命名空间中,并且还具有转换功能。
# create an Integer2 class, living in namespace Math
cppyy.cppdef("""
namespace Math {
class Integer2 : public Integer1 {
public:
using Integer1::Integer1;
operator int() { return m_data; }
};
}""")
True
# prepare a pythonizor
def pythonizor(klass, name):
# A pythonizor receives the freshly prepared bound C++ class, and a name stripped down to
# the namespace the pythonizor is applied. Also accessible are klass.__name__ (for the
# Python name) and klass.__cpp_name__ (for the C++ name)
if name == 'Integer2':
klass.__repr__ = lambda self: repr(self.m_data)
# install the pythonizor as a callback on namespace 'Math' (default is the global namespace)
cppyy.py.add_pythonization(pythonizor, 'Math')
# when we next get the Integer2 class, it will have been decorated
Integer2 = cppyy.gbl.Math.Integer2 # first time a new namespace is used, it can not be imported from
v2 = vector[Integer2]()
v2 += [Integer2(j) for j in range(10)]
# now test the effect of the pythonizor:
print(list(v2))
# in addition, Integer2 has a conversion function, which is automatically recognized and pythonized
i2 = Integer2(13)
print("Converted Integer2 variable:", int(i2))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Converted Integer2 variable: 13
# continue the decoration on the C++ side, by adding an operator+ overload
cppyy.cppdef("""
namespace Math {
Integer2 operator+(const Integer2& left, const Integer1& right) {
return left.m_data + right.m_data;
}
}""")
True
# now use that fresh decoration (it will be located and bound on use):
k = i2 + i
print(k, i2.m_data + i.m_data)
55 55
cppyy 类层次结构#
Python 和 C++ 都支持多种编程范式,使得映射语言特性(例如类继承、自由函数等)相对简单;许多其他特性可以干净地隐藏,仅仅因为语法非常相似或自然(例如重载、抽象类、静态数据成员等);还有一些特性优雅地映射,因为它们的语义意图在语法中清晰地表达(例如智能指针、STL 等)。
下面展示了一些自然映射的 C++ 特性,并在 Python 中对它们进行练习。
# create some animals to play with
cppyy.cppdef("""
namespace Zoo {
enum EAnimal { eLion, eMouse };
class Animal {
public:
virtual ~Animal() {}
virtual std::string make_sound() = 0;
};
class Lion : public Animal {
public:
virtual std::string make_sound() { return s_lion_sound; }
static std::string s_lion_sound;
};
std::string Lion::s_lion_sound = "growl!";
class Mouse : public Animal {
public:
virtual std::string make_sound() { return "peep!"; }
};
Animal* release_animal(EAnimal animal) {
if (animal == eLion) return new Lion{};
if (animal == eMouse) return new Mouse{};
return nullptr;
}
std::string identify_animal(Lion*) {
return "the animal is a lion";
}
std::string identify_animal(Mouse*) {
return "the animal is a mouse";
}
}
""")
# pull in the Zoo (after which we can import from it)
Zoo = cppyy.gbl.Zoo
# pythonize the animal release function to take ownership on return
Zoo.release_animal.__creates__ = True
# abstract base classes can not be instantiated:
try:
animal = Zoo.Animal()
except TypeError as e:
print('Failed:', e, '\n')
# derived classes can be inspected in the same class hierarchy on the Python side
print('A Lion is an Animal?', issubclass(Zoo.Lion, Zoo.Animal) and 'Yes!' or 'No!', '\n')
# returned pointer types are auto-casted to the lowest known derived type:
mouse = Zoo.release_animal(Zoo.eMouse)
print('Type of mouse:', type(mouse))
lion = Zoo.release_animal(Zoo.eLion)
print('Type of lion:', type(lion), '\n')
# as pythonized, the ownership of the return value from release_animal is Python's
print("Does Python own the 'lion'?", lion.__python_owns__ and 'Yes!' or 'No!')
print("Does Python own the 'mouse'?", mouse.__python_owns__ and 'Yes!' or 'No!', '\n')
# virtual functions work as expected:
print('The mouse says:', mouse.make_sound())
print('The lion says:', lion.make_sound(), '\n')
# now change what the lion says through its static (class) variable
Zoo.Lion.s_lion_sound = "mooh!"
print('The lion says:', lion.make_sound(), '\n')
# overloads are combined into a single function on the Python side and resolved dynamically
print("Identification of \'mouse\':", Zoo.identify_animal(mouse))
print("Identification of \'lion\':", Zoo.identify_animal(lion))
Failed: none of the 2 overloaded methods succeeded. Full details:
cannot instantiate abstract class 'Zoo::Animal' (from derived classes, use super() instead)
cannot instantiate abstract class 'Zoo::Animal' (from derived classes, use super() instead)
A Lion is an Animal? Yes!
Type of mouse: <class cppyy.gbl.Zoo.Mouse at 0x55d6b332a180>
Type of lion: <class cppyy.gbl.Zoo.Lion at 0x55d6b32c80b0>
Does Python own the 'lion'? Yes!
Does Python own the 'mouse'? Yes!
The mouse says: peep!
The lion says: growl!
The lion says: mooh!
Identification of 'mouse': the animal is a mouse
Identification of 'lion': the animal is a lion
cppyy 现代 C++#
随着 C++ 的成熟,越来越多的语义意图(例如对象所有权)在语法中表达。这不是为了绑定生成器的利益,而是为了那些不得不阅读代码的可怜程序员。尽管如此,绑定生成器从这种增加的表达中受益匪浅。
cppyy.cppdef("""
namespace Zoo {
std::shared_ptr<Lion> free_lion{new Lion{}};
std::string identify_animal_smart(std::shared_ptr<Lion>& smart) {
return "the animal is a lion";
}
}
""")
True
# shared pointers are presented transparently as the wrapped type
print("Type of the 'free_lion' global:", type(Zoo.free_lion).__name__)
# if need be, the smart pointer is accessible with a helper
smart_lion = Zoo.free_lion.__smartptr__()
print("Type of the 'free_lion' smart ptr:", type(smart_lion).__name__)
# pass through functions that expect a naked pointer or smart pointer
print("Dumb passing: ", Zoo.identify_animal(Zoo.free_lion))
print("Smart passing:", Zoo.identify_animal_smart(Zoo.free_lion))
Type of the 'free_lion' global: Lion
Type of the 'free_lion' smart ptr: shared_ptr<Zoo::Lion>
Dumb passing: the animal is a lion
Smart passing: the animal is a lion