简介Python有一种叫做增强算术赋值的东西。你可能不熟悉这个术语,但它实际上是在进行数学运算时的赋值,例如a-=b是减法的增强算术赋值。增强赋值是在Python2.0版中添加的。(译注:在PEP-203中引入)Anatomy-=由于Python不允许覆盖赋值,因此与其他具有特殊/魔术方法的操作相比,它可能无法完全按照您的想象实现扩充赋值。首先,知道a-=b在语义上与a=a-b相同。但也要意识到,如果你事先知道你要将一个对象赋值给一个变量名,那么它可能比盲目操作a-b效率更高。例如,最小的好处是避免创建一个新对象:如果一个对象可以就地修改,那么返回self比构造一个新对象更有效。因此,Python提供了__isub__()方法。如果它定义在赋值的左侧(通常称为左值),则调用右侧的值(通常称为右值)。所以对于a-=b,它会尝试调用a.__isub__(b)。如果调用的结果是NotImplemented,或者根本没有结果,那么Python会回退到常规的二进制算术:a-b。最后无论使用哪种方法,返回值都会赋值给a。下面是一个简单的伪代码,a-=b分解为:#实现a-=b的伪代码ifhasattr(a,"__isub__"):_value=a.__isub__(b)if_valueisnotNotImplemented:a=_valueelse:a=a-bdel_valueelse:a=a-b这些方法的归纳由于我们已经实现了二进制算术,归纳增广算术并不太复杂。通过传入二进制算术运算函数,并进行一些自省(如果发生类型错误则进行处理),它可以巧妙地简化为:def_create_binary_inplace_op(binary_op:_BinaryOp)->Callable[[Any,Any],Any]:binary_operation_name=binary_op.__name__[2:-2]method_name=f"__i{binary_operation_name}__"operator=f"{binary_op._operator}="defbinary_inplace_op(lvalue:Any,rvalue:Any,/)->Any:lvalue_type=type(lvalue)try:method=debuiltins._mro_getattr(lvalue_type,method_name)除了AttributeError:passelse:value=method(lvalue,rvalue)ifvalueisnotNotImplemented:returnvaluetry:returnbinary_op(lvalue,rvalue)exceptTypeErrorasexc:#如果TypeError是由于二元算术运算符引起的,则抑制它#以便我们可以为约定的赋值提出适当的错误。如果exc._binary_op!=binary_op._operator:raiseraiseTypeError(f"{operator}不支持的操作数类型:{lvalue_type!r}和{type(rvalue)!r}")binary_inplace_op.__name__=binary_inplace_op.__qualname__=method_namebinary_inplace_op.__doc__=(f"""实现增广算术赋值`a{operator}b`。""")returnbinary_inplace_op这使得定义的-=支持create_binary_inplace_op(_sub__),并且可以推断出其他的东西:函数名,调用了什么i*函数,以及Which当二进制运算出现问题时可以调用吗?我发现几乎没有人使用=**在为本文编写代码时,我遇到了一个带有**=的奇怪测试错误。在确保正确调用pow的所有测试中,Python标准库中有一个针对operator模块的测试用例失败了。我的代码通常没有问题,如果我的代码和CPython的代码之间存在差异,通常意味着我有问题。然而,无论我多么仔细地对代码进行故障排除,我都找不到为什么我的测试通过而标准库失败的原因。我决定更深入地了解CPython内部发生的事情。从反汇编的字节码开始:>>>deftest():a**=b...>>>importdis>>>dis.dis(test)10LOAD_FAST0(a)2LOAD_GLOBAL0(b)4INPLACE_POWER6STORE_FAST0(a)8LOAD_CONST0(None)10RETURN_VALUE通过它,我在eval循环中找到了INPLACE_POWER:caseTARGET(INPLACE_POWER):{PyObject*exp=POP();PyObject*base=TOP();PyObject*res=PyNumber_InPlacePower(base,exp,Py_None);Py_DECREF(基础);Py_DECREF(exp);SET_TOP(资源);如果(res==NULL)转到错误;派遣();}然后找到PyNumber_InPlacePower():PyObject*PyNumber_InPlacePower(PyObject*v,PyObject*w,PyObject*z){if(v->ob_type->tp_as_number&&v->ob_type->tp_as_number->nb_inplace_power!=NULL){返回三元操作(v,w,z,NB_SLOT(nb_inplace_power),"**=";}else{returnternary_op(v,w,z,NB_SLOT(nb_power),"**=");}}放心~代码显示如果定义了__ipow__就会调用,但是__pow__只有在没有__ipow__的时候才会调用但是正确的做法应该是:如果调用__ipow__有问题,返回NotImplemented或者有根本没有回报,那么应该调用pow和__rpow__。换句话说,当__ipow__存在时,上面的代码不小心跳过了a**b的回退语义!事实上,大约11个月前,这个问题被部分发现并提交了一个错误。我修复了它并在python-dev上做了一个注释。截至目前,这看起来将在Python3.10中得到修复,我们还需要添加一条通知,指出**=在3.8和3.9的文档中存在错误(该问题可能在更早的时候就存在,但旧的Python版本已经在安全维护模式,因此文档不会更改)。固定代码很可能不会被移植,因为它是语义更改,很难判断是否有人不小心依赖了相关语义。但是这个问题拖了这么久才被发现,这说明**=并没有被广泛使用,否则问题早就被发现了。以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享
