详解 Python 的二元算术运算,为什么说减法只是语法糖?( 二 )


当操作的双方是不同类型时,这样可以确保它们都有机会尝试使表达式生效 。当它们相同时,我们假设__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 。


推荐阅读