En Python, la réflexion (ou introspection) permet d'inspecter et de manipuler dynamiquement les entités du langage à l'exécution. Cette capacité est particulièrement utile lorsque les identifiants des attributs, méthodes ou fonctions ne sont connus qu'au moment de l'exécution, sous forme de chaînes de caractères. Elle constitue le fondement des interfaces dynamiques, des routeurs HTTP et des architectures orientées plugins.
- Manipulation dynamique des instances
Les primitives intégrées getattr(), hasattr(), setattr() et delattr() forment l'API principale de cette approche. Elles résolvent une chaîne de caractères vers un pointeur mémoire valide au sein de l'objet cible, permettant ainsi une interrogation flexible de l'espace de noms.
Récupération et invocation ciblée
L'utilisation de getattr() permet de traverser les membres d'une instance sans connaître l'identifiant à l'avance. Si la cible correspond à une méthode, l'appelant reçoit une référence appelable directement exécutable.
class Client:
def __init__(self, identifiant, score):
self.ref = identifiant
self.niveau = score
def generer_etiquette(self):
return f"{self.ref}_{self.niveau}"
instance = Client("user_99", 42)
valeur = getattr(instance, "ref")
print(valeur) # user_99
fonction_dyn = getattr(instance, "generer_etiquette")
resultat = fonction_dyn()
print(resultat) # user_99_42
Vérification d'existence et mutation
Pour éviter les exceptions AttributeError, hasattr() valide la présence du membre. L'injection et la suppression s'effectuent via setattr() et delattr(), modifiant directement le dictionnaire interne de l'instance.
entite = Client("dev_ops", 3)
if hasattr(entite, "niveau"):
print("Attribut valide détecté")
setattr(entite, "zone", "eu-west-2")
print(entite.zone) # eu-west-2
delattr(entite, "zone")
# Un accès ultérieur à entite.zone lèverait une AttributeError
- Introspection au niveau des modules
Les modules Python étant eux-mêmes des objets, les mêmes mécanismes s'appliquent à l'espace de noms global d'un fichier source. Cette technique permet de charger des constantes, des routinnes ou des constructeurs sans importation explicite préalable, ce qui optimise les chargements conditionnels.
# Fichier: endpoints.py
# API_KEY = "sk_123"
# def calculer(): return "OK"
# class Processeur: pass
import endpoints
constante = getattr(endpoints, "API_KEY")
print(constante) # sk_123
routine = getattr(endpoints, "calculer")
print(routine()) # OK
constructeur = getattr(endpoints, "Processeur")
objet = constructeur()
- Architecture par dispatching dynamique
Dans les applications web, il est courant de mapper une URL à une fonction spécifique. Remplacer une cascade de conditions if/elif par un routage réflexif simplifie considérablement la maintenance et l'extension du code.
L'implémentation suivante intercepte une entrée utilisateur, vérifie son existence dans le module cible, et délègue l'exécution si la ressource est disponible, simulant le comportement d'un framework léger.
import endpoints
def routeur(requete):
if hasattr(endpoints, requete):
cible = getattr(endpoints, requete)
if callable(cible):
return cible()
return "Erreur 404"
# Simulation d'appels
print(routeur("calculer")) # OK
print(routeur("inexistant")) # Erreur 404