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