魔法方法在 Cython 中更加魔法

简介: 魔法方法在 Cython 中更加魔法


楔子




通过魔法方法可以对运算符进行重载,魔法方法的特点就是它的名称以双下划线开头、并以双下划线结尾。我们之前讨论了 __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 是我们自定义的类:

  • 首先尝试寻找 A 的 __add__, 如果有直接调用;
  • 如果 A 中不存在 __add__, 那么会去寻找 B 的 __radd__;


但如果是内置对象(比如整数)和我们自定义的类的实例对象相加呢?

  • 123 + A(): 先寻找 A 的 __radd__;
  • A() + 123: 先寻找 A 的 __add__;


代码演示一下:

class A:
    def __add__(self, other):
        return "A add"
    def __radd__(self, other):
        return "A radd"
class B:
    def __add__(self, other):
        return "B add"
    def __radd__(self, other):
        return "B radd"
print(A() + B())  # A add
print(B() + A())  # B add
print(123 + B())  # B radd
print(A() + 123)  # A add

除了类似于 __add__ 这种实例对象放在左边、__radd__ 这种实例对象放在右边,还有 __iadd__,它用于 += 这种形式。

class A:
    def __iadd__(self, other):
        print("__iadd__ is called")
        return 1 + other
a = A()
a += 123
print(a)
"""
__iadd__ is called
124
"""

如果没定义__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 作为参数名。

但是说实话,用了 Python 这么些年,第一个参数不写成 self 感觉有点别扭。

cdef class Girl:
    def __add__(x, y):
        return x, y
    def __repr__(self):
        return "Girl 实例"

编译测试一下:

import pyximport
pyximport.install(language_level=3)
from cython_test import Girl
print(Girl() + 123)
print(123 + Girl())
"""
(Girl 实例, 123)
(123, Girl 实例)
"""

我们看到,__add__ 中的参数确实是由位置决定的,那么再来看一个例子。

cdef class Girl:
    cdef long a
    def __init__(self, a):
        self.a = a
    def __add__(x, y):
        # 这里必须要通过 <Girl> 转化一下
        # 因为 x 和 y 都是外界传来的动态变量
        # 而属性 a 不是一个 public 或者 readonly
        # 所以动态变量无法访问,真正的私有对动态变量是屏蔽的
        # 但静态变量可以自由访问,所以我们需要转成静态变量
        if isinstance(x, Girl):
            return (<Girl> x).a + y
        # 或者使用 cdef 重新静态声明一个静态变量
        # 比如 cdef Girl y1 = y,然后 y1.a + x 也可以
        return (<Girl> y).a + x

编译测试一下:

import pyximport
pyximport.install(language_level=3)
import cython_test
g = cython_test.Girl(3)
print(g + 2)  # 5
print(2 + g)  # 5
# 和浮点数运算也是可以的
print(g + 2.1)  # 5.1
print(2.1 + g)  # 5.1
g += 4
print(g)  # 7

除了 __add__,Cython 也支持 __iadd__,此时的第一个参数是 self,因为 += 这种形式,第一个参数永远是实例对象。

另外这里说的 __add__ 和 __iadd__ 只是举例,其它的算术操作也是可以的。


富比较




Cython 的扩展类也可以使用 __eq, __ne__ 等等和 Python 一致的富比较魔法方法。

cdef class A:
    # 比较操作,Cython 和 Python 类似
    # 第一个参数永远是 self
    # 调用谁的 __eq__,第一个参数就是谁
    def __eq__(self, y):
        return self, y
    def __repr__(self):
        return "A 实例"
print(A() == 123)
print(123 == A())
"""
(A 实例, 123)
(A 实例, 123)
"""

其它的操作符也类似,可以自己试一下。


小结




Python 里面的魔法方法有很多,像迭代器协议、上下文管理、反射等等,Cython 都支持,并且用法一致,这里就不多说了。

注意:魔法方法只能用def定义,不可以使用cdef或者cpdef。

到目前为止,关于扩展类的内容就说完了。总之扩展类和内置类是等价的,都是直接指向了 C 一级的数据结构,不需要字节码的翻译过程。也正因为如此,它失去一些动态特性,但同时也获得了效率,因为这两者本来就是不可兼得的。

Cython 的类有点复杂,还是需要多使用,不过它毕竟在各方面都和 Python 保持接近,因此学习来也不是那么费劲。虽然创建扩展类最简单的方式是通过 Cython,但是通过 Python/C API 直接在 C 中实现的话,则是最有用的练习。

但还是那句话,这需要我们对 Python/C API 有一个很深的了解,而这是一件非常难得的事情,因此使用 Cython 就变成了我们最佳的选择。

E N D


相关文章
|
7月前
|
JSON 缓存 安全
Python 的其他应用: 解释什么是 Python 的 pickle 模块?
Python 的其他应用: 解释什么是 Python 的 pickle 模块?
71 0
|
3月前
|
编译器 API C语言
Cython 是什么?为什么会有 Cython?
Cython 是什么?为什么会有 Cython?
62 0
|
3月前
|
存储 编译器 Linux
Cython 和 Python 的区别
Cython 和 Python 的区别
45 0
|
5月前
|
机器学习/深度学习 编译器 测试技术
什么是 Python 编译器
**Python 编程语言以解释型为主,但也有编译器用于提升性能。CPython是默认解释器,先转为字节码再解释执行。PyPy是JIT编译器,执行速度快。Numba是针对数值计算的JIT编译器,优化数学运算。选择Python编译器要考虑性能、兼容性、内存使用及社区支持。对于机器学习,需支持科学库和GPU加速。**
|
6月前
|
Unix Shell 数据处理
怎样使用Cython提升Python的性能
**Cython是Python的性能增强工具,用于提升Python代码的速度。它允许声明变量类型并调用C库。安装Cython使用`pip install Cython`。Cython语法接近Python,但通过类型声明优化性能。编译Cython代码需创建setup.py文件,然后运行`python setup.py build_ext --inplace`。通过Cython,可以直接优化Python代码和调用C函数,平衡速度与灵活性。**
125 2
|
7月前
|
JSON API 数据库
「Python系列」Python标准库
Python标准库是Python编程语言自带的一系列模块和功能的集合,这些模块提供了各种常见任务的解决方案,如文件处理、网络编程、数据库接口、图形界面开发、科学计算等。使用标准库可以大大提高开发效率,减少重复劳动。
74 0
|
7月前
|
Python
「Python系列」Python函数
Python函数是组织代码的一种方式,它允许你定义可重用的代码块,并通过名称来调用这些代码块。函数可以接收输入(称为参数)并产生输出(称为返回值)。
38 0
|
存储 索引 Python
python中 itertools模块的使用方法
itertools模块的使用方法
93 0
python中 itertools模块的使用方法
|
Java 开发工具 Python
Python 静态分析Pylint、Pyflakes 与 Mypy ——我应该用谁?
Python 静态分析Pylint、Pyflakes 与 Mypy ——我应该用谁?
223 0