Recherche…


Méthodes Magic / Dunder

Les méthodes magiques (également appelées dbr comme abréviation de double-trait de soulignement) en Python ont un objectif similaire à la surcharge d'opérateurs dans d'autres langages. Ils permettent à une classe de définir son comportement lorsqu'elle est utilisée comme opérande dans des expressions d'opérateur unaires ou binaires. Ils servent également d'implémentations appelées par certaines fonctions intégrées.

Considérons cette implémentation de vecteurs à deux dimensions.

import math

class Vector(object):
    # instantiation
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # unary negation (-v)
    def __neg__(self):
        return Vector(-self.x, -self.y)

    # addition (v + u)
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    # subtraction (v - u)
    def __sub__(self, other):
        return self + (-other)

    # equality (v == u)
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    # abs(v)
    def __abs__(self):
        return math.hypot(self.x, self.y)

    # str(v)
    def __str__(self):
        return '<{0.x}, {0.y}>'.format(self)

    # repr(v)
    def __repr__(self):
        return 'Vector({0.x}, {0.y})'.format(self)

Il est maintenant possible d'utiliser naturellement des instances de la classe Vector dans diverses expressions.

v = Vector(1, 4)
u = Vector(2, 0)

u + v           # Vector(3, 4)
print(u + v)    # "<3, 4>" (implicit string conversion)
u - v           # Vector(1, -4)
u == v          # False
u + v == v + u  # True
abs(u + v)      # 5.0

Types de conteneur et de séquence

Il est possible d'émuler des types de conteneur, qui prennent en charge l'accès aux valeurs par clé ou par index.

Considérons cette implémentation naïve d'une liste fragmentée, qui stocke uniquement ses éléments non nuls pour conserver la mémoire.

class sparselist(object):
    def __init__(self, size):
        self.size = size
        self.data = {}
    
    # l[index]
    def __getitem__(self, index):
        if index < 0:
            index += self.size
        if index >= self.size:
            raise IndexError(index)
        try:
            return self.data[index]
        except KeyError:
            return 0.0

    # l[index] = value
    def __setitem__(self, index, value):
        self.data[index] = value

    # del l[index]
    def __delitem__(self, index):
        if index in self.data:
            del self.data[index]

    # value in l
    def __contains__(self, value):
        return value == 0.0 or value in self.data.values()

    # len(l)
    def __len__(self):
        return self.size

    # for value in l: ...
    def __iter__(self):
        return (self[i] for i in range(self.size)) # use xrange for python2

Ensuite, nous pouvons utiliser une liste sparselist comme une list régulière.

l = sparselist(10 ** 6)  # list with 1 million elements
0 in l                   # True
10 in l                  # False

l[12345] = 10            
10 in l                  # True
l[12345]                 # 10

for v in l:
    pass  # 0, 0, 0, ... 10, 0, 0 ... 0

Types appelables

class adder(object):
    def __init__(self, first):
        self.first = first

    # a(...)
    def __call__(self, second):
        return self.first + second

add2 = adder(2)
add2(1)  # 3
add2(2)  # 4

Gestion du comportement non implémenté

Si votre classe n'implémente pas d'opérateur surchargé spécifique pour les types d'arguments fournis, elle doit return NotImplemented ( notez qu'il s'agit d'une constante spéciale différente de NotImplementedError ). Cela permettra à Python de recourir à d'autres méthodes pour faire fonctionner l'opération:

Lorsque NotImplemented est renvoyé, l'interpréteur essaiera alors l'opération reflétée sur l'autre type, ou une autre solution de secours, selon l'opérateur. Si toutes les opérations tentées renvoient NotImplemented , l'interpréteur NotImplemented une exception appropriée.

Par exemple, avec x + y , si x.__add__(y) renvoie sans implémentation, y.__radd__(x) est tenté à la place.

class NotAddable(object):

    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return NotImplemented


class Addable(NotAddable):

    def __add__(self, other):
        return Addable(self.value + other.value)

    __radd__ = __add__

Comme c'est la méthode réfléchie , nous devons implémenter __add__ et __radd__ pour obtenir le comportement attendu dans tous les cas; Heureusement, comme ils font tous les deux la même chose dans cet exemple simple, nous pouvons prendre un raccourci.

Utilisé:

>>> x = NotAddable(1)
>>> y = Addable(2)
>>> x + x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'NotAddable' and 'NotAddable'
>>> y + y
<so.Addable object at 0x1095974d0>
>>> z = x + y
>>> z
<so.Addable object at 0x109597510>
>>> z.value
3

Surcharge de l'opérateur

Vous trouverez ci-dessous les opérateurs pouvant être surchargés dans les classes, ainsi que les définitions de méthode requises et un exemple de l'opérateur utilisé dans une expression.

NB L'utilisation d'un other nom de variable n'est pas obligatoire, mais est considérée comme la norme.

Opérateur Méthode Expression
+ Ajout __add__(self, other) a1 + a2
- soustraction __sub__(self, other) a1 - a2
* Multiplication __mul__(self, other) a1 * a2
@ Multiplication de matrices __matmul__(self, other) a1 @ a2 ( Python 3.5 )
/ Division __div__(self, other) a1 / a2 ( Python 2 uniquement )
/ Division __truediv__(self, other) a1 / a2 ( Python 3 )
// Division de plancher __floordiv__(self, other) a1 // a2
% Modulo / reste __mod__(self, other) a1 % a2
** puissance __pow__(self, other[, modulo]) a1 ** a2
<< Changement bit à gauche __lshift__(self, other) a1 << a2
>> Changement de bit à droite __rshift__(self, other) a1 >> a2
& Bitwise ET __and__(self, other) a1 & a2
^ Bit-bit XOR __xor__(self, other) a1 ^ a2
| (Bit à bit OU) __or__(self, other) a1 | a2
- Négation (arithmétique) __neg__(self) -a1
+ Positif __pos__(self) +a1
~ Pas binaire __invert__(self) ~a1
< Moins que __lt__(self, other) a1 < a2
<= Inférieur ou égal à __le__(self, other) a1 <= a2
== égal à __eq__(self, other) a1 == a2
!= Pas égal à __ne__(self, other) a1 != a2
> Supérieur à __gt__(self, other) a1 > a2
>= Supérieur ou égal à __ge__(self, other) a1 >= a2
[index] Opérateur d'index __getitem__(self, index) a1[index]
in opérateur In __contains__(self, other) a2 in a1
(*args, ...) Appel __call__(self, *args, **kwargs) a1(*args, **kwargs)

Le paramètre optionnel modulo pour __pow__ n'est utilisé que par la fonction intégrée pow .


Chacune des méthodes correspondant à un opérateur binaire a une méthode "droite" correspondante qui commence par __r , par exemple __radd__ :

class A:
    def __init__(self, a):
        self.a = a
    def __add__(self, other):
        return self.a + other
    def __radd__(self, other):
        print("radd")
        return other + self.a

A(1) + 2  # Out:  3
2 + A(1)  # prints radd. Out: 3

ainsi qu'une version correspondante en place, commençant par __i :

class B:
    def __init__(self, b):
        self.b = b
    def __iadd__(self, other):
        self.b += other
        print("iadd")
        return self

b = B(2)
b.b       # Out: 2
b += 1    # prints iadd
b.b       # Out: 3

Comme ces méthodes n'ont rien de particulier, de nombreuses autres parties du langage, des parties de la bibliothèque standard et même des modules tiers ajoutent des méthodes magiques, comme des méthodes pour convertir un objet en type ou vérifier les propriétés de l'objet. Par exemple, la fonction intégrée str() appelle la méthode __str__ l'objet, si elle existe. Certaines de ces utilisations sont énumérées ci-dessous.

Fonction Méthode Expression
Casting à l' int __int__(self) int(a1)
Fonction absolue __abs__(self) abs(a1)
Casting à str __str__(self) str(a1)
Casting à unicode __unicode__(self) unicode(a1) (Python 2 uniquement)
Représentation de chaîne __repr__(self) repr(a1)
Casting à bool __nonzero__(self) bool(a1)
Formatage de chaîne __format__(self, formatstr) "Hi {:abc}".format(a1)
Hachage __hash__(self) hash(a1)
Longueur __len__(self) len(a1)
Renversé __reversed__(self) reversed(a1)
Sol __floor__(self) math.floor(a1)
Plafond __ceil__(self) math.ceil(a1)

Il existe également les méthodes spéciales __enter__ et __exit__ pour les gestionnaires de contexte, et bien plus encore.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow