Heim  >  Artikel  >  Backend-Entwicklung  >  Ausführliche Erklärung binärer Rechenoperationen in Python

Ausführliche Erklärung binärer Rechenoperationen in Python

coldplay.xixi
coldplay.xixinach vorne
2020-09-10 16:41:212441Durchsuche

Ausführliche Erklärung binärer Rechenoperationen in Python

Verwandte Lernempfehlungen: Python-Tutorial

Die überwältigende Resonanz auf meinen Blog-Beitrag, in dem ich den Attributzugriff erklärt habe, hat mich dazu inspiriert, einen weiteren Beitrag darüber zu schreiben, wie viel von Pythons Syntax eigentlich nur syntaktischer Zucker ist. In diesem Artikel möchte ich über binäre Rechenoperationen sprechen.

Konkret möchte ich erklären, wie die Subtraktion funktioniert: a - b. Ich habe mich bewusst für die Subtraktion entschieden, weil sie nicht kommutativ ist. Dies unterstreicht die Bedeutung der Reihenfolge der Operationen im Vergleich zur Additionsoperation, bei der Sie während der Implementierung versehentlich a und b vertauschen und trotzdem das gleiche Ergebnis erhalten. a - b。我故意选择了减法,因为它是不可交换的。这可以强调出操作顺序的重要性,与加法操作相比,你可能会在实现时误将 a 和 b 翻转,但还是得到相同的结果。

查看 C 代码

按照惯例,我们从查看 CPython 解释器编译的字节码开始。

>>> def sub(): a - b... >>> import dis>>> dis.dis(sub)  1           0 LOAD_GLOBAL              0 (a)              2 LOAD_GLOBAL              1 (b)              4 BINARY_SUBTRACT              6 POP_TOP              8 LOAD_CONST               0 (None)             10 RETURN_VALUE复制代码

看起来我们需要深入研究 BINARY_SUBTRACT  操作码。翻查 Python/ceval.c 文件,可以看到实现该操作码的 C 代码如下:

case TARGET(BINARY_SUBTRACT): {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *diff = PyNumber_Subtract(left, right);
    Py_DECREF(right);
    Py_DECREF(left);
    SET_TOP(diff);    if (diff == NULL)    goto error;
    DISPATCH();
}复制代码

来源:github.com/python/cpyt…

这里的关键代码是PyNumber_Subtract(),实现了减法的实际语义。继续查看该函数的一些宏,可以找到binary_op1() 函数。它提供了一种管理二元操作的通用方法。

不过,我们不把它作为实现的参考,而是要用Python的数据模型,官方文档很好,清楚介绍了减法所使用的语义。

从数据模型中学习

通读数据模型的文档,你会发现在实现减法时,有两个方法起到了关键作用:__sub__ 和 __rsub__。

1、__sub__()方法

当执行a - b

C-Code anzeigen

Wie üblich betrachten wir zunächst den vom CPython-Interpreter kompilierten Bytecode.

# 通过调用__sub__()实现减法 def sub(lhs: Any, rhs: Any, /) -> Any:
    """Implement the binary operation `a - b`."""
    lhs_type = type(lhs)    try:
        subtract = _mro_getattr(lhs_type, "__sub__")    except AttributeError:
        msg = f"unsupported operand type(s) for -: {lhs_type!r} and {type(rhs)!r}"
        raise TypeError(msg)    else:        return subtract(lhs, rhs)复制代码
Sieht so aus, als müssten wir uns mit dem Opcode BINARY_SUBTRACT befassen. Wenn Sie die Datei Python/ceval.c überprüfen, können Sie sehen, dass der C-Code, der diesen Opcode implementiert, wie folgt lautet:

# 减法的实现,其中表达式的左侧和右侧均可参与运算_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}"
            )复制代码
Quelle: github.com/python/cpyt…

Der Schlüsselcode hier ist PyNumber_Subtract(), der implementiert die eigentliche Subtraktionssemantik. Wenn Sie sich weiterhin einige Makros für diese Funktion ansehen, finden Sie die Funktion „binary_op1()“. Es bietet eine allgemeine Möglichkeit, binäre Operationen zu verwalten.

Wir verwenden es jedoch nicht als Referenz für die Implementierung, sondern verwenden das Datenmodell von Python. Die offizielle Dokumentation ist sehr gut und stellt die bei der Subtraktion verwendete Semantik klar vor.

Lernen Sie vom Datenmodell

Lesen Sie die Dokumentation des Datenmodells durch und Sie werden feststellen, dass es zwei Methoden gibt, die eine Schlüsselrolle bei der Implementierung der Subtraktion spielen: __sub__ und __rsub__ .

1. __sub__()-Methode

Beim Ausführen von a - b wird __sub__() im Typ a und dann b gesucht seine Parameter. Dies ist ähnlich wie __getattribute__() in meinem Artikel über den Attributzugriff. Die speziellen/magischen Methoden werden aus Leistungsgründen basierend auf dem Typ des Objekts analysiert, nicht auf dem Objekt selbst, das ich im Beispielcode unten darstelle diesen Prozess.

Wenn also __sub__() definiert ist, wird Typ(a).__sub__(a,b) für die Subtraktionsoperation verwendet. (Anmerkung: Magische Methoden gehören zum Typ des Objekts, nicht zum Objekt)

Das bedeutet, dass die Subtraktion im Wesentlichen nur ein Methodenaufruf ist! Sie können es sich auch als die Funktion „operator.sub()“ in der Standardbibliothek vorstellen.

Wir werden diese Funktion nachahmen, um unser eigenes Modell zu implementieren, indem wir die beiden Namen lhs und rhs verwenden, die jeweils die linke und rechte Seite von a-b darstellen, um den Beispielcode leichter verständlich zu machen.

# 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 (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif 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}"
            )复制代码

2. Lassen Sie die rechte Seite __rsub__() verwenden

Aber was ist, wenn a __sub__() nicht implementiert? Wenn a und b unterschiedliche Typen sind, werden wir versuchen, __rsub__() von b aufzurufen (das „r“ in __rsub__ bedeutet „rechts“, also auf der rechten Seite des Operators).

Wenn beide Seiten der Operation unterschiedlichen Typs sind, stellt dies sicher, dass beide die Möglichkeit haben, zu versuchen, den Ausdruck gültig zu machen. Wenn sie gleich sind, gehen wir davon aus, dass __sub__() damit umgehen wird. Selbst wenn beide Implementierungen identisch sind, müssen Sie dennoch __rsub__() aufrufen, falls eines der Objekte eine (Unter-)Klasse des anderen ist.

3. Der Typ ist egal

🎜Jetzt können beide Seiten des Ausdrucks an der Operation teilnehmen! Was aber, wenn der Typ eines Objekts aus irgendeinem Grund die Subtraktion nicht unterstützt (z. B. 4 – „stuff“ wird nicht unterstützt)? In diesem Fall kann __sub__ oder __rsub__ lediglich NotImplemented zurückgeben. 🎜🎜Dies ist das an Python zurückgegebene Signal, dass es mit der nächsten Operation fortfahren und versuchen soll, den Code ordnungsgemäß auszuführen. Für unseren Code bedeutet das, dass der Rückgabewert der Methode überprüft werden muss, bevor wir davon ausgehen können, dass sie funktioniert. 🎜
# 一个创建闭包的函数,实现了二元运算的逻辑_MISSING = object()def _create_binary_op(name: str, operator: str) -> Any:
    """Create a binary operation function.

    The `name` parameter specifies the name of the special method used for the
    binary operation (e.g. `sub` for `__sub__`). The `operator` name is the
    token representing the binary operation (e.g. `-` for subtraction).

    """

    lhs_method_name = f"__{name}__"

    def binary_op(lhs: Any, rhs: Any, /) -> Any:
        """A closure implementing a binary operation in Python."""
        rhs_method_name = f"__r{name}__"

        # lhs.__*__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, lhs_method_name)        except AttributeError:
            lhs_method = _MISSING        # lhs.__r*__ (for knowing if rhs.__r*__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, rhs_method_name)        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__r*__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, rhs_method_name)        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif 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:
            exc = TypeError(                f"unsupported operand type(s) for {operator}: {lhs_type!r} and {rhs_type!r}"
            )
            exc._binary_op = operator            raise exc复制代码
🎜4. Unterklassen haben Vorrang vor übergeordneten Klassen🎜🎜Wenn Sie sich die Dokumentation für __rsub__() ansehen, werden Sie einen Kommentar bemerken. Darin heißt es: Wenn die rechte Seite eines Subtraktionsausdrucks eine Unterklasse der linken Seite ist (eine echte Unterklasse, nicht dieselbe Klasse) und die __rsub__()-Methoden der beiden Objekte unterschiedlich sind, wird __sub__() vorher aufgerufen Rufen Sie zuerst __rsub__() auf. Mit anderen Worten: Wenn b eine Unterklasse von a ist, ist die Reihenfolge der Aufrufe umgekehrt. 🎜🎜Das mag wie eine seltsame Ausnahme erscheinen, aber es gibt einen Grund dafür. Wenn Sie eine Unterklasse erstellen, bedeutet das, dass Sie zusätzlich zu den von der übergeordneten Klasse bereitgestellten Operationen neue Logik einfügen. Diese Art von Logik muss nicht zur übergeordneten Klasse hinzugefügt werden, da die übergeordnete Klasse sonst leicht die Operationen überschreibt, die die Unterklasse implementieren möchte, wenn sie an der Unterklasse arbeitet. 🎜🎜Angenommen, es gibt eine Klasse namens Spam(). Wenn Sie Spam() - Spam() ausführen, erhalten Sie eine Instanz von LessSpam. Dann erstellen Sie eine Unterklasse von Spam mit dem Namen Bacon. Wenn Sie also Bacon von Spam abziehen, erhalten Sie VeggieSpam. 🎜🎜Ohne die oben genannten Regeln würde Spam() - Bacon() LessSpam ergeben, da Spam nicht weiß, dass das Subtrahieren von Bacon VeggieSpam ergeben sollte. 🎜

但是,有了上述规则,就会得到预期的结果 VeggieSpam,因为 Bacon.__rsub__() 首先会在表达式中被调用(如果计算的是 Bacon() - Spam(),那么也会得到正确的结果,因为首先会调用 Bacon.__sub__(),因此,规则里才会说两个类的不同的方法需有区别,而不仅仅是一个由 issubclass() 判断出的子类。)

# 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 (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif 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}"
            )复制代码

推广到其它二元运算

解决掉了减法运算,那么其它二元运算又如何呢?好吧,事实证明它们的操作相同,只是碰巧使用了不同的特殊/魔术方法名称。

所以,如果我们可以推广这种方法,那么我们就可以实现 13 种操作的语义:+ 、-、*、@、/、//、%、**、<<、>>、&、^、和 |。

由于闭包和 Python 在对象自省上的灵活性,我们可以提炼出 operator 函数的创建。

# 一个创建闭包的函数,实现了二元运算的逻辑_MISSING = object()def _create_binary_op(name: str, operator: str) -> Any:
    """Create a binary operation function.

    The `name` parameter specifies the name of the special method used for the
    binary operation (e.g. `sub` for `__sub__`). The `operator` name is the
    token representing the binary operation (e.g. `-` for subtraction).

    """

    lhs_method_name = f"__{name}__"

    def binary_op(lhs: Any, rhs: Any, /) -> Any:
        """A closure implementing a binary operation in Python."""
        rhs_method_name = f"__r{name}__"

        # lhs.__*__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, lhs_method_name)        except AttributeError:
            lhs_method = _MISSING        # lhs.__r*__ (for knowing if rhs.__r*__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, rhs_method_name)        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__r*__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, rhs_method_name)        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif 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:
            exc = TypeError(                f"unsupported operand type(s) for {operator}: {lhs_type!r} and {rhs_type!r}"
            )
            exc._binary_op = operator            raise exc复制代码

有了这段代码,你可以将减法运算定义为 _create_binary_op(“sub”, “-”),然后根据需要重复定义出其它运算。

想了解更多编程学习,敬请关注php培训栏目!

Das obige ist der detaillierte Inhalt vonAusführliche Erklärung binärer Rechenoperationen in Python. 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