Cppyy 教程#
首先,导入模块cppyy
。所有功能,包括使用绑定的类,总是从这个顶层开始。
import cppyy
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 1
----> 1 import cppyy
ModuleNotFoundError: No module named 'cppyy'
cppyy
有三个层次:
最顶层是模块
gbl
(即全局命名空间);一系列辅助函数;
以及一组子模块(如
py
),它们服务于特定目的。
让我们从使用辅助函数 cppdef
定义 C++ 小助手类开始,使示例更有趣:
cppyy.cppdef("""
class Integer1 {
public:
Integer1(int i) : m_data(i) {}
int m_data;
};""")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 cppyy.cppdef("""
2 class Integer1 {
3 public:
4 Integer1(int i) : m_data(i) {}
5 int m_data;
6 };""")
NameError: name 'cppyy' is not defined
现在我们有了 Integer1
类。请注意,这个类存在于 C++ 端,必须遵循 C++ 规则。例如,在 Python 中我们可以简单地重新定义一个类,但在 C++ 中我们不能这样做。因此,我们将随着示例的进行为 Integer
类编号,以便能够根据需要扩展示例。
Python 类是动态构建的。它们的定义位置和方式无关紧要,无论是在 Python 脚本中、编译成 C 扩展模块中,还是其他方式。cppyy
利用这一事实来即时生成绑定。这为具有数千个 C++ 类的大库带来了性能优势;由于除了模块 cppyy
本身之外,没有代码依赖于任何特定版本的 Python,因此具有通用分发优势;并且通过 Cling 后端,它实现了对 C++ 的交互式访问。
要访问我们的第一个类,请在 gbl
(全局命名空间)中找到它:
print(cppyy.gbl.Integer1)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[3], line 1
----> 1 print(cppyy.gbl.Integer1)
NameError: name 'cppyy' is not defined
命名空间与模块有相似之处,所以我们也可以导入该类。
绑定的 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!')
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[4], line 2
1 # for convenience, bring Integer1 into __main__
----> 2 from cppyy.gbl import Integer1
4 # create a C++ Integer1 object
5 i = Integer1(42)
ModuleNotFoundError: No module named 'cppyy'
# 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)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[5], line 2
1 # pull in the STL vector class
----> 2 from cppyy.gbl.std import vector
4 # create a vector of Integer1 objects; note how [] instantiates the template and () instantiates the class
5 v = vector[Integer1]()
ModuleNotFoundError: No module named 'cppyy'
嗯,这看起来不太好。然而,由于 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))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[6], line 2
1 # add a custom conversion for printing
----> 2 Integer1.__repr__ = lambda self: repr(self.m_data)
4 # now try again (note the conversion of the vector to a Python list)
5 print(list(v))
NameError: name 'Integer1' is not defined
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; }
};
}""")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[7], line 2
1 # create an Integer2 class, living in namespace Math
----> 2 cppyy.cppdef("""
3 namespace Math {
4 class Integer2 : public Integer1 {
5 public:
6 using Integer1::Integer1;
7 operator int() { return m_data; }
8 };
9 }""")
NameError: name 'cppyy' is not defined
# 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')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[8], line 10
7 klass.__repr__ = lambda self: repr(self.m_data)
9 # install the pythonizor as a callback on namespace 'Math' (default is the global namespace)
---> 10 cppyy.py.add_pythonization(pythonizor, 'Math')
NameError: name 'cppyy' is not defined
# 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))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[9], line 2
1 # when we next get the Integer2 class, it will have been decorated
----> 2 Integer2 = cppyy.gbl.Math.Integer2 # first time a new namespace is used, it can not be imported from
3 v2 = vector[Integer2]()
4 v2 += [Integer2(j) for j in range(10)]
NameError: name 'cppyy' is not defined
# 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;
}
}""")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[10], line 2
1 # continue the decoration on the C++ side, by adding an operator+ overload
----> 2 cppyy.cppdef("""
3 namespace Math {
4 Integer2 operator+(const Integer2& left, const Integer1& right) {
5 return left.m_data + right.m_data;
6 }
7 }""")
NameError: name 'cppyy' is not defined
# now use that fresh decoration (it will be located and bound on use):
k = i2 + i
print(k, i2.m_data + i.m_data)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[11], line 2
1 # now use that fresh decoration (it will be located and bound on use):
----> 2 k = i2 + i
3 print(k, i2.m_data + i.m_data)
NameError: name 'i2' is not defined
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
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[12], line 2
1 # create some animals to play with
----> 2 cppyy.cppdef("""
3 namespace Zoo {
4
5 enum EAnimal { eLion, eMouse };
6
7 class Animal {
8 public:
9 virtual ~Animal() {}
10 virtual std::string make_sound() = 0;
11 };
12
13 class Lion : public Animal {
14 public:
15 virtual std::string make_sound() { return s_lion_sound; }
16 static std::string s_lion_sound;
17 };
18 std::string Lion::s_lion_sound = "growl!";
19
20 class Mouse : public Animal {
21 public:
22 virtual std::string make_sound() { return "peep!"; }
23 };
24
25 Animal* release_animal(EAnimal animal) {
26 if (animal == eLion) return new Lion{};
27 if (animal == eMouse) return new Mouse{};
28 return nullptr;
29 }
30
31 std::string identify_animal(Lion*) {
32 return "the animal is a lion";
33 }
34
35 std::string identify_animal(Mouse*) {
36 return "the animal is a mouse";
37 }
38
39 }
40 """)
42 # pull in the Zoo (after which we can import from it)
43 Zoo = cppyy.gbl.Zoo
NameError: name 'cppyy' is not defined
# 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))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[13], line 3
1 # abstract base classes can not be instantiated:
2 try:
----> 3 animal = Zoo.Animal()
4 except TypeError as e:
5 print('Failed:', e, '\n')
NameError: name 'Zoo' is not defined
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";
}
}
""")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[14], line 1
----> 1 cppyy.cppdef("""
2 namespace Zoo {
3 std::shared_ptr<Lion> free_lion{new Lion{}};
4
5 std::string identify_animal_smart(std::shared_ptr<Lion>& smart) {
6 return "the animal is a lion";
7 }
8 }
9 """)
NameError: name 'cppyy' is not defined
# 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))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[15], line 2
1 # shared pointers are presented transparently as the wrapped type
----> 2 print("Type of the 'free_lion' global:", type(Zoo.free_lion).__name__)
4 # if need be, the smart pointer is accessible with a helper
5 smart_lion = Zoo.free_lion.__smartptr__()
NameError: name 'Zoo' is not defined