4.4. Obtenir des références objet avec getattr

Vous savez déjà que les fonctions Python sont des objets. Ce que vous ne savez pas, c’est que vous pouvez obtenir une référence à une fonction sans connaître son nom avant l’exécution, à l’aide de la fonction getattr.

Exemple 4.10. Présentation de getattr

>>> li = ["Larry", "Curly"]
>>> li.pop                       1
<built-in method pop of list object at 010DF884>
>>> getattr(li, "pop")           2
<built-in method pop of list object at 010DF884>
>>> getattr(li, "append")("Moe") 3
>>> li
["Larry", "Curly", "Moe"]
>>> getattr({}, "clear")         4
<built-in method clear of dictionary object at 00F113D4>
>>> getattr((), "pop")           5
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'pop'
1 Ceci retourne une référence à la méthode pop de la liste. Ce n’est pas un appel à la méthode pop, un appel se ferait par li.pop(). C’est la méthode elle-même.
2 Ceci retourne également une référence à la méthode pop, mais cette fois ci le nom de la méthode est passé comme argument de la fonction getattr. getattr est une fonction prédéfinie extrèmement utile qui retourne n’importe quel attribut de n’importe quel objet. Ici, l’objet est une liste et l’attribut est la méthode pop.
3 Au cas où vous ne voyez pas à quel point c’est utile, regardez ceci : la valeur de retour de getattr est la méthode, que vous pouvez alors appeler comme si vous aviez tapé li.append("Moe") directement. Mais vous n’avez pas appelé la fonction directement, vous avez passé le nom de la fonction comme paramètre sous forme de chaîne.
4 getattr fonctionne aussi avec les dictionnaires.
5 En théorie, getattr pourrait fonctionner avec les tuples, mais les tuples n’ont pas de méthodes et getattr déclenchera une exception quel que soit le nom d’attribut que vous lui donnez.

4.4.1. getattr et les modules

getattr n’est pas seulement fait pour les types prédéfinis, il fonctionne aussi avec les modules.

Exemple 4.11. getattr dans apihelper.py

>>> import odbchelper
>>> odbchelper.buildConnectionString             1
<function buildConnectionString at 00D18DD4>
>>> getattr(odbchelper, "buildConnectionString") 2
<function buildConnectionString at 00D18DD4>
>>> object = odbchelper
>>> method = "buildConnectionString"
>>> getattr(object, method)                      3
<function buildConnectionString at 00D18DD4>
>>> type(getattr(object, method))                4
<type 'function'>
>>> import types
>>> type(getattr(object, method)) == types.FunctionType
True
>>> callable(getattr(object, method))            5
True
1 Ceci retourne une référence à la fonction buildConnectionString du module odbchelper, que nous avons étudié au Chapitre 2, Votre premier programme Python. (L’adresse hexadécimale qui s’affiche est spécifique à ma machine, votre sortie sera différente.)
2 A l’aide de getattr, nous pouvons obtenir la même référence à la même fonction. En général, getattr(objet, "attribut") est équivalent à objet.attribut. Si objet est un module, alors attribut peut être toute chose définie dans le module : une fonction, une classe ou une variable globale.
3 Voici ce que nous utilisons dans la fonction info. object est passé en argument à la fonction, method est une chaîne, le nom de la méthode ou de la fonction.
4 Dans ce cas, method est le nom d’une fonction, ce que nous prouvons en obtenant son type.
5 Puisque method est une fonction, elle est callable (appelable).

4.4.2. getattr comme sélecteur

Une utilisation usuelle de getattr est dans le rôle de sélecteur. Par exemple, si vous avez un programme qui peut produire des données dans différents formats, vous pouvez définir des fonctions différentes pour chaque format de sortie et utiliser une fonction de sélection pour appeler celle qui convient.

Par exemple, imaginons un programme qui affiche des statistiques de consultations d'un site Web aux formats HTML, XML et texte simple. Le choix du format de sortie peut être spécifié depuis la ligne de commande ou stocké dans un fichier de configuration. Un module statsout définit trois fonctions, output_html, output_xml et output_text. Ensuite, le programme principal définit une fonction de sortie unique, comme ceci :

Exemple 4.12. Création d'un sélecteur avec getattr


import statsout

def output(data, format="text"):                              1
    output_function = getattr(statsout, "output_%s" % format) 2
    return output_function(data)                              3
1 La fonction output prend un argument obligatoire, data et un argument optionnel, format. Si format n'est pas spécifié, il a la valeur par défaut text ce qui appelera la fonction de sortie en texte simple.
2 Nous concaténons l'argument format à "output_" pour produire un nom de fonction et allons chercher cette fonction dans le module statsout. Cela nous permet d'étendre simplement le programme plus tard pour supporter d'autres formats de sortie, sans changer la fonction de sélection. Il suffit d'ajouter une autre fonction à statsout nommée, par exemple, output_pdf et de passer "pdf" comme format à la fonction output.
3 Maintenant, nous pouvons simplement appeler la fonction de sortie comme toute autre fonction. La variable output_function est une référence à la fonction appropriée du module statsout.

Avez vous vu le problème dans l'exemple précédent ? Il y a un couplage très lâche entre chaînes et fonctions et il n'y a aucune vérification d'erreur. Que se passe-t-il si l'utilisateur passe un format pour lequel aucune fonction correspondante n'est définie dans le module statsout? Et bien, getattr retournera None, qui sera assigné à output_function au lieu d'une fonction valide et à la ligne suivante, qui tente d'appeler cette fonction inexistante, plantera et déclenchera une exception. C'est un problème.

Heureusement getattr prend un troisième argument optionnel, une valeurpar défaut.

Exemple 4.13. Valeurs par défaut de getattr


import statsout

def output(data, format="text"):
    output_function = getattr(statsout, "output_%s" % format, statsout.output_text)
    return output_function(data) 1
1 Cet appel de fonction est assuré de fonctionner puisque nous avons ajouté un troisième argument à l'appel à getattr. Le troisième argument est une valeur par défaut qui est retournée si l'attribut ou la méthode spécifié par le second argument n'est pas trouvé.

Comme vous pouvez le voir, getattr est très puissant. C'est le coeur même de l'introspection et vous en verrez des exemples encore plus puissants dans des prochains chapitres.