Python Language
Surcharge
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 renvoientNotImplemented
, l'interpréteurNotImplemented
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.