Maison  >  Article  >  développement back-end  >  Explication détaillée des opérations arithmétiques binaires en Python

Explication détaillée des opérations arithmétiques binaires en Python

coldplay.xixi
coldplay.xixiavant
2020-09-10 16:41:212485parcourir

Explication détaillée des opérations arithmétiques binaires en Python

Recommandations d'apprentissage associées : Tutoriel Python

Tout le monde a répondu avec enthousiasme à mon article de blog sur l'interprétation de l'accès aux attributs, qui a inspiré moi, j'écrirai un autre article sur la part de la syntaxe de Python qui n'est en réalité que du sucre syntaxique. Dans cet article, je veux parler des opérations arithmétiques binaires.

Plus précisément, je veux expliquer comment fonctionne la soustraction : a - b. J'ai choisi volontairement la soustraction car elle n'est pas commutative. Cela souligne l'importance de l'ordre des opérations, par rapport à l'opération d'addition, où vous pouvez par erreur inverser a et b pendant l'implémentation et obtenir toujours le même résultat.

Affichage du code C

Comme d'habitude, nous commençons par regarder le bytecode compilé par l'interpréteur 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复制代码

On dirait que nous devons creuser dans l'opcode BINARY_SUBTRACT. En vérifiant le fichier Python/ceval.c, vous pouvez voir que le code C qui implémente cet opcode est le suivant :

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

Source : github.com/python/cpyt…

Le code clé voici PyNumber_Subtract (), implémente la sémantique réelle de la soustraction. En continuant à regarder quelques macros de cette fonction, vous pouvez trouver la fonction binaire_op1(). Il fournit un moyen général de gérer les opérations binaires.

Cependant, nous ne l'utilisons pas comme référence pour l'implémentation, mais utilisons le modèle de données Python. La documentation officielle est très bonne et présente clairement la sémantique utilisée dans la soustraction.

Apprenez du modèle de données

Lisez la documentation du modèle de données et vous découvrirez qu'il existe deux méthodes qui jouent un rôle clé dans la mise en œuvre de la soustraction : __sub__ et __rsub__.

1. Méthode __sub__()

Lors de l'exécution de a - b, elle recherchera __sub__() dans le type de a, puis utilisera b comme paramètre. Cela ressemble beaucoup à __getattribute__() dans mon article sur l'accès aux attributs, les méthodes spéciales/magiques sont analysées en fonction du type de l'objet, et non de l'objet lui-même à des fins de performances, dans l'exemple de code ci-dessous, j'utilise _ mro_getattr() représente ; ce processus.

Par conséquent, si __sub__() est défini, type(a).__sub__(a,b) sera utilisé pour la soustraction. (Annotation : les méthodes magiques appartiennent au type de l'objet, pas à l'objet)

Cela signifie qu'en substance, la soustraction n'est qu'un appel de méthode ! Vous pouvez également la considérer comme la fonction Operator.sub() dans la bibliothèque standard.

Nous imiterons cette fonction pour implémenter notre propre modèle, en utilisant les deux noms lhs et rhs, représentant respectivement les côtés gauche et droit de a-b, pour rendre l'exemple de code plus facile à comprendre.

# 通过调用__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)复制代码

2. Laissez le côté droit utiliser __rsub__()

Mais que se passe-t-il si a n'implémente pas __sub__() ? Si a et b sont de types différents, alors nous essaierons d'appeler le __rsub__() de b (le "r" dans __rsub__ signifie "droit", ce qui signifie sur le côté droit de l'opérateur).

Lorsque les deux côtés de l'opération sont de types différents, cela garantit qu'ils ont tous deux une chance d'essayer de valider l'expression. Lorsqu'ils sont identiques, nous supposons que __sub__() s'en chargera. Cependant, même si les deux implémentations sont identiques, vous devez toujours appeler __rsub__() au cas où l'un des objets serait une (sous)classe de l'autre.

3. Ne vous souciez pas du type

Maintenant, les deux côtés de l'expression peuvent participer à l'opération ! Mais que se passe-t-il si, pour une raison quelconque, le type d'un objet ne prend pas en charge la soustraction (par exemple, 4 - "trucs" n'est pas pris en charge) ? Dans ce cas, tout ce que __sub__ ou __rsub__ peut faire est de renvoyer NotImplemented.

Il s'agit d'un signal renvoyé à Python lui indiquant qu'il doit passer à l'opération suivante pour essayer de faire fonctionner le code correctement. Pour notre code, cela signifie que la valeur de retour de la méthode doit être vérifiée avant de pouvoir supposer qu'elle fonctionne.

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

4. Les sous-classes ont priorité sur les classes parentes

Si vous regardez la documentation de __rsub__(), vous remarquerez un commentaire. Il dit que si le côté droit d'une expression de soustraction est une sous-classe du côté gauche (une vraie sous-classe, pas la même classe) et que les méthodes __rsub__() des deux objets sont différentes, alors __sub__() sera appelée avant Appelez d'abord __rsub__(). En d’autres termes, si b est une sous-classe de a, l’ordre des appels est inversé.

Cela peut sembler une exception étrange, mais il y a une raison derrière cela. Lorsque vous créez une sous-classe, cela signifie que vous injectez une nouvelle logique en plus des opérations fournies par la classe parent. Ce type de logique n'a pas besoin d'être ajouté à la classe parent, sinon la classe parent remplacera facilement les opérations que la sous-classe souhaite implémenter lors de ses opérations sur la sous-classe.

Plus précisément, supposons qu'il existe une classe nommée Spam. Lorsque vous exécutez Spam() - Spam(), vous obtenez une instance de LessSpam. Ensuite, vous créez une sous-classe de spam nommée Bacon, de sorte que lorsque vous soustrayez Bacon du spam, vous obtenez VeggieSpam.

Sans la règle ci-dessus, Spam() - Bacon() donnerait LessSpam, car Spam ne sait pas que soustraire Bacon devrait donner VeggieSpam.

但是,有了上述规则,就会得到预期的结果 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培训栏目!

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