Maison  >  Article  >  développement back-end  >  Explication détaillée de l'opération d'affectation arithmétique améliorée "-="

Explication détaillée de l'opération d'affectation arithmétique améliorée "-="

coldplay.xixi
coldplay.xixiavant
2020-09-11 17:11:102678parcourir

Explication détaillée de l'opération d'affectation arithmétique améliorée

Recommandations d'apprentissage associées : Tutoriel Python

Préface

Cet article est une série d'articles sur la syntaxe Python Sugar One. Le dernier code source peut être trouvé dans le projet desugar (github.com/brettcannon…

Introduction

Python a quelque chose appelé 增强算术赋值 (affectation arithmétique augmentée). Peut-être que vous n'êtes pas familier avec this Le nom est en fait une affectation lors d'opérations mathématiques. Par exemple, a -= b est l'affectation arithmétique améliorée de la soustraction

L'affectation améliorée a été ajoutée dans Python 2.0 (Traduction : dans PEP- Introduit en 203) <.>

Analyse

-=

Parce que Python ne permet pas l'écrasement de l'affectation, donc par rapport à d'autres opérations avec des méthodes spéciales/magiques, la façon dont il implémente l'affectation améliorée peut être différente de ce que vous imaginez. exactement la même chose que

Tout d'abord, sachez que

est sémantiquement la même chose que a -= b mais sachez aussi que si vous savez à l'avance que vous souhaitez attribuer un nom de variable à un objet, ce sera différent que a = a-b. Les opérations aveugles de 🎜> peuvent être plus efficaces a - b

Par exemple, l'avantage minimum est qu'elles peuvent éviter de créer un nouvel objet : si un objet peut être modifié sur place, alors il est préférable de se retourner soi-même. que d'en reconstruire un nouveau. Les objets sont efficaces.

Par conséquent, Python fournit une méthode __isub__() qui sera appelée sur la valeur du côté droit (souvent appelée lvalue) si elle est définie sur le côté gauche. de l'opération d'affectation. rvalue). Donc pour

, il essaiera d'appeler a.__isub__(b). Si le résultat de l'appel est NotImplemented, ou s'il n'y a aucun résultat, Python reviendra au binaire normal. arithmétique.Opération : a -= b (Traduction : L'article de l'auteur sur les opérations binaires, la traduction est ici)

En fin de compte, quelle que soit la méthode utilisée, la valeur de retour sera attribuée à un ci-dessous. est un simple pseudocode, a - b se décompose en :

# 实现 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复制代码

Induction de ces méthodes

Puisque nous avons déjà implémenté des opérations arithmétiques binaires, il n'est pas trop compliqué d'induire des opérations arithmétiques améliorées 🎜. >a -= bEn passant une fonction d'opération arithmétique binaire et en effectuant une certaine introspection (et en gérant les TypeErrors possibles), cela peut être clairement résumé comme :

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复制代码

Cela rend le -= support_ create_binary_inplace_op(__ sub__) défini , et peut déduire d'autres choses : le nom de la fonction, comment la fonction __i*__ est appelée et laquelle appeler lorsqu'une opération arithmétique binaire tourne mal

J'ai trouvé que presque personne n'utilise

Lors de l'écriture du code de cet article, j'ai rencontré un bug de test étrange avec **=. Parmi tous les tests garantissant que __pow__ serait appelé de manière appropriée, il y en avait un. Le cas d'utilisation échoue pour le

. module dans la bibliothèque standard Python.

Mon code est généralement correct, et s'il y a des différences entre le code et celui de CPython, cela signifie généralement que je fais quelque chose de mal.

**=Cependant, peu importe le soin avec lequel j'ai fouillé le code, je n'ai pas pu déterminer pourquoi mes tests ont réussi mais la bibliothèque standard a échoué.

J'ai décidé de regarder de plus près ce qui se passe sous le capot de CPython. À partir du bytecode démonté : operator

>>> 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复制代码

Grâce à lui, j'ai trouvé le

dans la boucle eval :

        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();
        }复制代码

Source : github.com/python/cpyt…

Alors trouvez

 :

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), "**=");
    }
}复制代码
INPLACE_POWER Source : github.com/python/cpyt…

Poussa un soupir de soulagement~ Le code montre que si __ipow__ est défini, il sera appelé, mais __pow__ n'est appelé que s'il n'y a pas de __ipow__.

Cependant, l'approche correcte devrait être : PyNumber_InPlacePower()Si quelque chose ne va pas lors de l'appel de __ipow__ et renvoie NotImplemented ou ne revient pas du tout, alors __pow__ et __rpow__ doivent être appelés.

En d'autres termes, le code ci-dessus ignore accidentellement la sémantique de secours de a**b lorsque __ipow__ est présent !

En fait, ce problème a été partiellement découvert et un bug soumis il y a environ 11 mois. J'ai résolu le problème et l'ai documenté sur python-dev.

Pour l'instant, il semble que cela sera corrigé dans Python 3.10, nous devons également ajouter un avis concernant **= étant bogué dans la documentation pour 3.8 et 3.9 (le problème est peut-être là depuis longtemps , mais il est plus ancien. La version Python est déjà en mode maintenance de sécurité uniquement, la documentation ne changera donc pas).

Le code corrigé ne sera probablement pas porté, car il s'agit d'un changement sémantique, et il est difficile de dire si quelqu'un s'est accidentellement appuyé sur la sémantique problématique. Mais il a fallu tellement de temps pour que ce problème soit remarqué, ce qui suggère que **= n'est pas largement utilisé, sinon le problème aurait été découvert il y a longtemps.

Si vous souhaitez en savoir plus sur la programmation, faites attention à la rubrique

Formation php

!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer