楔子 通过魔法方法可以对运算符进行重载,魔法方法的特点就是它的名称以双下划线开头、并以双下划线结尾。我们之前讨论了 __cinit__, __init__, __dealloc__,并了解了它们分别用于 C 一级的初始化、Python 一级的初始化、对象的释放(特指 C 中的指针)。 除了那三个,Cython 也支持其它的魔法方法,但是注意:Cython 的析构不是 __del__,它用于前面介绍的描述符。至于析构函数则由 __dealloc__ 负责实现,所以 __dealloc__ 不仅用于 C 指针指向内存的释放,还负责 Python 对象的析构。 算术魔法方法 假设在 Python 中定义了一个类 class A,如果希望 A 的实例对象可以进行加法运算,那么内部需要定义 __add__ 或 __radd__。关于 __add__ 和 __radd__ 的区别就在于该实例对象是在加号的左边还是右边。我们以 A() + B() 为例,A 和 B 是我们自定义的类:
但如果是内置对象(比如整数)和我们自定义的类的实例对象相加呢?
代码演示一下:
除了类似于 __add__ 这种实例对象放在左边、__radd__ 这种实例对象放在右边,还有 __iadd__,它用于 += 这种形式。
如果没定义__iadd__,也可以使用 += 这种形式,会退化成 a = a + 123,所以会调用__add__方法。 当然这都比较简单,其它的算数魔法方法也是类似的。并且里面的 self 就是对应类的实例对象,有人会觉得这不是废话吗?之所以要提这一点,是为了给下面的 Cython 做铺垫。 对于 Cython 的扩展类来说,不使用类似于 __radd__ 这种实现方式,我们只需要定义一个 __add__ 即可同时实现 __add__ 和 __radd__。 对于 Cython 的扩展类型 A,a 是 A 的实例对象,如果是 a + 123,那么会调用 __add__ 方法,然后第一个参数是 a、第二个参数是123;但如果是 123 + a,那么依旧会调用 __add__,不过此时 __add__ 的第一个参数是 123、第二个参数才是 a。 所以不像 Python 的魔法方法,第一个参数 self 永远是实例本身,第一个参数是谁取决于谁在前面。所以将第一个参数叫做 self 容易产生误解,官方也不建议将第一个参数使用 self 作为参数名。
编译测试一下:
我们看到,__add__ 中的参数确实是由位置决定的,那么再来看一个例子。
编译测试一下:
除了 __add__,Cython 也支持 __iadd__,此时的第一个参数是 self,因为 += 这种形式,第一个参数永远是实例对象。 另外这里说的 __add__ 和 __iadd__ 只是举例,其它的算术操作也是可以的。 富比较 Cython 的扩展类也可以使用 __eq, __ne__ 等等和 Python 一致的富比较魔法方法。
其它的操作符也类似,可以自己试一下。 小结 Python 里面的魔法方法有很多,像迭代器协议、上下文管理、反射等等,Cython 都支持,并且用法一致,这里就不多说了。
到目前为止,关于扩展类的内容就说完了。总之扩展类和内置类是等价的,都是直接指向了 C 一级的数据结构,不需要字节码的翻译过程。也正因为如此,它失去一些动态特性,但同时也获得了效率,因为这两者本来就是不可兼得的。 Cython 的类有点复杂,还是需要多使用,不过它毕竟在各方面都和 Python 保持接近,因此学习来也不是那么费劲。虽然创建扩展类最简单的方式是通过 Cython,但是通过 Python/C API 直接在 C 中实现的话,则是最有用的练习。 但还是那句话,这需要我们对 Python/C API 有一个很深的了解,而这是一件非常难得的事情,因此使用 Cython 就变成了我们最佳的选择。 E N D |
|