19. Magic Methods Avancés
__getattr__vs__getattribute__,__new__,__del__,__copy__/__deepcopy__,__instancecheck__,__subclasshook__,__set_name__,__init_subclass__.
19.1 __getattr__ vs __getattribute__
La différence fondamentale :
class Exemple:
def __init__(self):
self.x = 10
def __getattr__(self, name):
"""Appelé SEULEMENT si l'attribut n'existe pas."""
return f"{name} n'existe pas"
def __getattribute__(self, name):
"""Appelé TOUJOURS en premier."""
if name == "secret":
raise AttributeError("Interdit")
return super().__getattribute__(name)
e = Exemple()
e.x # 10 (via __getattribute__ normal)
e.y # "y n'existe pas" (via __getattr__)
# e.secret # AttributeErrorCas d’usage :
__getattr__: proxies, délégation, attributs virtuels__getattribute__: contrôle d’accès, logging, validation
Proxy par __getattr__
class Proxy:
def __init__(self, obj):
self._obj = obj
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(name)
return getattr(self._obj, name)
def __setattr__(self, name, value):
if name == "_obj":
super().__setattr__(name, value)
else:
setattr(self._obj, name, value)
class Data:
def __init__(self):
self.valeur = 42
d = Data()
p = Proxy(d)
print(p.valeur) # 42 (délégue à d)
p.valeur = 100
print(d.valeur) # 10019.2 __setattr__, __delattr__
class Validé:
def __setattr__(self, name, value):
if name == "âge" and (not isinstance(value, int) or value < 0):
raise ValueError("Âge invalide")
super().__setattr__(name, value)
def __delattr__(self, name):
if name == "âge":
raise AttributeError("Ne peut pas supprimer l'âge")
super().__delattr__(name)19.3 __new__ — constructeur réel
Appelé avant __init__. Crée l’instance. Retourne une instance.
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, valeur):
# Attention : __init__ est appelé à chaque fois
if not hasattr(self, 'initialisé'):
self.valeur = valeur
self.initialisé = TrueCas d’usage :
- Singleton (comme ci-dessus)
- Classes immuables (comme
tuple,int,str) - Pool d’objets
__new__avec héritage de types immuables
Sous-classer tuple avec __new__
class Point:
def __new__(cls, x, y):
instance = super().__new__(cls)
instance.x = x
instance.y = y
return instance
class PointTuple(tuple):
def __new__(cls, x, y):
return super().__new__(cls, (x, y))
@property
def x(self):
return self[0]
@property
def y(self):
return self[1]
p = PointTuple(3, 4)
p.x, p.y, p[0], p[1] # 3 4 3 419.4 __del__ — destructeur
Appelé lors de la garbage collection. Pas garanti d’être appelé.
class Ressource:
def __init__(self, nom):
self.nom = nom
print(f"Ouverture de {nom}")
def __del__(self):
print(f"Fermeture de {self.nom}")Ne pas utiliser pour la gestion de ressources → préférer les context managers (with).
19.5 __copy__ et __deepcopy__
import copy
class Arbre:
def __init__(self, valeur, enfants=None):
self.valeur = valeur
self.enfants = enfants or []
def __copy__(self):
"""Copie superficielle personnalisée."""
return Arbre(self.valeur, list(self.enfants))
def __deepcopy__(self, memo):
"""Copie profonde personnalisée."""
enfants = [copy.deepcopy(e, memo) for e in self.enfants]
return Arbre(copy.deepcopy(self.valeur, memo), enfants)a = Arbre(1, [Arbre(2), Arbre(3)])
b = copy.copy(a) # __copy__
c = copy.deepcopy(a) # __deepcopy__19.6 __instancecheck__ et __subclasscheck__
Personnaliser isinstance et issubclass :
class Meta(type):
def __instancecheck__(cls, instance):
print(f"isinstance check pour {instance}")
return type.__instancecheck__(cls, instance)
def __subclasscheck__(cls, subclass):
print(f"issubclass check pour {subclass}")
return type.__subclasscheck__(cls, subclass)
class MaClasse(metaclass=Meta):
pass
isinstance(MaClasse(), MaClasse) # True après logsAvec Protocol et __subclasshook__ :
from typing import Protocol
class Volant(Protocol):
def voler(self) -> None: ...
@classmethod
def __subclasshook__(cls, other):
for c in other.__mro__:
if "voler" in c.__dict__:
return True
return NotImplemented
class Oiseau:
def voler(self):
print("Vole")
issubclass(Oiseau, Volant) # True (via __subclasshook__)19.7 __set_name__ — connaître le nom de l’attribut
Vue en leçon 10b. Rappel :
class Descripteur:
def __set_name__(self, owner, name):
self._name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self._name)
def __set__(self, obj, value):
setattr(obj, self._name, value)
class Point:
x = Descripteur()
y = Descripteur()19.8 __init_subclass__ — hook à l’héritage
Vue en leçons 10b et 18. Rappel :
class PluginBase:
registry = {}
def __init_subclass__(cls, tag=None, **kwargs):
super().__init_subclass__(**kwargs)
if tag:
PluginBase.registry[tag] = cls
class Plugin(PluginBase, tag="maths"):
pass19.9 __class_getitem__ — subscriptable sur la classe
class Pile:
def __class_getitem__(cls, item):
return f"Pile[{item}]"
Pile[int] # "Pile[int]"19.10 __length_hint__ — estimation de taille
class Itérable:
def __init__(self, n):
self.n = n
def __iter__(self):
return iter(range(self.n))
def __length_hint__(self):
return self.n # estimation, pas obligé d'être exact
list(Itérable(5)) # [0, 1, 2, 3, 4]19.11 __reversed__ — inversion personnalisée
class Plage:
def __init__(self, start, stop):
self.range = range(start, stop)
def __iter__(self):
return iter(self.range)
def __reversed__(self):
return iter(reversed(self.range))
list(reversed(Plage(0, 5))) # [4, 3, 2, 1, 0]19.12 __aiter__, __anext__, __aenter__, __aexit__
class AsyncContext:
async def __aenter__(self):
print("Entrée async")
return self
async def __aexit__(self, *args):
print("Sortie async")
async def main():
async with AsyncContext() as ctx:
...19.13 Tableau récapitulatif
| Méthode | Déclencheur | Usage |
|---|---|---|
__new__ | cls(args) | Contrôle de création d’instance |
__init__ | Après __new__ | Initialisation |
__del__ | GC | Nettoyage (ne pas compter dessus) |
__getattr__ | Attribut manquant | Proxy, délégation |
__getattribute__ | Tout accès attribut | Contrôle d’accès |
__setattr__ | obj.x = val | Validation |
__delattr__ | del obj.x | Protection |
__copy__ | copy.copy(obj) | Copie superficielle |
__deepcopy__ | copy.deepcopy(obj) | Copie profonde |
__instancecheck__ | isinstance(obj, cls) | Logique de type personnalisée |
__subclasscheck__ | issubclass(sub, cls) | Héritage virtuel |
__set_name__ | Création de classe | Nom du descripteur |
__class_getitem__ | Cls[T] | Génériques |