> 백엔드 개발 > 파이썬 튜토리얼 > 강화된 연산 할당 '-=' 연산에 대한 자세한 설명

강화된 연산 할당 '-=' 연산에 대한 자세한 설명

coldplay.xixi
풀어 주다: 2020-09-11 17:11:10
앞으로
2770명이 탐색했습니다.

강화된 연산 할당 '-=' 연산에 대한 자세한 설명

관련 학습 권장 사항: python 튜토리얼

머리말

이 문서는 Python 구문 설탕에 대한 일련의 문서 중 하나입니다. 최신 소스 코드는 desugar 프로젝트(github.com/brettcannon…

Introduction

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()

Python 2.0에서 향상된 할당이 추가되었습니다. (번역: PEP-203에서 도입됨)

분석-=

파이썬은 덮어쓰기 할당을 허용하지 않기 때문에 특수/마법 메서드를 사용하는 다른 작업에 비해 향상된 할당을 구현합니다.

먼저, 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), "**=");
    }
}复制代码
로그인 후 복사

이러한 메소드를 일반화하세요

이미 이진 산술이 구현되어 있으므로 향상된 산술을 일반화하는 것은 그리 복잡하지 않습니다.

이진 산술 연산 함수를 전달하고 몇 가지 자체 검사(발생할 수 있는 TypeError 처리 포함)를 수행하면 다음과 같이 깔끔하게 요약될 수 있습니다. rrreee이것은 정의된 _create_binary_inplace_op(__ sub__)를 지원하고 함수 이름, 호출되는 __i*__ 함수, 이진 산술 연산 시 문제가 발생할 때 호출할 수 있는 항목 등을 추론할 수 있습니다.

**=를 사용하는 사람이 거의 없다는 사실을 발견했습니다

이 기사의 코드를 작성하는 동안 **=에 대한 이상한 테스트에 직면했습니다. 실수. __pow__가 적절하게 호출되는지 확인하는 모든 테스트 중에서 Python 표준 라이브러리의 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.im
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿