- Méthodes liées et décorateurs de classe
En Python, les méthodes définies à l'intérieur d'une classe peuvent être liées soit à l'instance, soit à la classe elle-même. Cette liaison détermine quel objet est implicitement passé comme premier argument lors de l'appel.
- Méthodes d'instance : Le premier paramètre est conventionnellement nommé
self. Losrqu'une instance appelle cette méthode, l'instance elle-même est passée automatiquement. - Méthodes de classe : Le premier paramètre est
cls. Elles sont appelées par la classe et reçoivent la classe comme argument implicite.
Décorateurs intégrés
Pour modifier le comportement de liaison, Python fournit des décorateurs natifs :
@classmethod: Transforme une méthode pour qu'elle reçoive la classe (cls) au lieu de l'instance. Idéal pour les constructeurs alternatifs.@staticmethod: Détache la méthode de la classe et de l'instance. Elle se comporte comme une fonction ordinaire mais réside dans l'espace de noms de la classe.@property: Permet d'accéder à une méthode comme s'il s'agissait d'un attribut de données, sans nécessiter de parenthèses lors de l'appel.
Génération d'identifiants uniques
Le module uuid est couramment utilisé pour générer des identifiants uniques. Contrairement à hashlib qui produit des hachages cryptographiques, uuid4() génère un UUID aléatoire de 128 bits.
import hashlib
import uuid
class UserSession:
def __init__(self, username, role):
self.username = username
self.role = role
self.session_id = self._generate_session_id()
def display_info(self):
print(f"Session active pour {self.username} (Rôle: {self.role})")
@classmethod
def create_admin_session(cls):
"""Constructeur alternatif pour créer une session administrateur."""
return cls(username="admin_root", role="administrator")
@staticmethod
def _generate_session_id():
"""Génère un identifiant de session unique et haché."""
raw_uuid = str(uuid.uuid4())
hasher = hashlib.sha256(raw_uuid.encode('utf-8'))
return hasher.hexdigest()
# Utilisation
admin_sess = UserSession.create_admin_session()
admin_sess.display_info()
print(f"ID de session : {admin_sess.session_id}")
print(f"Appel statique direct : {UserSession._generate_session_id()}")
- Inspection de types : isinstance et issubclass
Python offre des fonctions intégrées pour vérifier dynamiquement les relations d'héritage et d'instanciation :
isinstance(obj, classinfo): RetourneTruesiobjest une instance declassinfoou d'une de ses sous-classes.issubclass(class, classinfo): RetourneTruesiclassest une sous-classe declassinfo.
- Le mécanisme de réflexion
La réflexion permet d'inspecter et de manipuler dynamiquement les attributs et méthodes d'un objet en utilisant des chaînes de caractères. Cela repose sur quatre fonctions intégrées :
hasattr(obj, name): Vérifie si l'attributnameexiste dansobj.getattr(obj, name[, default]): Récupère la valeur de l'attribut. Si l'attribut est une méthode, elle peut être appelée ensuite. Accepte une valeur par défaut si l'attribut est absent.setattr(obj, name, value): Assigne une valeur à un attribut, ou le crée s'il n'existe pas.delattr(obj, name): Supprime l'attribut spécifié de l'objet.
class ApiClient:
def __init__(self, base_url):
self.base_url = base_url
self.timeout = 30
client = ApiClient("https://api.example.com")
# Vérification et récupération
if hasattr(client, "timeout"):
current_timeout = getattr(client, "timeout")
print(f"Timeout actuel : {current_timeout}")
# Modification dynamique
setattr(client, "retries", 3)
print(f"Nouvel attribut 'retries' : {getattr(client, 'retries')}")
# Suppression
delattr(client, "retries")
- Les méthodes magiques (Dunder methods)
Les méthodes magiques, entourées de doubles underscores (__), permettent de définir comment les objets se comportent face aux opérations intégrées de Python.
Cycle de vie et initialisation
__new__(cls, ...): Méthode statique implicite responsable de la création de l'instance. Elle est appelée avant__init__et doit retourner une instance.__init__(self, ...): Initialise l'instance créée par__new__.__del__(self): Appelée juste avant la destruction de l'objet par le ramasse-miettes.
Représentation et conversion
__str__(self): Définit la représentation lisible par un humain (utilisée parprint()). Doit retourner une chaîne de caractères.
Gestion dynamique des attributs
__getattr__(self, name): Invoquée uniquement lorsque l'attribut recherché n'est pas trouvé par les mécanismes normaux de recherche.__getattribute__(self, name): Invoquée pour tout accès à un attribut, qu'il existe ou non. Si elle est définie,__getattr__n'est appelée que si__getattribute__lève uneAttributeError.__setattr__(self, name, value): Intercepte toute assignation d'attribut. Pour éviter les boucles infinies, l'assignation interne doit passer parself.__dict__[name] = valueousuper().__setattr__.__delattr__(self, name): Intercepte la suppression d'un attribut via l'instructiondel.
Appel d'objet
__call__(self, ...): Permet à une instance d'être appelée comme une fonction (ex:obj()).
- Le patron de conception Singleton
Le Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci. C'est particulièrement utile pour des ressources partagées comme les connexions à une base de données, les pools de threads ou les configurations globales, permettant ainsi d'optimiser l'utilisation de la mémoire.
Approche 1 : Via une méthode de classe
Cette méthode utilise un attribut de classe privé pour stocker l'instance et une méthode de classe pour contrôler l'instanciation.
class DatabasePool:
_instance = None
def __init__(self, host, port):
self.host = host
self.port = port
self.connections = []
@classmethod
def get_instance(cls, host="localhost", port=5432):
if cls._instance is None:
cls._instance = cls(host, port)
return cls._instance
# Utilisation
db1 = DatabasePool.get_instance("192.168.1.10", 5432)
db2 = DatabasePool.get_instance("10.0.0.5", 3306) # Les paramètres sont ignorés car l'instance existe déjà
print(db1 is db2) # True
print(db1.host) # "192.168.1.10"
Approche 2 : Surcharge de __new__ (Recommandée)
En redéfinissant __new__, on intercepte la création de l'objet au niveau le plus bas. C'est l'approche la plus robuste car elle empêche toute instanciation multiple, même via le constructeur standard.
class AppConfig:
_instance = None
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(AppConfig, cls).__new__(cls)
return cls._instance
def __init__(self, environment="production", debug=False):
# Empêche la réinitialisation des attributs lors des appels suivants au constructeur
if not self._initialized:
self.environment = environment
self.debug = debug
self._initialized = True
cfg1 = AppConfig(environment="staging", debug=True)
cfg2 = AppConfig(environment="production", debug=False)
print(cfg1 is cfg2) # True
print(cfg1.environment) # "staging" (les paramètres de cfg2 sont ignorés)
Approche 3 : Implémentation par décorateur
Un décorateur peut envelopper la classe pour intercepter les appels au constructeur et gérer le cache de l'instance de manière externe.
def singleton_decorator(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton_decorator
class Logger:
def __init__(self, log_level="INFO"):
self.log_level = log_level
self.log_file = "app.log"
def log(self, message):
print(f"[{self.log_level}] {message}")
logger1 = Logger("DEBUG")
logger2 = Logger("ERROR")
print(logger1 is logger2) # True
print(logger1.log_level) # "DEBUG"