관련 학습 권장 사항: python 튜토리얼
이 문서는 Python 구문 설탕에 대한 일련의 문서 중 하나입니다. 최신 소스 코드는 desugar 프로젝트(github.com/brettcannon…
Python에는 향상된 산술 할당
(증강 산술 할당)이라는 기능이 있습니다. 아마 여러분은 이에 익숙하지 않을 수도 있습니다. 호출됨 수학 연산을 수행하면서 실제로 할당을 수행합니다. 예를 들어 a -= b는 뺄셈의 향상된 산술 할당입니다. 增强算术赋值
(augmented arithmetic assignment)的东西。可能你不熟悉这个叫法,其实就是在做数学运算的同时进行赋值,例如 a -= b 就是减法的增强算术赋值。
增强赋值是在 Python 2.0 版本中 加入进来的。(译注:在 PEP-203 中引入)
-=
因为 Python 不允许覆盖式赋值,所以相比其它有特殊/魔术方法的操作,它实现增强赋值的方式可能跟你想象的不完全一样。
首先,要知道a -= b
在语义上与 a = a-b
相同。但也要意识到,如果你预先知道要将一个对象赋给一个变量名,相比a - b
的盲操作,就可能会更高效。
例如,最起码的好处是可以避免创建一个新对象:如果可以就地修改一个对象,那么返回 self,就比重新构造一个新对象要高效。
因此,Python 提供了一个__isub__() 方法。如果它被定义在赋值操作的左侧(通常称为 lvalue),则会调用右侧的值(通常称为 rvalue )。所以对于a -= b
,就会尝试去调用 a.__isub__(b)。
如果调用的结果是 NotImplemented,或者根本不存在结果,那么 Python 会退回到常规的二元算术运算:a - b
。(译注:作者关于二元运算的文章,译文在此)
最终无论用了哪种方法,返回值都会被赋值给 a。
下面是简单的伪代码,a -= b
被分解成:
# 实现 a -= b 的伪代码if hasattr(a, "__isub__"): _value = a.__isub__(b) if _value is not NotImplemented: a = _value else: a = a - b del _value else: a = a - b复制代码
由于我们已经实现了二元算术运算,因此归纳增强算术运算并不太复杂。
通过传入二元算术运算函数,并做一些自省(以及处理可能发生的 TypeError),它可以被漂亮地归纳成:
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}=" def binary_inplace_op(lvalue: Any, rvalue: Any, /) -> Any: lvalue_type = type(lvalue) try: method = debuiltins._mro_getattr(lvalue_type, method_name) except AttributeError: pass else: value = method(lvalue, rvalue) if value is not NotImplemented: return value try: return binary_op(lvalue, rvalue) except TypeError as exc: # If the TypeError is due to the binary arithmetic operator, suppress # it so we can raise the appropriate one for the agumented assignment. if exc._binary_op != binary_op._operator: raise raise TypeError( f"unsupported operand type(s) for {operator}: {lvalue_type!r} and {type(rvalue)!r}" ) binary_inplace_op.__name__ = binary_inplace_op.__qualname__ = method_name binary_inplace_op.__doc__ = ( f"""Implement the augmented arithmetic assignment `a {operator} b`.""" ) return binary_inplace_op复制代码
这使得定义的 -= 支持 _create_binary_inplace_op(__ sub__),且可以推断出其它内容:函数名、调用什么 __i*__ 函数,以及当二元算术运算出问题时,该调用哪个可调用对象。
**=
在写本文的代码时,我碰上了 **= 的一个奇怪的测试错误。在所有确保 __pow__ 会被适当地调用的测试中,有个测试用例对于 Python 标准库中的operator
模块却是失败。
我的代码通常没问题,如果代码与 CPython 的代码之间存在差异,通常会意味着是我哪里出错了。
但是,无论我多么仔细地排查代码,我都无法定位出为什么我的测试会通过,而标准库则失败。
我决定深入地了解 CPython 内部发生了什么。从反汇编字节码开始:
>>> def test(): a **= b... >>> import dis>>> dis.dis(test) 1 0 LOAD_FAST 0 (a) 2 LOAD_GLOBAL 0 (b) 4 INPLACE_POWER 6 STORE_FAST 0 (a) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE复制代码
通过它,我找到了在 eval 循环中的INPLACE_POWER
:
case TARGET(INPLACE_POWER): { PyObject *exp = POP(); PyObject *base = TOP(); PyObject *res = PyNumber_InPlacePower(base, exp, Py_None); Py_DECREF(base); Py_DECREF(exp); SET_TOP(res); if (res == NULL) goto error; DISPATCH(); }复制代码
出处:github.com/python/cpyt…
然后找到PyNumber_InPlacePower()
-=
파이썬은 덮어쓰기 할당을 허용하지 않기 때문에 특수/마법 메서드를 사용하는 다른 작업에 비해 향상된 할당을 구현합니다.
먼저, a -= b
는 의미론적으로 a = a-b
와 동일하다는 점을 알아두세요. 하지만 객체를 a에 할당하려는 것을 미리 알고 있다면 변수 이름은 a - b
의 맹목적인 작업보다 더 효율적일 수 있습니다. 예를 들어, 최소한의 이점은 새 개체 생성을 피할 수 있다는 것입니다. 개체를 제자리에서 수정할 수 있는 경우 self를 반환하면 새 객체를 재구성하는 것보다 더 효율적입니다. 따라서 할당 작업의 왼쪽에 정의된 경우(일반적으로 lvalue라고 함) Python은 __isub__() 메서드를 제공하고 오른쪽의 값(일반적으로 rvalue라고 함)은 다음과 같습니다. 따라서 a -= b
의 경우 a.__isub__(b) 호출을 시도합니다.
호출 결과가 NotImplemented이거나 결과가 전혀 없으면 Python은 실패합니다. 일반 이진 산술 연산으로 돌아가기: a - b
(주석: 이진 연산에 대한 저자의 기사, 번역은 여기에 있음)
결국 어떤 방법을 사용하든 반환 값은 다음과 같습니다.
다음은 간단한 의사 코드입니다. a -= b
는 다음과 같이 분해됩니다.
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) { return ternary_op(v, w, z, NB_SLOT(nb_inplace_power), "**="); } else { return ternary_op(v, w, z, NB_SLOT(nb_power), "**="); } }复制代码
이 기사의 코드를 작성하는 동안 **=에 대한 이상한 테스트에 직면했습니다. 실수. __pow__가 적절하게 호출되는지 확인하는 모든 테스트 중에서 Python 표준 라이브러리의이진 산술 연산 함수를 전달하고 몇 가지 자체 검사(발생할 수 있는 TypeError 처리 포함)를 수행하면 다음과 같이 깔끔하게 요약될 수 있습니다. rrreee이것은 정의된 _create_binary_inplace_op(__ sub__)를 지원하고 함수 이름, 호출되는 __i*__ 함수, 이진 산술 연산 시 문제가 발생할 때 호출할 수 있는 항목 등을 추론할 수 있습니다.
**=
를 사용하는 사람이 거의 없다는 사실을 발견했습니다
operator
모듈에 대해 실패하는 테스트가 있습니다. 🎜🎜내 코드는 일반적으로 문제가 없으며, 코드와 CPython의 코드 사이에 차이가 있으면 일반적으로 내가 뭔가 잘못하고 있다는 의미입니다. 🎜🎜그러나 코드 문제를 아무리 주의 깊게 해결해도 테스트는 통과하는데 표준 라이브러리는 실패하는 이유를 정확히 알 수 없습니다. 🎜🎜저는 CPython 내부에서 무슨 일이 일어나고 있는지 자세히 살펴보기로 했습니다. 디스어셈블리 바이트코드에서 시작: 🎜rrreee🎜이를 통해 평가 루프에서 INPLACE_POWER
를 찾았습니다: 🎜rrreee🎜출처: github.com/python/cpyt…🎜🎜그런 다음 PyNumber_InPlacePower( )
: 🎜rrreee🎜출처: github.com/python/cpyt…🎜🎜안심 ~ 코드는 __ipow__가 정의되면 호출되지만 __ipow__가 없는 경우에만 __pow__가 호출된다는 것을 보여줍니다. 🎜🎜그러나 올바른 접근 방식은 다음과 같습니다. 🎜 __ipow__를 호출할 때 문제가 발생하여 NotImplemented를 반환하거나 전혀 반환이 없는 경우 __pow__ 및 __rpow__를 호출해야 합니다. 🎜🎜🎜즉, 위 코드는 __ipow__가 있을 때 실수로 a**b의 대체 의미를 건너뛰는 것입니다! 🎜🎜사실 이 문제는 부분적으로 발견되었으며 약 11개월 전에 버그가 접수되었습니다. 문제를 해결하고 python-dev에 문서화했습니다. 🎜🎜현재로서는 Python 3.10에서 이 문제가 수정될 것으로 보입니다. 또한 3.8 및 3.9 문서에 **= 버그가 있다는 알림을 추가해야 합니다(이 문제는 오래 전에 있었을 수도 있지만 이전 Python 버전에는 보안 전용 유지 관리 모드가 있으므로 설명서는 변경되지 않습니다. 🎜🎜수정된 코드는 의미론적 변경이므로 이식되지 않을 가능성이 높으며 누군가 실수로 문제가 있는 의미론에 의존했는지 알기 어렵습니다. 하지만 이 문제가 발견되기까지 너무 오랜 시간이 걸렸습니다. 이는 **=가 널리 사용되지 않았음을 의미합니다. 그렇지 않으면 문제가 오래 전에 발견되었을 것입니다. 🎜🎜🎜프로그래밍 학습에 대해 더 자세히 알고 싶다면 🎜php training🎜 칼럼을 주목해주세요! 🎜🎜🎜위 내용은 강화된 연산 할당 '-=' 연산에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!