8. 类

本章将在第五章的基础上,进一步讲解类的绑定方法。

8.1 在Python中重载虚函数

假设有一个含有虚函数的C++类或接口,我们想在Python中重载虚函数。

  1. class Animal {
  2. public:
  3. virtual ~Animal() { }
  4. virtual std::string go(int n_times) = 0;
  5. };
  6. class Dog : public Animal {
  7. public:
  8. std::string go(int n_times) override {
  9. std::string result;
  10. for (int i=0; i<n_times; ++i)
  11. result += "woof! ";
  12. return result;
  13. }
  14. };

现在有一个普通函数,它调用任意Animal实例的go()函数。

  1. std::string call_go(Animal *animal) {
  2. return animal->go(3);
  3. }

pybind11绑定代码如下:

  1. PYBIND11_MODULE(example, m) {
  2. py::class_<Animal>(m, "Animal")
  3. .def("go", &Animal::go);
  4. py::class_<Dog, Animal>(m, "Dog")
  5. .def(py::init<>());
  6. m.def("call_go", &call_go);
  7. }

但是,这样绑定不可扩展,当我们尝试继承Animal类时会提示”No constructor defined!”,因为Animal无法构造。这时,我们需要类似于”跳板(trampoline)”的工具来重定向虚函数调用到Python中。

我们可以在Python中定义一个新的Animal类作为辅助跳板:

  1. class PyAnimal : public Animal {
  2. public:
  3. /* Inherit the constructors */
  4. using Animal::Animal;
  5. /* Trampoline (need one for each virtual function) */
  6. std::string go(int n_times) override {
  7. PYBIND11_OVERRIDE_PURE(
  8. std::string, /* Return type */
  9. Animal, /* Parent class */
  10. go, /* Name of function in C++ (must match Python name) */
  11. n_times /* Argument(s) */
  12. );
  13. }
  14. };

定义纯虚函数时需要使用PYBIND11_OVERRIDE_PURE宏,而有默认实现的虚函数则使用PYBIND11_OVERRIDEPYBIND11_OVERRIDE_PURE_NAMEPYBIND11_OVERRIDE_NAME 宏的功能类似,主要用于C函数名和Python函数名不一致的时候。以__str__为例:

  1. std::string toString() override {
  2. PYBIND11_OVERRIDE_NAME(
  3. std::string, // Return type (ret_type)
  4. Animal, // Parent class (cname)
  5. "__str__", // Name of method in Python (name)
  6. toString, // Name of function in C++ (fn)
  7. );
  8. }

Animal类的绑定代码也需要一些微调:

  1. PYBIND11_MODULE(example, m) {
  2. py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal")
  3. .def(py::init<>())
  4. .def("go", &Animal::go);
  5. py::class_<Dog, Animal>(m, "Dog")
  6. .def(py::init<>());
  7. m.def("call_go", &call_go);
  8. }

pybind11通过向class_指定额外的模板参数PyAnimal,让我们可以在Python中继承Animal类。

接下来,我们可以像往常一样定义构造函数。绑定时我们需要使用真实类,而不是辅助类。

  1. py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal");
  2. .def(py::init<>())
  3. .def("go", &PyAnimal::go); /* <--- THIS IS WRONG, use &Animal::go */

但是,上面的改动可以让我们在Python中继承Animal类,而不能继承Dog类。后续章节将会在此基础上进一步改进。

下面的Python代码展示了我们继承并重载了Animal::go方法,并通过虚函数来调用它:

  1. from example import *
  2. d = Dog()
  3. call_go(d) # u'woof! woof! woof! '
  4. class Cat(Animal):
  5. def go(self, n_times):
  6. return "meow! " * n_times
  7. c = Cat()
  8. call_go(c) # u'meow! meow! meow! '

如果你在派生的Python类中自定义了一个构造函数,你必须保证显示调用C++构造函数(通过__init__),不管它是否为默认构造函数。否则,实例属于C++那部分的内存就未初始化,可能导致未定义行为。在pybind11 2.6版本中,这种错误将会抛出TypeError异常。

  1. class Dachshund(Dog):
  2. def __init__(self, name):
  3. Dog.__init__(self) # Without this, a TypeError is raised.
  4. self.name = name
  5. def bark(self):
  6. return "yap!"

注意必须显式地调用__init__,而不应该使用supper()。在一些简单的线性继承中,supper()或许可以正常工作;一旦你混合Python和C++类使用多重继承,由于Python MRO和C++的机制,一切都将崩溃。

Note:

当重载函数返回一个pybind11从Python中转换过来的类型的引用或指针时,有些限制条件需要注意下:

  • because in these cases there is no C++ variable to reference (the value is stored in the referenced Python variable), pybind11 provides one in the PYBIND11_OVERRIDE macros (when needed) with static storage duration. Note that this means that invoking the overridden method on any instance will change the referenced value stored in all instances of that type.
  • Attempts to modify a non-const reference will not have the desired effect: it will change only the static cache variable, but this change will not propagate to underlying Python instance, and the change will be replaced the next time the override is invoked.

8.2 虚函数与继承

综合考虑虚函数与继承时,你需要为每个你允许在Python派生类中重载的方法提供重载方式。下面我们扩展Animal和Dog来举例:

  1. class Animal {
  2. public:
  3. virtual std::string go(int n_times) = 0;
  4. virtual std::string name() { return "unknown"; }
  5. };
  6. class Dog : public Animal {
  7. public:
  8. std::string go(int n_times) override {
  9. std::string result;
  10. for (int i=0; i<n_times; ++i)
  11. result += bark() + " ";
  12. return result;
  13. }
  14. virtual std::string bark() { return "woof!"; }
  15. };

上节涉及到的Animal辅助类仍是必须的,为了让Python代码能够继承Dog类,我们也需要为Dog类增加一个跳板类,来实现bark()和继承自Animal的go()name()等重载方法(即便Dog类并不直接重载name方法)。

  1. class PyAnimal : public Animal {
  2. public:
  3. using Animal::Animal; // Inherit constructors
  4. std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Animal, go, n_times); }
  5. std::string name() override { PYBIND11_OVERRIDE(std::string, Animal, name, ); }
  6. };
  7. class PyDog : public Dog {
  8. public:
  9. using Dog::Dog; // Inherit constructors
  10. std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, Dog, go, n_times); }
  11. std::string name() override { PYBIND11_OVERRIDE(std::string, Dog, name, ); }
  12. std::string bark() override { PYBIND11_OVERRIDE(std::string, Dog, bark, ); }
  13. };

注意到name()bark()尾部的逗号,这用来说明辅助类的函数不带任何参数。当函数至少有一个参数时,应该省略尾部的逗号。

注册一个继承已经在pybind11中注册的带虚函数的类,同样需要为其添加辅助类,即便它没有定义或重载任何虚函数:

  1. class Husky : public Dog {};
  2. class PyHusky : public Husky {
  3. public:
  4. using Husky::Husky; // Inherit constructors
  5. std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Husky, go, n_times); }
  6. std::string name() override { PYBIND11_OVERRIDE(std::string, Husky, name, ); }
  7. std::string bark() override { PYBIND11_OVERRIDE(std::string, Husky, bark, ); }
  8. };

我们可以使用模板辅助类将简化这类重复的绑定工作,这对有多个虚函数的基类尤其有用:

  1. template <class AnimalBase = Animal> class PyAnimal : public AnimalBase {
  2. public:
  3. using AnimalBase::AnimalBase; // Inherit constructors
  4. std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); }
  5. std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); }
  6. };
  7. template <class DogBase = Dog> class PyDog : public PyAnimal<DogBase> {
  8. public:
  9. using PyAnimal<DogBase>::PyAnimal; // Inherit constructors
  10. // Override PyAnimal's pure virtual go() with a non-pure one:
  11. std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, DogBase, go, n_times); }
  12. std::string bark() override { PYBIND11_OVERRIDE(std::string, DogBase, bark, ); }
  13. };

这样,我们只需要一个辅助方法来定义虚函数和纯虚函数的重载了。只是这样编译器就需要生成许多额外的方法和类。

下面我们在pybind11中注册这些类:

  1. py::class_<Animal, PyAnimal<>> animal(m, "Animal");
  2. py::class_<Dog, Animal, PyDog<>> dog(m, "Dog");
  3. py::class_<Husky, Dog, PyDog<Husky>> husky(m, "Husky");
  4. // ... add animal, dog, husky definitions

注意,Husky不需要一个专门的辅助类,因为它没定义任何新的虚函数和纯虚函数的重载。

Python中的使用示例:

  1. class ShihTzu(Dog):
  2. def bark(self):
  3. return "yip!"

8.3 扩展跳板类的功能

8.3.1 跳板类的初始化

默认情况下,跳板类需要的时候才初始化,即当一个Python类继承了绑定的C++类时(而不是创建绑定类的实例时),或者注册的构造函数仅对跳板类而非注册类有效时。这主要是处于性能的考量:如果只有虚函数需要跳板类时,不初始化跳板类可以避免运行时检查Python继承类是否有重载函数,以提高性能。

有时,将跳板类作为一个不仅仅用于处理虚函数分发的中间类来初始化还是有用的。例如,这个类可以执行额外的初始化操作,额外的析构操作,定义属性或方法来给类提供类似Python风格的接口。

要让pybind11在创建类实例时,总是初始化跳板类,类的构造函数需要使用py::init_alias<Args, ...>()来代替py::init<Args, ...>()。这样可以强制通过跳板类来构造,确保类成员的初始化和析构。

See also:See the file tests/test_virtual_functions.cpp for complete examples showing both normal and forced trampoline instantiation.

差异化函数签名

第一节中介绍的宏可以覆盖绝大多数公开C++类的场景。有时,我们难以创建参数和返回类型间的一一映射关系。如C++的参数即是输入又是输出的情况(入参为引用,在函数中修改该参数)。

我们可以通过跳板类来解决这种Python方法输入和输出的问题,也可以参考 Limitations involving reference arguments中的处理方法。

get_override()函数允许Python从跳板类方法中检索方法的实现。Consider for example a C++ method which has the signature bool myMethod(int32_t& value), where the return indicates whether something should be done with the value. This can be made convenient on the Python side by allowing the Python function to return None or an int:

  1. bool MyClass::myMethod(int32_t& value)
  2. {
  3. pybind11::gil_scoped_acquire gil; // Acquire the GIL while in this scope.
  4. // Try to look up the overridden method on the Python side.
  5. pybind11::function override = pybind11::get_override(this, "myMethod");
  6. if (override) { // method is found
  7. auto obj = override(value); // Call the Python function.
  8. if (py::isinstance<py::int_>(obj)) { // check if it returned a Python integer type
  9. value = obj.cast<int32_t>(); // Cast it and assign it to the value.
  10. return true; // Return true; value should be used.
  11. } else {
  12. return false; // Python returned none, return false.
  13. }
  14. }
  15. return false; // Alternatively return MyClass::myMethod(value);
  16. }

8.4 定制构造函数

前面章节介绍了绑定构造函数的方法,但它仅仅在C++侧刚好有对应的函数时才能正常工作。为了扩展到更通用的情况,pybind11可以绑定工厂方法作为构造函数。如下所示:

  1. class Example {
  2. private:
  3. Example(int); // private constructor
  4. public:
  5. // Factory function:
  6. static Example create(int a) { return Example(a); }
  7. };
  8. py::class_<Example>(m, "Example")
  9. .def(py::init(&Example::create));

虽然可以直接绑定create方法,有时将其在Python侧将其作为构造函数公开更为合适。这可以通过调用.def(py::init(...))来完成,只需将对应的函数(返回一个新实例,如create)作为参数传入py::init()即可。同样的,用这个方法我们也可以传入一个函数,它返回新实例的原始指针或持有者(如`std::unique_ptr)。如下所示:

  1. class Example {
  2. private:
  3. Example(int); // private constructor
  4. public:
  5. // Factory function - returned by value:
  6. static Example create(int a) { return Example(a); }
  7. // These constructors are publicly callable:
  8. Example(double);
  9. Example(int, int);
  10. Example(std::string);
  11. };
  12. py::class_<Example>(m, "Example")
  13. // Bind the factory function as a constructor:
  14. .def(py::init(&Example::create))
  15. // Bind a lambda function returning a pointer wrapped in a holder:
  16. .def(py::init([](std::string arg) {
  17. return std::unique_ptr<Example>(new Example(arg));
  18. }))
  19. // Return a raw pointer:
  20. .def(py::init([](int a, int b) { return new Example(a, b); }))
  21. // You can mix the above with regular C++ constructor bindings as well:
  22. .def(py::init<double>())
  23. ;

当Python侧调用这些构造函数时,pybind11将调用工厂函数,并将返回的C++示例存储到Python实例中。

当与重载函数跳板类结合使用时,有两种方法。第一种方法是跳板类增加一个构造函数,函数接受原类的右值引用,这样我们可以从原类的工厂函数构造跳板类的实例。第二种方法是使用py::init()提供原类和跳板类两个工厂函数。

你也可以指定一个工厂函数,它总是返回跳板类的实例,这与py::init_alias<...>的行为类似。

下面的示例展示了这两种方法:

  1. #include <pybind11/factory.h>
  2. class Example {
  3. public:
  4. // ...
  5. virtual ~Example() = default;
  6. };
  7. class PyExample : public Example {
  8. public:
  9. using Example::Example;
  10. PyExample(Example &&base) : Example(std::move(base)) {}
  11. };
  12. py::class_<Example, PyExample>(m, "Example")
  13. // Returns an Example pointer. If a PyExample is needed, the Example
  14. // instance will be moved via the extra constructor in PyExample, above.
  15. .def(py::init([]() { return new Example(); }))
  16. // Two callbacks:
  17. .def(py::init([]() { return new Example(); } /* no alias needed */,
  18. []() { return new PyExample(); } /* alias needed */))
  19. // *Always* returns an alias instance (like py::init_alias<>())
  20. .def(py::init([]() { return new PyExample(); }))
  21. ;

大括号初始化

pybind11潜在地使用C++11的大括号初始化来调用目标类的构造函数,这意味着它也可以绑定隐式的构造函数:

  1. struct Aggregate {
  2. int a;
  3. std::string b;
  4. };
  5. py::class_<Aggregate>(m, "Aggregate")
  6. .def(py::init<int, const std::string &>());

Note: 大括号初始化优先匹配带列表初始化的重载构造函数。极少数情况下会出问题,你可以使用py::init(...)传入一个构造新对象的匿名函数来处理这个问题。

8.5 非公有析构函数

如果一个类拥有私有或保护的析构函数(例如单例类),通过pybind11绑定类时编译器将会报错。本质的问题是std::unique_ptr智能指针负责管理实例的生命周期需要引用析构函数,即便没有资源需要回收。Pybind11提供了辅助类py::nodelete来禁止对析构函数的调用。这种情况下,C++侧负责析构对象避免内存泄漏就十分重要。

  1. /* ... definition ... */
  2. class MyClass {
  3. private:
  4. ~MyClass() { }
  5. };
  6. /* ... binding code ... */
  7. py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass")
  8. .def(py::init<>())

8.6 在析构函数中调用Python

在析构函数中调用Python函数出错时,会抛出异常error_already_set。如果异常在析构函数外抛出,将会调用std::terminate()来终结程序。类析构函数必须捕获所有error_already_set类型的异常,并使用error_already_set::discard_as_unraisable()来抛弃Python异常。

任意Python函数都可能抛出异常。当一个Python生成器停止生成条目时,Pyhton将抛出StopIteration异常,如果生成器的堆栈持有C++对象的最后一个引用时,它将传递异常到C++析构函数。

  1. class MyClass {
  2. public:
  3. ~MyClass() {
  4. try {
  5. py::print("Even printing is dangerous in a destructor");
  6. py::exec("raise ValueError('This is an unraisable exception')");
  7. } catch (py::error_already_set &e) {
  8. // error_context should be information about where/why the occurred,
  9. // e.g. use __func__ to get the name of the current function
  10. e.discard_as_unraisable(__func__);
  11. }
  12. }
  13. };

Note: pybind11不支持将C++析构函数标识为noexcept(false)

8.7 隐式转换

假设项目中有A和B两个类型,A可以直接转换为B。

  1. py::class_<A>(m, "A")
  2. /// ... members ...
  3. py::class_<B>(m, "B")
  4. .def(py::init<A>())
  5. /// ... members ...
  6. m.def("func",
  7. [](const B &) { /* .... */ }
  8. );

如果想func函数传入A类型的参数a,Pyhton侧需要这样写func(B(a)),而C++则可以直接使用func(a),自动将A类型转换为B类型。

这种情形下(B有一个接受A类型参数的构造函数),我们可以使用如下声明来让Python侧也支持类似的隐式转换:

  1. py::implicitly_convertible<A, B>();

Note: To prevent runaway recursion, implicit conversions are non-reentrant: an implicit conversion invoked as part of another implicit conversion of the same type (i.e. from A to B) will fail.

8.8 静态属性

静态属性也可以像普通属性一样公开getter和setter方法。隐式的self参数仍然存在,并在Python中用于传递Pythontype子类实例。我们通常在C++测忽略这个参数,下面的例子演示了如何使用lambda表达式做为getter函数,并忽略self参数。

  1. py::class_<Foo>(m, "Foo")
  2. .def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); });

8.9 重载操作符

假设有这样一个类Vector2,它通过重载操作符实现了向量加法和标量乘法。

  1. class Vector2 {
  2. public:
  3. Vector2(float x, float y) : x(x), y(y) { }
  4. Vector2 operator+(const Vector2 &v) const { return Vector2(x + v.x, y + v.y); }
  5. Vector2 operator*(float value) const { return Vector2(x * value, y * value); }
  6. Vector2& operator+=(const Vector2 &v) { x += v.x; y += v.y; return *this; }
  7. Vector2& operator*=(float v) { x *= v; y *= v; return *this; }
  8. friend Vector2 operator*(float f, const Vector2 &v) {
  9. return Vector2(f * v.x, f * v.y);
  10. }
  11. std::string toString() const {
  12. return "[" + std::to_string(x) + ", " + std::to_string(y) + "]";
  13. }
  14. private:
  15. float x, y;
  16. };

操作符绑定代码如下:

  1. #include <pybind11/operators.h>
  2. PYBIND11_MODULE(example, m) {
  3. py::class_<Vector2>(m, "Vector2")
  4. .def(py::init<float, float>())
  5. .def(py::self + py::self)
  6. .def(py::self += py::self)
  7. .def(py::self *= float())
  8. .def(float() * py::self)
  9. .def(py::self * float())
  10. .def(-py::self)
  11. .def("__repr__", &Vector2::toString);
  12. }

.def(py::self * float())是如下代码的简短标记:

  1. .def("__mul__", [](const Vector2 &a, float b) {
  2. return a * b;
  3. }, py::is_operator())

8.10 支持pickle

Python的pickle模块提供了强大的将Python对象图到二进制数据流的序列化和反序列化的设施。pybind11提供了py::pickle()定义来支持pickle和unpickle C++类。现在有这样一个类:

  1. class Pickleable {
  2. public:
  3. Pickleable(const std::string &value) : m_value(value) { }
  4. const std::string &value() const { return m_value; }
  5. void setExtra(int extra) { m_extra = extra; }
  6. int extra() const { return m_extra; }
  7. private:
  8. std::string m_value;
  9. int m_extra = 0;
  10. };

Python中通过定义__setstate____getstate__使能pciking支持。对于pybind11类,可以使用py::pickle()来绑定这两个函数:

  1. py::class_<Pickleable>(m, "Pickleable")
  2. .def(py::init<std::string>())
  3. .def("value", &Pickleable::value)
  4. .def("extra", &Pickleable::extra)
  5. .def("setExtra", &Pickleable::setExtra)
  6. .def(py::pickle(
  7. [](const Pickleable &p) { // __getstate__
  8. /* Return a tuple that fully encodes the state of the object */
  9. return py::make_tuple(p.value(), p.extra());
  10. },
  11. [](py::tuple t) { // __setstate__
  12. if (t.size() != 2)
  13. throw std::runtime_error("Invalid state!");
  14. /* Create a new C++ instance */
  15. Pickleable p(t[0].cast<std::string>());
  16. /* Assign any additional state */
  17. p.setExtra(t[1].cast<int>());
  18. return p;
  19. }
  20. ));

py::pickle()中的__setstate__部分遵循与py::init()单参数版本相同的规则,返回值可以是一个值,指针或者holder type。

Python中使用示例如下:

  1. try:
  2. import cPickle as pickle # Use cPickle on Python 2.7
  3. except ImportError:
  4. import pickle
  5. p = Pickleable("test_value")
  6. p.setExtra(15)
  7. data = pickle.dumps(p, 2)

Note: Note that only the cPickle module is supported on Python 2.7.

The second argument to dumps is also crucial: it selects the pickle protocol version 2, since the older version 1 is not supported. Newer versions are also fine—for instance, specify -1 to always use the latest available version. Beware: failure to follow these instructions will cause important pybind11 memory allocation routines to be skipped during unpickling, which will likely lead to memory corruption and/or segmentation faults.

8.11 深拷贝支持

Python通常在赋值中使用引用。有时需要一个真正的拷贝,以防止修改所有的拷贝实例。copy模块提供了这样的拷贝能力。

在Python3中,带pickle支持的类自带深拷贝能力。但是,自定义__copy____deepcopy__方法能够提高拷贝的性能。在Python2.7中,由于pybind11只支持cPickle,要想实现深拷贝,自定义这两个方法必须实现。

对于一些简单的类,可以使用拷贝构造函数来实现深拷贝。如下所示:

  1. py::class_<Copyable>(m, "Copyable")
  2. .def("__copy__", [](const Copyable &self) {
  3. return Copyable(self);
  4. })
  5. .def("__deepcopy__", [](const Copyable &self, py::dict) {
  6. return Copyable(self);
  7. }, "memo"_a);

Note: 本例中不会复制动态属性。

8.12 多重继承

pybind11支持绑定多重继承的类,只需在将所有基类作为class_的模板参数即可:

  1. py::class_<MyType, BaseType1, BaseType2, BaseType3>(m, "MyType")
  2. ...

基类间的顺序任意,甚至可以穿插使用别名或者holder类型,pybind11能够自动识别它们。唯一的要求就是第一个模板参数必须是类型本身。

允许Python中定义的类继承多个C++类,也允许混合继承C++类和Python类。

有一个关于该特性实现的警告:当仅指定一个基类,实际上有多个基类时,pybind11会认为它并没有使用多重继承,这将导致未定义行为。对于这个问题,我们可以在类构造函数中添加multiple_inheritance的标识。

  1. py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance());

当模板参数列出了多个基类时,无需使用该标识。

8.13 绑定Module-local类

pybind11默认将类绑定到模块的全局作用域中。这意味着模块中定义的类型,可能获得其他模块中相同类型名的结果。示例如下:

  1. // In the module1.cpp binding code for module1:
  2. py::class_<Pet>(m, "Pet")
  3. .def(py::init<std::string>())
  4. .def_readonly("name", &Pet::name);
  5. // In the module2.cpp binding code for module2:
  6. m.def("create_pet", [](std::string name) { return new Pet(name); });
  1. >>> from module1 import Pet
  2. >>> from module2 import create_pet
  3. >>> pet1 = Pet("Kitty")
  4. >>> pet2 = create_pet("Doggy")
  5. >>> pet2.name()
  6. 'Doggy'

有时,我们希望将一个复杂的库分割到几个Python模块中。

在某些例子中,这也会引起冲突。例如,有两个不相干的模块使用了同一个C++外部库,而且他们各自提供了这个库的自定义绑定。当Python程序同时(直接或间接地)导入两个库时,由于外部类型的定义冲突而导致错误。

  1. // dogs.cpp
  2. // Binding for external library class:
  3. py::class<pets::Pet>(m, "Pet")
  4. .def("name", &pets::Pet::name);
  5. // Binding for local extension class:
  6. py::class<Dog, pets::Pet>(m, "Dog")
  7. .def(py::init<std::string>());
  1. // cats.cpp, in a completely separate project from the above dogs.cpp.
  2. // Binding for external library class:
  3. py::class<pets::Pet>(m, "Pet")
  4. .def("get_name", &pets::Pet::name);
  5. // Binding for local extending class:
  6. py::class<Cat, pets::Pet>(m, "Cat")
  7. .def(py::init<std::string>());
  1. >>> import cats
  2. >>> import dogs
  3. Traceback (most recent call last):
  4. File "<stdin>", line 1, in <module>
  5. ImportError: generic_type: type "Pet" is already registered!

为避开这点,你可以想py::class_传递py::module_local()属性,将外部类绑定到模块内部。

  1. // Pet binding in dogs.cpp:
  2. py::class<pets::Pet>(m, "Pet", py::module_local())
  3. .def("name", &pets::Pet::name);
  1. // Pet binding in cats.cpp:
  2. py::class<pets::Pet>(m, "Pet", py::module_local())
  3. .def("get_name", &pets::Pet::name);

这样,Python侧的dogs.Petcats.Pet就是两个不同的类,两个模块也能顺利地同时导入,互不干扰。有两点需要注意的是:1)外部模块不能返回或转换Pet示例到Python(除非他们也提供自己内部的绑定);2)在Python的视角来看,他们就是两个截然不同的类。

注意,这个局部性仅作用于C++到Python方向。传递一个py::module_local类型到C++函数,在module-local类看来仍是合理的。这意味着,下面的函数添加到任意哪个模块(不限于cats和dogs两个模块),它将可以通过dogs.Petcats.Pet参数来调用。

  1. m.def("pet_name", [](const pets::Pet &pet) { return pet.name(); });

举个例子,假设上述函数被添加到cats.cppdogs.cppfrogs.cppfrogs.cpp没有绑定Pets类)。

  1. >>> import cats, dogs, frogs # No error because of the added py::module_local()
  2. >>> mycat, mydog = cats.Cat("Fluffy"), dogs.Dog("Rover")
  3. >>> (cats.pet_name(mycat), dogs.pet_name(mydog))
  4. ('Fluffy', 'Rover')
  5. >>> (cats.pet_name(mydog), dogs.pet_name(mycat), frogs.pet_name(mycat))
  6. ('Rover', 'Fluffy', 'Fluffy')

即便其他模块已经全局地注册了相同的类型,我们还是可以使用py::module_local()来注册到另一个模块:在module-local定义的模块,所有C++势力将被转为关联的Python类型。在其他模块,这个实例则被转为全局地Python类型。

Note: STL bindings (as provided via the optional pybind11/stl_bind.h header) apply py::module_local by default when the bound type might conflict with other modules; see Binding STL containers for details.

The localization of the bound types is actually tied to the shared object or binary generated by the compiler/linker. For typical modules created with PYBIND11_MODULE(), this distinction is not significant. It is possible, however, when Embedding the interpreter to embed multiple modules in the same binary (see Adding embedded modules). In such a case, the localization will apply across all embedded modules within the same binary.

8. 14 绑定protected成员函数

通常不可能向Python公开protected 成员函数:

  1. class A {
  2. protected:
  3. int foo() const { return 42; }
  4. };
  5. py::class_<A>(m, "A")
  6. .def("foo", &A::foo); // error: 'foo' is a protected member of 'A'

因为非公有成员函数意味着外部不可调用。但我们还是希望在Python派生类中使用protected 函数。我们可以通过下面的方式来实现:

  1. class A {
  2. protected:
  3. int foo() const { return 42; }
  4. };
  5. class Publicist : public A { // helper type for exposing protected functions
  6. public:
  7. using A::foo; // inherited with different access modifier
  8. };
  9. py::class_<A>(m, "A") // bind the primary class
  10. .def("foo", &Publicist::foo); // expose protected methods via the publicist

因为 &Publicist::foo&A::foo 准确地说是同一个函数(相同的签名和地址),仅仅是获取方式不同。 Publicist 的唯一意图,就是将函数的作用域变为public

如果是希望公开在Python侧重载的 protected虚函数,可以将publicist pattern与之前提到的trampoline相结合:

  1. class A {
  2. public:
  3. virtual ~A() = default;
  4. protected:
  5. virtual int foo() const { return 42; }
  6. };
  7. class Trampoline : public A {
  8. public:
  9. int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); }
  10. };
  11. class Publicist : public A {
  12. public:
  13. using A::foo;
  14. };
  15. py::class_<A, Trampoline>(m, "A") // <-- `Trampoline` here
  16. .def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`!

8.15 绑定final类

在C++11中,我们可以使用findal关键字来确保一个类不被继承。py::is_final属性则可以用来确保一个类在Python中不被继承。底层的C++类型不需要定义为final。

  1. class IsFinal final {};
  2. py::class_<IsFinal>(m, "IsFinal", py::is_final());

在Python中试图继承这个类,将导致错误:

  1. class PyFinalChild(IsFinal):
  2. pass
  3. TypeError: type 'IsFinal' is not an acceptable base type

8.16 定制自动向下转型

如前面“继承与自动转型”一节中解释的,pybind11内置了对C++多态的动态类型的处理。Sometimes, you might want to provide this automatic downcasting behavior when creating bindings for a class hierarchy that does not use standard C++ polymorphism, such as LLVM 3. As long as there’s some way to determine at runtime whether a downcast is safe, you can proceed by specializing the pybind11::polymorphic_type_hook template:

  1. enum class PetKind { Cat, Dog, Zebra };
  2. struct Pet { // Not polymorphic: has no virtual methods
  3. const PetKind kind;
  4. int age = 0;
  5. protected:
  6. Pet(PetKind _kind) : kind(_kind) {}
  7. };
  8. struct Dog : Pet {
  9. Dog() : Pet(PetKind::Dog) {}
  10. std::string sound = "woof!";
  11. std::string bark() const { return sound; }
  12. };
  13. namespace pybind11 {
  14. template<> struct polymorphic_type_hook<Pet> {
  15. static const void *get(const Pet *src, const std::type_info*& type) {
  16. // note that src may be nullptr
  17. if (src && src->kind == PetKind::Dog) {
  18. type = &typeid(Dog);
  19. return static_cast<const Dog*>(src);
  20. }
  21. return src;
  22. }
  23. };
  24. } // namespace pybind11

When pybind11 wants to convert a C++ pointer of type Base* to a Python object, it calls polymorphic_type_hook<Base>::get() to determine if a downcast is possible. The get() function should use whatever runtime information is available to determine if its src parameter is in fact an instance of some class Derived that inherits from Base. If it finds such a Derived, it sets type = &typeid(Derived) and returns a pointer to the Derived object that contains src. Otherwise, it just returns src, leaving type at its default value of nullptr. If you set type to a type that pybind11 doesn’t know about, no downcasting will occur, and the original src pointer will be used with its static type Base*.

It is critical that the returned pointer and type argument of get() agree with each other: if type is set to something non-null, the returned pointer must point to the start of an object whose type is type. If the hierarchy being exposed uses only single inheritance, a simple return src; will achieve this just fine, but in the general case, you must cast src to the appropriate derived-class pointer (e.g. using static_cast<Derived>(src)) before allowing it to be returned as a void*.

8.17 访问类型对象

我们可以从已注册的C++类,获取到类型对象:

  1. py::type T_py = py::type::of<T>();

也可以直接使用py::type::of(ob)来获取任意Python对象的类型,跟Python中的type(ob)一样。