Maison > Article > développement back-end > Explication détaillée de l'opération d'affectation arithmétique améliorée "-="
Recommandations d'apprentissage associées : Tutoriel Python
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…
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-=
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
, 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 -= b
En 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复制代码
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.
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!