千家信息网

如何理解Python的二元算术运算

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,本篇内容介绍了"如何理解Python的二元算术运算"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!查看
千家信息网最后更新 2026年11月01日如何理解Python的二元算术运算

本篇内容介绍了"如何理解Python的二元算术运算"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

查看 C 代码

按照惯例,我们从查看 CPython 解释器编译的字节码开始。

>>> def sub(): a - b ...  >>> import dis >>> dis.dis(sub)   1           0 LOAD_GLOBAL              0 (a)               2 LOAD_GLOBAL              1 (b)               4 BINARY_SUBTRACT               6 POP_TOP               8 LOAD_CONST               0 (None)              10 RETURN_VALUE

看起来我们需要深入研究 BINARY_SUBTRACT 操作码。翻查 Python/ceval.c 文件,可以看到实现该操作码的 C 代码如下:

case TARGET(BINARY_SUBTRACT): {     PyObject *right = POP();     PyObject *left = TOP();     PyObject *diff = PyNumber_Subtract(left, right);     Py_DECREF(right);     Py_DECREF(left);     SET_TOP(diff);     if (diff == NULL)     goto error;     DISPATCH(); }

来源:https://github.com/python/cpython/blob/6f8c8320e9eac9bc7a7f653b43506e75916ce8e8/Python/ceval.c#L1569-L1579

这里的关键代码是PyNumber_Subtract(),实现了减法的实际语义。继续查看该函数的一些宏,可以找到binary_op1() 函数。它提供了一种管理二元操作的通用方法。

不过,我们不把它作为实现的参考,而是要用Python的数据模型,官方文档很好,清楚介绍了减法所使用的语义。

从数据模型中学习

通读数据模型的文档,你会发现在实现减法时,有两个方法起到了关键作用:__sub__ 和 __rsub__。

1、__sub__()方法

当执行a - b 时,会在 a 的类型中查找__sub__(),然后把 b 作为它的参数。这很像我写属性访问的文章 里的__getattribute__(),特殊/魔术方法是根据对象的类型来解析的,并不是出于性能目的而解析对象本身;在下面的示例代码中,我使用_mro_getattr() 表示此过程。

因此,如果已定义 __sub__(),则 type(a).__sub__(a,b) 会被用来作减法操作。(译注:魔术方法属于对象的类型,不属于对象)

这意味着在本质上,减法只是一个方法调用!你也可以将它理解成标准库中的 operator.sub() 函数。

我们将仿造该函数实现自己的模型,用 lhs 和 rhs 两个名称,分别表示 a-b 的左侧和右侧,以使示例代码更易于理解。

# 通过调用__sub__()实现减法  def sub(lhs: Any, rhs: Any, /) -> Any:     """Implement the binary operation `a - b`."""     lhs_type = type(lhs)     try:         subtract = _mro_getattr(lhs_type, "__sub__")     except AttributeError:         msg = f"unsupported operand type(s) for -: {lhs_type!r} and {type(rhs)!r}"         raise TypeError(msg)     else:         return subtract(lhs, rhs)

2、让右侧使用__rsub__()

但是,如果 a 没有实现__sub__() 怎么办?如果 a 和 b 是不同的类型,那么我们会尝试调用 b 的 __rsub__()(__rsub__ 里面的"r"表示"右",代表在操作符的右侧)。

当操作的双方是不同类型时,这样可以确保它们都有机会尝试使表达式生效。当它们相同时,我们假设__sub__() 就能够处理好。但是,即使两边的实现相同,你仍然要调用__rsub__(),以防其中一个对象是其它的(子)类。

3、不关心类型

现在,表达式双方都可以参与运算!但是,如果由于某种原因,某个对象的类型不支持减法怎么办(例如不支持 4 - "stuff")?在这种情况下,__sub__ 或__rsub__ 能做的就是返回 NotImplemented。

这是给 Python 返回的信号,它应该继续执行下一个操作,尝试使代码正常运行。对于我们的代码,这意味着需要先检查方法的返回值,然后才能假定它起作用。

# 减法的实现,其中表达式的左侧和右侧均可参与运算 _MISSING = object()  def sub(lhs: Any, rhs: Any, /) -> Any:         # lhs.__sub__         lhs_type = type(lhs)         try:             lhs_method = debuiltins._mro_getattr(lhs_type, "__sub__")         except AttributeError:             lhs_method = _MISSING          # lhs.__rsub__ (for knowing if rhs.__rub__ should be called first)         try:             lhs_rmethod = debuiltins._mro_getattr(lhs_type, "__rsub__")         except AttributeError:             lhs_rmethod = _MISSING          # rhs.__rsub__         rhs_type = type(rhs)         try:             rhs_method = debuiltins._mro_getattr(rhs_type, "__rsub__")         except AttributeError:             rhs_method = _MISSING          call_lhs = lhs, lhs_method, rhs         call_rhs = rhs, rhs_method, lhs          if lhs_type is not rhs_type:             calls = call_lhs, call_rhs         else:             calls = (call_lhs,)          for first_obj, meth, second_obj in calls:             if meth is _MISSING:                 continue             value = meth(first_obj, second_obj)             if value is not NotImplemented:                 return value         else:             raise TypeError(                 f"unsupported operand type(s) for -: {lhs_type!r} and {rhs_type!r}"             )

4、子类优先于父类

如果你看一下__rsub__() 的文档,就会注意到一条注释。它说如果一个减法表达式的右侧是左侧的子类(真正的子类,同一类的不算),并且两个对象的__rsub__() 方法不同,则在调用__sub__() 之前会先调用__rsub__()。换句话说,如果 b 是 a 的子类,调用的顺序就会被颠倒。

这似乎是一个很奇怪的特例,但它背后是有原因的。当你创建一个子类时,这意味着你要在父类提供的操作上注入新的逻辑。这种逻辑不一定要加给父类,否则父类在对子类操作时,就很容易覆盖子类想要实现的操作。

具体来说,假设有一个名为 Spam 的类,当你执行 Spam() - Spam() 时,得到一个 LessSpam 的实例。接着你又创建了一个 Spam 的子类名为 Bacon,这样,当你用 Spam 去减 Bacon 时,你得到的是 VeggieSpam。

如果没有上述规则,Spam() - Bacon() 将得到 LessSpam,因为 Spam 不知道减掉 Bacon 应该得出 VeggieSpam。

但是,有了上述规则,就会得到预期的结果 VeggieSpam,因为 Bacon.__rsub__() 首先会在表达式中被调用(如果计算的是 Bacon() - Spam(),那么也会得到正确的结果,因为首先会调用 Bacon.__sub__(),因此,规则里才会说两个类的不同的方法需有区别,而不仅仅是一个由 issubclass() 判断出的子类。)

# Python中减法的完整实现 _MISSING = object()  def sub(lhs: Any, rhs: Any, /) -> Any:         # lhs.__sub__         lhs_type = type(lhs)         try:             lhs_method = debuiltins._mro_getattr(lhs_type, "__sub__")         except AttributeError:             lhs_method = _MISSING          # lhs.__rsub__ (for knowing if rhs.__rub__ should be called first)         try:             lhs_rmethod = debuiltins._mro_getattr(lhs_type, "__rsub__")         except AttributeError:             lhs_rmethod = _MISSING          # rhs.__rsub__         rhs_type = type(rhs)         try:             rhs_method = debuiltins._mro_getattr(rhs_type, "__rsub__")         except AttributeError:             rhs_method = _MISSING          call_lhs = lhs, lhs_method, rhs         call_rhs = rhs, rhs_method, lhs          if (             rhs_type is not _MISSING  # Do we care?             and rhs_type is not lhs_type  # Could RHS be a subclass?             and issubclass(rhs_type, lhs_type)  # RHS is a subclass!             and lhs_rmethod is not rhs_method  # Is __r*__ actually different?         ):             calls = call_rhs, call_lhs         elif lhs_type is not rhs_type:             calls = call_lhs, call_rhs         else:             calls = (call_lhs,)          for first_obj, meth, second_obj in calls:             if meth is _MISSING:                 continue             value = meth(first_obj, second_obj)             if value is not NotImplemented:                 return value         else:             raise TypeError(                 f"unsupported operand type(s) for -: {lhs_type!r} and {rhs_type!r}"             )

推广到其它二元运算

解决掉了减法运算,那么其它二元运算又如何呢?好吧,事实证明它们的操作相同,只是碰巧使用了不同的特殊/魔术方法名称。

所以,如果我们可以推广这种方法,那么我们就可以实现 13 种操作的语义:+ 、-、*、@、/、//、%、**、<<、>>、&、^、和 |。

由于闭包和 Python 在对象自省上的灵活性,我们可以提炼出 operator 函数的创建。

# 一个创建闭包的函数,实现了二元运算的逻辑 _MISSING = object()   def _create_binary_op(name: str, operator: str) -> Any:     """Create a binary operation function.      The `name` parameter specifies the name of the special method used for the     binary operation (e.g. `sub` for `__sub__`). The `operator` name is the     token representing the binary operation (e.g. `-` for subtraction).      """      lhs_method_name = f"__{name}__"      def binary_op(lhs: Any, rhs: Any, /) -> Any:         """A closure implementing a binary operation in Python."""         rhs_method_name = f"__r{name}__"          # lhs.__*__         lhs_type = type(lhs)         try:             lhs_method = debuiltins._mro_getattr(lhs_type, lhs_method_name)         except AttributeError:             lhs_method = _MISSING          # lhs.__r*__ (for knowing if rhs.__r*__ should be called first)         try:             lhs_rmethod = debuiltins._mro_getattr(lhs_type, rhs_method_name)         except AttributeError:             lhs_rmethod = _MISSING          # rhs.__r*__         rhs_type = type(rhs)         try:             rhs_method = debuiltins._mro_getattr(rhs_type, rhs_method_name)         except AttributeError:             rhs_method = _MISSING          call_lhs = lhs, lhs_method, rhs         call_rhs = rhs, rhs_method, lhs          if (             rhs_type is not _MISSING  # Do we care?             and rhs_type is not lhs_type  # Could RHS be a subclass?             and issubclass(rhs_type, lhs_type)  # RHS is a subclass!             and lhs_rmethod is not rhs_method  # Is __r*__ actually different?         ):             calls = call_rhs, call_lhs         elif lhs_type is not rhs_type:             calls = call_lhs, call_rhs         else:             calls = (call_lhs,)          for first_obj, meth, second_obj in calls:             if meth is _MISSING:                 continue             value = meth(first_obj, second_obj)             if value is not NotImplemented:                 return value         else:             exc = TypeError(                 f"unsupported operand type(s) for {operator}: {lhs_type!r} and {rhs_type!r}"             )             exc._binary_op = operator             raise exc

有了这段代码,你可以将减法运算定义为 _create_binary_op("sub", "-"),然后根据需要重复定义出其它运算。

"如何理解Python的二元算术运算"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

减法 方法 运算 子类 代码 对象 类型 函数 不同 右侧 表达式 两个 模型 相同 意味 数据 文档 规则 语义 逻辑 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 百单网互联网科技有限公司怎么样 互联网科技公司华为 深圳恒合互联网络科技有限公司 互联网科技深圳 有马互联网科技 齐家互联网科技 长亮科技互联网金融 宁波青枫互联网科技有限公司 贵州叶脉互联网科技有限公司所属行业 坤平互联网科技 民营互联网公司国防科技 内蒙古九一互联网科技 山东子淼互联网科技有限公司 上海钢联属于互联网科技股吗 宇信科技在杭州互联网排第几 互联网风格科技公司名称 世界互联网科技成果奖 青岛易易互联网科技 10位科技互联网大佬 武汉亿视互联网科技有限公司 苏州找大状互联网科技有限公司 北京路客互联网科技有限公司 互联网下的科技发展 礼当家互联网科技 彤创互联网科技有限公司 智腾互联网科技有限公司 卓翼科技产业互联网合作 领航科技大厦互联网公司 互联网科技包装股份有限公司上市 科技发展第三阶段互联网时代
0