Heim  >  Artikel  >  Backend-Entwicklung  >  Ausführliche Erläuterung der erweiterten arithmetischen Zuweisungsoperation „-="

Ausführliche Erläuterung der erweiterten arithmetischen Zuweisungsoperation „-="

coldplay.xixi
coldplay.xixinach vorne
2020-09-11 17:11:102678Durchsuche

Ausführliche Erläuterung der erweiterten arithmetischen Zuweisungsoperation „-=

Verwandte Lernempfehlungen: Python-Tutorial

Vorwort

Dieser Artikel gehört zu einer Reihe von Artikeln über den syntaktischen Zucker von Python. Den neuesten Quellcode finden Sie im Desugar-Projekt (github.com/brettcannon…

Einführung

Python verfügt über etwas namens enhanced arithmetic attachment (erweiterte arithmetische Zuweisung). Vielleicht sind Sie damit nicht vertraut aufgerufen Die Methode führt tatsächlich eine Zuweisung durch, während sie mathematische Operationen durchführt. Beispielsweise ist a -= b die erweiterte arithmetische Zuweisung der Subtraktion. 增强算术赋值(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()

Erweiterte Zuweisung wurde in Python 2.0 hinzugefügt (Übersetzung: Eingeführt in PEP-203)

Analysis-=

Da Python keine überschreibende Zuweisung zulässt, implementiert es eine erweiterte Zuweisung im Vergleich zu anderen Operationen mit speziellen/magischen Methoden. Es ist möglicherweise nicht genau das, was Sie denken.

Wissen Sie zunächst, dass a -= b semantisch dasselbe ist wie a = a-b, aber machen Sie sich auch bewusst, dass Sie im Voraus wissen, dass Sie a ein Objekt zuweisen möchten Der Variablenname ist möglicherweise effizienter als die Blindoperation von a - b Der minimale Vorteil besteht beispielsweise darin, dass Sie die Erstellung eines neuen Objekts vermeiden können: Wenn ein Objekt an Ort und Stelle geändert werden kann, wird self zurückgegeben ist effizienter als die Rekonstruktion eines neuen Objekts. Daher stellt Python eine __isub__()-Methode bereit, wenn sie auf der linken Seite der Zuweisungsoperation (normalerweise als .lvalue bezeichnet) definiert wird, der Wert auf der rechten Seite (normalerweise als rvalue bezeichnet). aufgerufen. Für a -= b wird also versucht, a.__isub__(b) aufzurufen.

Wenn das Ergebnis des Aufrufs „NotImplemented“ ist oder überhaupt kein Ergebnis vorliegt, stürzt Python ab Zurück zu regulären binären arithmetischen Operationen: a - b (Anmerkung: Der Artikel des Autors über binäre Operationen, die Übersetzung finden Sie hier)

Am Ende wird der Rückgabewert unabhängig von der verwendeten Methode sein a zugewiesen.

Das Folgende ist ein einfacher Pseudocode, a -= b wird zerlegt in:

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), "**=");
    }
}复制代码

Verallgemeinern Sie diese Methoden

Da wir Da die binäre Arithmetik bereits implementiert ist, ist es nicht allzu kompliziert, die erweiterte Arithmetik zu verallgemeinern.

Durch die Übergabe der binären arithmetischen Operationsfunktion und eine gewisse Selbstbeobachtung (sowie die Behandlung des möglicherweise auftretenden TypeErrors) lässt sich dies gut wie folgt zusammenfassen: rrreeeDadurch unterstützt das definierte -= _create_binary_inplace_op(__ sub__) und kann auf andere Dinge schließen: den Funktionsnamen, welche Funktion __i*__ aufgerufen wird und wann binäre arithmetische Operationen ausgeführt werden, die aufgerufen werden können, wenn etwas schief geht.

Ich habe festgestellt, dass fast niemand **= verwendet

Während ich den Code für diesen Artikel schrieb, stieß ich auf einen seltsamen Test von **= Fehler. Von allen Tests, die sicherstellen, dass __pow__ ordnungsgemäß aufgerufen wird, schlägt einer für das Modul operator in der Python-Standardbibliothek fehl. 🎜🎜Mein Code ist normalerweise in Ordnung, und wenn es Unterschiede zwischen dem Code und dem Code von CPython gibt, bedeutet das normalerweise, dass ich etwas falsch mache. 🎜🎜Aber egal wie sorgfältig ich den Code behebe, ich kann nicht genau sagen, warum meine Tests erfolgreich sind, die Standardbibliothek jedoch fehlschlägt. 🎜🎜Ich habe beschlossen, einen genaueren Blick darauf zu werfen, was unter der Haube von CPython vor sich geht. Ausgehend vom zerlegten Bytecode: 🎜rrreee🎜Dadurch habe ich INPLACE_POWER in der Auswertungsschleife gefunden: 🎜rrreee🎜Quelle: github.com/python/cpyt…🎜🎜Dann habe ich PyNumber_InPlacePower( ): 🎜rrreee🎜Quelle: github.com/python/cpyt…🎜🎜Erleichtert ~ Der Code zeigt, dass __ipow__ aufgerufen wird, wenn es definiert ist, aber nur, wenn kein __ipow__ vorhanden ist. __pow__ wird aufgerufen. 🎜🎜Der richtige Ansatz sollte jedoch sein: 🎜Wenn beim Aufruf von __ipow__ etwas schief geht und NotImplemented zurückgegeben wird oder es überhaupt keine Rückgabe gibt, sollten __pow__ und __rpow__ aufgerufen werden. 🎜🎜🎜Mit anderen Worten, der obige Code überspringt versehentlich die Fallback-Semantik von a**b, wenn __ipow__ vorhanden ist! 🎜🎜Tatsächlich wurde dieses Problem vor etwa 11 Monaten teilweise entdeckt und ein Fehler gemeldet. Ich habe das Problem behoben und auf python-dev dokumentiert. 🔜 ältere Python-Versionen befinden sich im reinen Sicherheitswartungsmodus, sodass sich die Dokumentation nicht ändert. 🎜🎜Der feste Code wird höchstwahrscheinlich nicht portiert, da es sich um eine semantische Änderung handelt und es schwer zu sagen ist, ob sich jemand versehentlich auf die problematische Semantik verlassen hat. Aber es hat so lange gedauert, bis dieses Problem bemerkt wurde, was darauf hindeutet, dass **= nicht weit verbreitet ist, sonst wäre das Problem schon vor langer Zeit entdeckt worden. 🎜🎜🎜Wenn Sie mehr über das Erlernen des Programmierens erfahren möchten, achten Sie bitte auf die Spalte „PHP-Schulung“! 🎜🎜🎜

Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung der erweiterten arithmetischen Zuweisungsoperation „-=". Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen