IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Plongez au coeur de Python ,De débutant à expert


précédentsommairesuivant

IV. Le pouvoir de l'introspection

Ce chapitre traite d'une des forces de Python : l'introspection. Comme vous le savez, tout est objet dans Python, l'introspection consiste à considérer des modules et des fonctions en mémoire comme des objets, à obtenir des informations de leur part et à les manipuler. Au cours du chapitre, nous définirons des fonctions sans nom, nous appelerons des fonctions avec les arguments dans le désordre et nous référencerons des fonctions dont nous ne connaissons même pas le nom à l'avance.

IV-A. Plonger

Voici un programme Python complet et fonctionnel. Vous devriez en comprendre une grande partie rien qu'en le lisant. Les lignes numérotées illustrent des concepts traités dans Chapitre 2, Votre premier programme Python. Ne vous inquiétez pas si le reste du code a l'air intimidant, vous en apprendrez tous les aspects au cours de ce chapitre.

Exemple 4.1. apihelper.py

Si vous ne l'avez pas déjà fait,vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.

 
Sélectionnez
def info(object, spacing=10, collapse=1): ***1*** ***2*** ***3***
    """Print methods and doc strings.
    
    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

if __name__ == "__main__":                ***4*** ***5***
    print info.__doc__

***1*** Ce module a une fonction, info. Selon sa déclaration de fonction, elle prend trois paramètres : object, spacing et collapse. Les deux derniers sont en fait des paramètres optionnels comme nous le verrons bientôt.
***2*** La fonction info a une doc string multi-lignes qui décrit succintement son usage. Notez qu'aucune valeur de retour n'est mentionnée, cette fonction sera employée uniquement pour son effet, pas sa valeur.
***3*** Le code à l'interieur de la fonction est indenté.
***4*** L'astuce if __name__ permet à ce programme de faire quelque chose d'utile lorsqu'il est exécuté tout seul sans intérférence avec son usage comme module pour d'autres programmes. Dans ce cas, le programme affiche simplement la doc string de la fonction info.
***5*** L'instruction if utilise == pour la comparaison et ne nécessite pas de parenthèses.

La fonction info est conçue pour être utilisée par vous, le programmeur, lorsque vous travaillez dans l'IDE Python. Elle prend n'importe quel objet qui a des fonctions ou des méthodes (comme un module, qui a des fonction, ou une liste, qui a des méthodes) et affiche les fonctions et leur doc string.

Exemple 4.2. Exemple d'utilisation de apihelper.py

 
Sélectionnez
>>> from apihelper import info
>>> li = []
>>> info(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of value
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1

Par défaut, la sortie est formatée pour être facilement lisible. Les doc string multi-lignes sont combinées en une seule longue ligne, mais cette option peut être changée en spécifiant 0 pour l'argument collapse. Si les noms de fonction font plus de 10 caractères, vous pouvez spécifier une valeur plus grande pour l'argument spacing, pour faciliter la lecture.

Exemple 4.3. Utilisation avancée de apihelper.py

 
Sélectionnez
>>> import odbchelper
>>> info(odbchelper)
buildConnectionString Build a connection string from a dictionary Returns string.
>>> info(odbchelper, 30)
buildConnectionString          Build a connection string from a dictionary Returns string.
>>> info(odbchelper, 30, 0)
buildConnectionString          Build a connection string from a dictionary
    
    Returns string.

IV-B. Arguments optionnels et nommés

Python permet aux arguments de fonction d'avoir une valeur par défaut, si la fonction est appelée sans l'argument il a la valeur par défaut. De plus, les arguments peuvent être donnés dans n'importe quel ordre en utilisant les arguments nommés. Les procédures stockées de Transact/SQL sous SQL Server peuvent faire la même chose, si vous êtes un as des scripts sous SQL Server, vous pouvez survoler cette partie.

Voici un exemple de info, une fonction avec deux arguments optionnels :

 
Sélectionnez
def info(object, spacing=10, collapse=1):

spacing et collapse sont optionnels car ils ont des valeurs par défaut définies. object est obligatoire car il n'a pas de valeur par défaut. Si info est appelé avec un seul argument, spacing prend pour valeur 10 et collapse la valeur 1. Si info est appelé avec deux arguments, collapse prend encore pour valeur 1.

Imaginez que vous vouliez spécifier une valeur pour collapse mais garder la valeur par défaut pour spacing. Dans la plupart des langages, vous ne pouvez pas le faire, vous auriez à spécifier les trois arguments. Mais en Python, les arguments peuvent être spécifiés par leur nom, dans n'importe quel ordre.

Exemple 4.4. Appels de info autorisés

 
Sélectionnez
info(odbchelper)                	***1***    
info(odbchelper, 12)            	***2***    
info(odbchelper, collapse=0)    	***3***    
info(spacing=15, object=odbchelper)	***4***

***1*** Avec un seul argument, spacing prend pour valeur 10 et collapse 1. ***2*** Avec deux arguments, collapse prend pour valeur 1. ***3*** Ici, vous nommez l'argument collapse explicitement et spécifiez sa valeur. spacing prend la valeur par défaut 10. ***4*** Les arguments obligatoires (comme object, qui n'a pas de valeurs par défaut) peuvent aussi être nommés et les arguments nommés peuvent apparaître dans n'importe quel ordre.

Cela a l'air confus jusqu'à que vous réalisiez que les arguments sont tout simplement un dictionnaire. La manière «normale» d'appeler les fonctions sans le nom des arguments est en fait un raccourci dans lequel Python fait correspondre les valeurs avec le nom des arguments dans l'ordre dans lequel ils sont spécifiés par la déclaration de fonction. Dans la plupart des cas, vous appellerez le fonctions de la manière «normale», mais vous aurez toujours cette souplesse pour les autres cas.

La seule chose que vous avez à faire pour appeler une fonction est de spécifier une valeur (d'une manière ou d'une autre) pour chaque argument obligatoire, la manière et l'ordre dans lequel vous le faites ne dépendent que de vous.

Pour en savoir plus

IV-C. Utilisation de type, str, dir et autres fonction prédéfinies

Python a un petit ensemble de fonctions prédéfinies très utiles. Toutes les autres fonctions sont réparties dans des modules. C'est une décision de conception consciente, afin d'éviter au langage de trop grossir comme d'autres langages de script (au hasard, Visual Basic).

IV-C-1. La fonction type

La fonction type retourne le type de données d'un objet quelconque. Les types possibles sont répertoriés dans le module types. C'est utile pour les fonctions capables de gérer plusieurs types de données.

Exemple 4.5. Présentation de type
 
Sélectionnez
>>> type(1)           ***1***
<type 'int'>
>>> li = []
>>> type(li)          ***2***
<type 'list'>
>>> import odbchelper
>>> type(odbchelper)  ***3***
<type 'module'>
>>> import types      ***4***
>>> type(odbchelper) == types.ModuleType
True

***1*** type prend n'importe quel argument et retourne son type de données. Je dis bien n'importe lequel : entiers, chaînes, listes, dictionnaires, tuples, fonctions, classes, modules et même types.
***2*** type peut prendre une variable et retourne son type de données.
***3*** type fonctionne aussi avec les modules.
***4*** Vous pouvez utiliser les constantes du module types pour comparer les types des objets. C'est ce que fait la fonction info, comme nous le verrons bientôt.

IV-C-2. La fonction str

La fonction str convertit des données en chaîne. Tous les types de données peuvent être convertis en chaîne.

Exemple 4.6. Présentation de str
 
Sélectionnez
>>> str(1)          ***1***
'1'
>>> horsemen = ['war', 'pestilence', 'famine']
>>> horsemen
['war', 'pestilence', 'famine']
>>> horsemen.append('Powerbuilder')
>>> str(horsemen)   ***2***
"['war', 'pestilence', 'famine', 'Powerbuilder']"
>>> str(odbchelper) ***3***
"<module 'odbchelper' from 'c:\\docbook\\dip\\py\\odbchelper.py'>"
>>> str(None)       ***4***
'None'

***1*** Pour des types simples comme les entiers, il semble normal que str fonctionne, presque tous les langages ont une fonction de conversion d'entier en chaîne.
***2*** Cependant, str fonctionne pour les objets de tout type. Ici, avec une liste que nous avons construit petit à petit.
***3*** str fonctionne aussi pour les modules. Notez que la représentation en chaîne du module comprend le chemin du module sur le disque, la votre sera donc différente.
***4*** Un aspect subtil mais important du comportement de str est qu'elle fonctionne pour None, la valeur nulle de Python. Elle retourne la chaîne 'None'. Nous utiliserons cela à notre avantage dans la fonction info, comme nous le verrons bientôt.

Au coeur de notre fonction info, il y a la puissante fonction dir. dir retourne une liste des attributs et méthodes de n'importe quel objet : module, fonction, chaîne, liste, dictionnaire... à peu près tout.

Exemple 4.7. Introducing dir
 
Sélectionnez
>>> li = []
>>> dir(li)           ***1***
['append', 'count', 'extend', 'index', 'insert',
'pop', 'remove', 'reverse', 'sort']
>>> d = {}
>>> dir(d)           ***2***
['clear', 'copy', 'get', 'has_key', 'items', 'keys', 'setdefault', 'update', 'values']
>>> import odbchelper
>>> dir(odbchelper)   ***3***
['__builtins__', '__doc__', '__file__', '__name__', 'buildConnectionString']

***1*** li est une liste, donc dir(li) retourne la liste de toutes les méthodes de liste. Notez que la liste retournée comprend les noms des méthodes sous forme de chaînes, pas les méthoses elles-mêmes.
***2*** d est un dictionnaire, donc dir(d) retourne la liste des noms de méthodes de dictionnaire. Au moins l'un de ces noms, keys, devrait être familier.
***3*** C'est ici que cela devient vraiment intéressant. odbchelper est un module, donc dir(odbchelper) retourne la liste de toutes les choses définies dans le module, y compris le attributs prédéfinis comme __name__ et __doc__ et tout attribut et méthode que vous définissez. Dans ce cas, odbchelper a une seule méthode définie par l'utilisateur, la fonction buildConnectionString que nous avons étudiée au Chapitre 2.

Enfin, la fonction callable prend n'importe quel objet et retourne True si l'objet peut être appelé, sinon False.

Les objets appelables sont les fonctions, les méthodes de classes ainsi que les classes elles-mêmes (nous verrons les classes au prochain chapitre).

Exemple 4.8. Présentation de callable
 
Sélectionnez
>>> import string
>>> string.punctuation           ***1***
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
>>> string.join                  ***2***
<function join at 00C55A7C>
>>> callable(string.punctuation) ***3***
False
>>> callable(string.join)        ***4***
True
>>> print string.join.__doc__    ***5***
join(list [,sep]) -> string

    Return a string composed of the words in list, with
    intervening occurrences of sep.  The default separator is a
    single space.

    (joinfields and join are synonymous)

***1*** Les fonctions du module string sont dépréciées (bien que beaucoup de gens utilisent encore la fonction join), mais le module comprend un grand nombre de constantes utiles comme ce string.punctuation, qui comprend tous les caractères de ponctuation standards.
***2*** string.join est une fonction qui effectue la jointure d'une liste de chaînes.
***3*** string.punctuation n'est pas appelable, c'est une chaîne. (Une chaîne a des méthodes appelables, mais elle n'est pas elle-même appelable.)
***4*** string.join est appelable, c'est une fonction qui prend deux arguments.
***5*** Tout objet appelable peut avoir une doc string. En utilisant la fonction callable sur chacun des attributs d'un objet, nous pouvons déterminer les attributs qui nous intéressent (méthodes, fonctions et classes) et ce que nous voulons ignorer (constantes etc.) sans savoir quoi que ce soit des objets à l'avance.

IV-C-3. Fonctions prédéfinies

type, str, dir et toutes les autres fonctions prédéfinies de Python sont regroupés dans un module spécial appelé __builtin__. (Il y a deux caractères de soulignement avant et deux après.) Pour vous aider, vous pouvez imaginer que Python exécute automatiquement from __builtin__ import * au démarrage, ce qui importe toutes les fonctions prédéfinies (built-in) dans l'espace de noms pour que vous puissiez les utiliser directement.

L'avantage d'y penser de cette manière est que vous pouvez accéder à toutes les fonctions et attributs prédéfinis de manière groupée en obtenant des informations sur le module __builtin__. Et devinez quoi, nous avons une fonction pour ça, elle s'appelle info. Essayez vous-même et parcourez la liste maintenant, nous examinerons certaines des fonctions les plus importantes plus tard (certaines des classes d'erreur prédéfinies, comme AttributeError, devraient avoir l'air familier).

Exemple 4.9. Attributs et fonctions prédéfinis
 
Sélectionnez
>>> from apihelper import info
>>> import __builtin__
>>> info(__builtin__, 20)
ArithmeticError      Base class for arithmetic errors.
AssertionError       Assertion failed.
AttributeError       Attribute not found.
EOFError             Read beyond end of file.
EnvironmentError     Base class for I/O related errors.
Exception            Common base class for all exceptions.
FloatingPointError   Floating point operation failed.
IOError              I/O operation failed.

[...snip...]

Python est fourni avec d'excellent manuels de référence que vous devriez parcourir de manière exhaustive pour apprendre tous les modules que Python offre. Mais alors que dans la plupart des langages vous auriez à vous référer constamment aux manuels (ou aux pages man, ou pire, à MSDN) pour vous rappeler l'usage de ces modules, Python est en grande partie auto-documenté.

Pour en savoir plus sur les fonctions prédéfinies

IV-D. 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.

IV-D-1. Exemple 4.10. Présentation de getattr

 
Sélectionnez
>>> 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.

IV-D-2. 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
 
Sélectionnez
>>> 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).

IV-D-3. 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
 
Sélectionnez
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
 
Sélectionnez
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.

IV-E. Filtrage de listes

Comme vous le savez, Python a des moyens puissant de mutation d'une liste en une autre, au moyen des list comprehensions (Section «Mutation de listes»). Cela peut être associé à un mécanisme de filtrage par lequel certains éléments sont modifiés alors que d'autres sont totalement ignorés.

Voici la syntaxe du filtrage de liste :

 
Sélectionnez
[mapping-expression for element in source-list if filter-expression]

C'est une extension des list comprehensions que vous connaissez et appréciez. Les deux premiers tiers sont identiques, la dernière partie, commençant par le if, est l'expression de filtrage. Une expression de filtrage peut être n'importe quelle expression qui s'évalue en vrai ou faux (ce qui en Python peut être presque tout). Tout élément pour lequel l'expression de filtrage s'évalue à vrai sera inclu dans la liste à transformer. Tous les autres éléments seront ignorés, il ne passeront jamais par l'expression de mutation et ne seront pas inclus dans la liste retournée.

Exemple 4.14. Présentation du filtrage de liste

 
Sélectionnez
>>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"]
>>> [elem for elem in li if len(elem) > 1]       ***1***
['mpilgrim', 'foo']
>>> [elem for elem in li if elem != "b"]         ***2***
['a', 'mpilgrim', 'foo', 'c', 'd', 'd']
>>> [elem for elem in li if li.count(elem) == 1] ***3***
['a', 'mpilgrim', 'foo', 'c']

***1*** L'expression de mutation est ici très simple (elle retourne juste la valeur de chaque élément), observez plutôt attentivement l'expression de filtrage. Au fur et à mesure que Python parcours la liste, il soumet chaque élément à l'expression de filtrage, si l'expression s'évalue à vrai, l'élément passe par l'expression de mutation et le résultat est inclu dans la liste de résultat. Ici, on filtre toutes les chaînes d'un seul caractère, il ne reste donc que les chaînes plus longues.
***2*** Ici, on filtre une valeur spécifique : b. Notez que cela filtre toutes les occurences de b, puisqu'à chaque fois qu'il apparaît, l'expression de filtrage s'évaluera à faux.
***3*** count est une méthode de listes qui retourne le nombre d'occurences d'une valeur dans la liste. On pourrait penser que ce filtre élimine les doublons de la liste, retournant une liste contenant seulement un exemplaire de chaque valeur. Mais en fait, les valeurs qui apparaissent deux fois dans la liste initiale (ici b et d) sont totalement éliminées. Il y a des moyens de supprimer les doublons d'une liste mais le filtrage n'est pas la solution.

Revenons à cette ligne de apihelper.py:

 
Sélectionnez
    methodList = [method for method in dir(object) if callable(getattr(object, method))]

Cela à l'air complexe et ça l'est, mais la structure de base est la même. L'expression complète renvoie une liste qui est assignée à la variable methodList. La première moitié de l'expression est la mutation de liste. L'expression de mutation est une expression d'identité, elle retourne la valeur de chaque élément. dir(object) retourne une liste des attributs et méthodes de object, c'est à cette liste que vous appliquez la mutation. La seule nouveauté est l'expression après le if.

L'expression de filtrage à l'air impressionant, mais elle n'est pas si terrible. Vous connaissez déjà callable, getattr et in. Comme vous l'avez vu dans la section précédente, l'expression getattr(object, method) retourne un objet fonction si object est un module et si method est le nom d'une fonction de ce module.

Donc, cette expression prend un objet, appelé object, obtient une liste des noms de ses attributs, méthodes, fonctions et quelques autres choses, puis filtre cette liste pour éliminer ce qui ne nous intéresse pas. Cette élimination se fait en prenant le nom de chaque attribut/méthode/fonction et en obtenant une référence vers l'objet véritable, grâce à la fonction getattr. On vérifie alors si cet objet est appelable, ce qui sera le cas pour toutes les méthodes et fonctions prédéfinies (comme la méthode pop d'une liste) ou définies par l'utilisateur ( comme la fonction buildConnectionString du module odbchelper). Nous ne nous intéressons pas aux autres attributs, comme l'attribut __name__ qui existe pour tout module.

Pour en savoir plus sur le filtrage de liste

IV-F. Particularités de and et or

En Python, and et or appliquent la logique booléenne comme vous pourriez l'attendre, mais ils ne retournent pas de valeurs booléennes, ils retournent une des valeurs comparées.

Exemple 4.15. Présentation de and

 
Sélectionnez
>>> 'a' and 'b'         ***1***
'b'
>>> '' and 'b'          ***2***
''
>>> 'a' and 'b' and 'c' ***3***
'c'

***1*** Lorsqu'on utilise and les valeurs sont évaluées dans un contexte booléen de gauche à droite. 0, '', [], (), {} et None valent faux dans ce contexte, tout le reste vaut vrai. (1) [1]Si toutes les valeurs valent vrai dans un contexte booléen, and retourne la dernière valeur. Ici and évalue 'a', qui vaut vrai, puis 'b', qui vaut vrai et retourne 'b'.
***2*** Si une des valeurs vaut faux and retourne la première valeur fausse. Ici '' est la première valeur fausse.
***3*** Toutes les valeurs sont vrai, donc and retourne la dernière valeur, 'c'.

Exemple 4.16. Présentation de or

 
Sélectionnez
>>> 'a' or 'b'          ***1***
'a'
>>> '' or 'b'           ***2***
'b'
>>> '' or [] or {}      ***3***
{}
>>> def sidefx():
...     print "in sidefx()"
...     return 1
>>> 'a' or sidefx()     ***4***
'a'

***1*** Lorsqu'on utilise or, les valeurs sont évaluées dans un contexte booléen de gauche à droite, comme pour and. Si une des valeurs vaut vrai, or la retourne immédiatement. Dans ce cas 'a' est la première valeur vraie. ***2*** or évalue '', qui vaut faux, puis 'b', qui vaut vrai et retourne 'b'. ***3*** Si toutes les valeurs valent faux, or retourne la dernière valeur. or évalue '', qui vaut faux, puis [], qui vaut faux, puis {}, qui vaut faux et retourne {}. ***4*** Notez que or continue l'évaluation seulement jusqu'à ce qu'il trouve une valeur vraie, le reste est ignoré. C'est important si certaines valeurs peuvent avoir un effet de bord. Ici, la fonction sidefx n'est jamais appelée, car or évalue 'a', qui vaut vrai et retourne 'a' immédiatement.

Si vous êtes un programmeur C, vous êtes certainement familier de l'expression ternaire bool ? a : b, qui s'évalue à a si bool vaut vrai et à b dans le cas contraire. Le fonctionnement de and et or en Python vous permet d'accomplir la même chose.

IV-F-3. Utilisation de l'astuce and-or

 
Sélectionnez
>>> a = "first"
>>> b = "second"
>>> 1 and a or b ***1***
'first'
>>> 0 and a or b ***2***
'second'

***1*** Cette syntaxe ressemble à celle de l'expression ternaire bool ? a : b de C. L'expression est évaluée de gauche à droite, donc le and est évalué en premier. 1 and 'first' s'évalue à 'first', puis 'first' or 'second' s'évalue à 'first'. ***2*** 0 and 'first' s'évalue à 0, puis 0 or 'second' s'évalue à 'second'.

Cependant, puisque cette expression en Python est simplement de la logique booléenne et non un dispositif spécial du langage, il y a une différence très, très importante entre l'astuce and-or en Python et la syntaxe bool ? a : b en C. Si a vaut faux, l'expression ne fonctionnera pas comme vous vous y attendez. (Vous devinez que cela m'a déjà joué des tours. Et plus d'une fois !)

Exemple 4.18. Quand l'astuce and-or échoue
 
Sélectionnez
>>> a = ""
>>> b = "second"
>>> 1 and a or b         ***1***
'second'

***1*** Puisque a est une chaîne vide, ce que Python évalue à faux dans un contexte booléen, 1 and '' s'évalue à '', puis '' or 'second' s'évalue à 'second'. Zut ! Ce n'est pas ce que nous voulions.

L'astuce and-or, bool and a or b, ne fonctionne pas comme l'expression ternaire de C bool ? a : b quand a s'évalue à faux dans un contexte booléen.

La véritable astuce cachée derrière l'astuce and-or, c'est de s'assurer que a ne vaut jamais faux. Une manière habituelle de le faire est de changer a en [a] et b en [b] et de prendre le premier élément de la liste retournée, qui sera soit a soit b.

Exemple 4.19. L'astuce and-or en toute sécurité
 
Sélectionnez
>>> a = ""
>>> b = "second"
>>> (1 and [a] or [b])[0] ***1***
''

***1*** Puisque [a] est une liste non-vide, il ne vaut jamais faux. Même si a est 0 ou '' ou une autre valeur fausse, la liste [a] vaut vrai puisqu'elle a un élément.

On peut penser que cette astuce apporte plus de complication que d'avantages. Après tout, on peut obtenir le même résultat avec une instruction if, alors pourquoi s'embarasser de tout ces problèmes ? Mais dans de nombreux cas, le choix se fait entre deux valeurs constantes et donc vous pouvez utiliser cette syntaxe plus simple sans vous inquiéter puisque vous savez que a vaudra toujours vrai. Et même si vous devez utiliser la version sûre plus complexe, il y a parfois de bonnes raisons de le faire, il y a des cas en Python où les instructions if ne sont pas autorisées, comme dans les fonctions lambda.

Pour en savoir plus sur l'astuce and-or

Footnotes

Presque tout, en fait. Par défaut, les instances de classes valent vrai dans un contexte booléen mais vous pouvez définir des méthodes spéciales de votre classe pour faire qu'une instance vale faux. Vous apprendrez tout sur les classes et les méthodes spéciales au Chapitre 5.

IV-G. Utiliser des fonctions lambda

Python permet une syntaxe intéressante qui vous laisse définir des mini-fonctions d'une ligne à la volée. Empruntées à Lisp, ces fonctions dites lambda peuvent être employées partout où une fonction est nécéssaire.

Exemple 4.20. Présentation des fonctions lambda

 
Sélectionnez
>>> def f(x):
...     return x*2
...     
>>> f(3)
6
>>> g = lambda x: x*2  ***1***
>>> g(3)
6
>>> (lambda x: x*2)(3) ***2***
6

***1*** Voici une fonction lambda qui fait la même chose que la fonction ordinaire précédente. Notez la syntaxe condensée : il n'y a pas de parenthèses autour de la liste d'arguments et le mot-clé return est manquant (il est implicite, la fonction complète ne pouvant être qu'une seule expression). Remarquez aussi que la fonction n'a pas de nom, mais qu'elle peut être appelée à travers la variable à laquelle elle est assignée. ***2*** Vous pouvez utiliser une fonction lambda sans l'assigner à une variable. Ce n'est pas forcément très utile, mais cela démontre qu'une fonction lambda est simplement une fonction en ligne.

Plus généralement, une fonction lambda est une fonction qui prend un nombre quelconque d'arguments (y compris des arguments optionnels) et retourne la valeur d'une expression unique. Les fonctions lambda ne peuvent pas contenir de commandes et elles ne peuvent contenir plus d'une expression. N'essayez pas de mettre trop de choses dans une fonction lambda, si vous avez besoin de quelque chose de complexe, définissez plutôt une fonction normale et faites la aussi longue que vous voulez.

Les fonctions lambda sont une question de style. Les utiliser n'est jamais une nécessité, partout où vous pouvez les utiliser, vous pouvez utiliser une fonction ordinaire. Je les utilise là où je veux incorporer du code spécifique et non réutilisable sans encombrer mon code de multiples fonctions d'une seule ligne.

IV-G-2. Les fonctions lambda dans le monde réel

Voici les fonctions lambda dans apihelper.py:

 
Sélectionnez
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

Il y a plusieurs chose à noter ici. D'abord, nous utilisons la forme simple de l'astuce and-or, ce qui est sûr car une fonction lambda vaut toujours vrai dans un contexte booléen. (Cela ne veut pas dire qu'une fonction lambda ne peut retourner faux. La fonction est toujours vrai, sa valeur de retour peut être vrai ou fausse.)

Ensuite, nous utilisons la fonction split sans arguments. Vous l'avez déjà vu employée avec un ou deux arguments, sans arguments elle utilise les espaces comme séparateur.

Exemple 4.21. split sans arguments
 
Sélectionnez
>>> s = "this   is\na\ttest"  ***1***
>>> print s
this   is
a	test
>>> print s.split()           ***2***
['this', 'is', 'a', 'test']
>>> print " ".join(s.split()) ***3***
'this is a test'

***1*** Voici une chaîne multi-lignes définie à l'aide de caractères d'échappement au lieu de triples guillemets. \n est le retour chariot et \t le caractère de tabulation. ***2*** split sans arguments fait la séparation sur les espaces. Trois espaces, un retour chariot ou un caractère de tabulation reviennent à la même chose. ***3*** Vous pouvez normaliser les espaces en utilisant split sur une chaîne et en la joignant par join avec un espace simple comme délimiteur. C'est ce que fait la fonction info pour replier les doc string sur une seule ligne.

Mais que fait donc exactement cette fonction info avec ces fonctions lambda, ces split et ces astuces and-or ?

 
Sélectionnez
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

processFunc est maintenant une fonction, mais la fonction qu'elle est dépend de la valeur de la variable collapse. Si collapse vaut vrai, processFunc(string) repliera les espaces, sinon, processFunc(string) retournera son argument sans le modifier.

Pour faire la même chose dans un langage moins robuste, tel que Visual Basic, vous auriez sans doute créé une fonction prenant une chaîne et un argument collapse qui aurait utilisé une instruction if pour décider de replier les espaces ou non, puis aurait retourné la valeur appropriée. Cela serait inefficace car la fonction devrait prendre en compte tous les cas possibles. A chaque fois que vous l'appeleriez, elle devrait décider si elle doit replier l'espace avant de pouvoir vous donner ce que vous souhaitez. En Python, vous pouvez retirer cette prise de décision de la fonction et définir une fonction lambda taillée sur mesure pour vous donner ce que vous voulez et seulement cela. C'est plus efficace, plus élégant et moins sujet à des erreurs dans l'ordre des arguments.

Pour en savoir plus sur les fonctions lambda

IV-H. Assembler les pièces

La dernière ligne du code, la seule que nous n'ayons pas encore déconstruite, est celle qui fait tout le travail. Mais arrivé à ce point, le travail est simple puisque tous les éléments dont nous avons besoin sont disponibles. Les dominos sont en place, il ne reste qu'à les faire tomber.

Voici le plat de résistance de apihelper.py:

 
Sélectionnez
 print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

Notez que ce n'est qu'une commande, répartie sur plusieurs lignes sans utiliser le caractère de continuation («\»). Vous vous rappelez quand j'ai dit que certaines expressions peuvent être divisées en plusieurs lignes sans utiliser de backslash ? Une list comprehension est une expression de ce type car toute l'expression est entourée de crochets.

Maintenant étudions l'expression de la fin vers le début. L'instruction

 
Sélectionnez
      for method in methodList

nous montre qu'il s'agit d'une list comprehension. Comme vous le savez, methodList est une liste de toutes les méthodes qui nous intéressent dans object. Nous parcourons donc cette liste avec method.

Exemple 4.22. Obtenir une doc string dynamiquement

 
Sélectionnez
>>> import odbchelper
>>> object = odbchelper                   ***1***
>>> method = 'buildConnectionString'      ***2***
>>> getattr(object, method)               ***3***
<function buildConnectionString at 010D6D74>
>>> print getattr(object, method).__doc__ ***4***
Build a connection string from a dictionary of parameters.

    Returns string.

***1*** Dans la fonction info, object est l'objet pour lequel nous demandons de l'aide, passé en argument.
***2*** Pendant que nous parcourons la methodList, method est le nom de la méthode en cours.
***3*** En utlisant la fonction getattr, nous obtenons une référence à la fonction method du module object.
***4*** Maintenant, afficher la doc string de la méthode est facile.

La pièce suivante du puzzle est l'utilisation de str sur la doc string. Comme vous vous rappelez peut-être, str est une fonction prédéfinie pour convertir des données en chaîne. Mais une doc string est toujours une chaîne, alors pourquoi utiliser str ? La réponse est que toutes les fonctions n'ont pas de doc string, et que l'attribut __doc__ de celles qui n'en ont pas renvoi None.

Exemple 4.23. Pourquoi utiliser str sur une doc string ?

 
Sélectionnez
>>> >>> def foo(): print 2
>>> >>> foo()
2
>>> >>> foo.__doc__     ***1***
>>> foo.__doc__ == None ***2***
True
>>> str(foo.__doc__)    ***3***
'None'

***1*** Nous pouvons facilement définir une fonction qui n'a pas de doc string, sont attribut __doc__ est None. Attention, si vous évaluez directement l'attribut __doc__, l'IDE Python n'affiche rien du tout, ce qui est logique si vous y réflechissez mais n'est pas très utile. ***2*** Vous pouvez vérifier que la valeur de l'attribut __doc__ est bien None en faisant directement la comparaison. ***3*** Lorsque l'on utilise la fonction str, elle prend la valeur nulle et en retourne une représentation en chaîne, 'None'.

En SQL, vous devez utiliser IS NULLmethod au lieu de = NULL pour la comparaison d'une valeur nulle. En Python, vous pouvez utiliser aussi bien == None que is None, mais is None est plus rapide.

Maintenant que nous sommes sûrs d'obtenir une chaîne, nous pouvons passer la chaîne à processFunc, que nous avons déjà défini comme une fonction qui replie ou non les espace. Maintenant vous voyez qu'il était important d'utiliser str pour convertir une valeur None en une représentation en chaîne. processFunc attend une chaîne comme argument et appelle sa méthode split, ce qui échouerait si nous passions None, car None n'a pas de méthode split.

En remontant en arrière encore plus loin, nous voyons que nous utilisons encore le formatage de chaîne pour concaténer la valeur de retour de processFunc avec celle de la méthode ljust de method. C'est une nouvelle méthode de chaîne que nous n'avons pas encore rencontré.

Exemple 4.24. Présentation de la méthode ljust

 
Sélectionnez
>>> s = 'buildConnectionString'
>>> s.ljust(30) ***1***
'buildConnectionString         '
>>> s.ljust(20) ***2***
'buildConnectionString'

***1*** ljust complète la chaîne avec des espaces jusqu'à la longueur donnée. La fonction info l'utilise pour afficher sur deux colonnes et aligner les doc string de la seconde colonne.
***2*** Si la longueur donnée est plus petite que la longueur de la chaîne, ljust retourne simplement la chaîne sans la changer. Elle ne tronque jamais la chaîne.

Nous avons presque terminé. Ayant obtenu le nom de méthode complété d'espaces de la méthode ljust et la doc string (éventuellement repliée sur une ligne) de l'appel à processFunc, nous concaténons les deux pour obtenir une seule chaîne. Comme nous faisons une mutation de methodList, nous obtenons une liste de chaînes. En utlisant la méthode join de la chaîne "\n", nous joignons cette liste en une chaîne unique, avec chaque élément sur une ligne et affichons le résultat..

Exemple 4.25. Affichage d'une liste

 
Sélectionnez
>>> li = ['a', 'b', 'c']
>>> print "\n".join(li) ***1***
a
b
c

***1*** C'est aussi une astuce de débogage utile lorsque vous travaillez avec des liste. Et en Python, vous travaillez toujours avec des listes.

C'est la dernière pièce du puzzle. Le code devrait maintenant être parfaitement compréhensible.

 
Sélectionnez
 print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

IV-I. Résumé

Le programme apihelper.py et sa sortie devraient maintenant être parfaitement clairs.

 
Sélectionnez
def info(object, spacing=10, collapse=1):
    """Print methods and doc strings.
    
    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

if __name__ == "__main__":
    print info.__doc__

Voici la sortie de apihelper.py :

 
Sélectionnez
>>> from apihelper import info
>>> li = []
>>> info(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of value
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1

Avant de plonger dans le chapitre suivant, assurez vous que vous vous sentez à l'aise pour :

  • Définir et appeler des fonctions avec des arguments optionnels et nommés
  • Utiliser str pour convertir une valeur quelconque en chaîne
  • Utiliser getattr pour obtenir des références à des fonctions et autres attributs dynamiquement
  • Etendre la syntaxe des list comprehensions pour faire du filtrage de liste
  • Identifier l'astuce and-or et l'utiliser de manière sûre
  • Définir des fonctions lambda
  • Assigner des fonctions à des variables et appeler ces fonctions en référençant les variables. Je n'insisterais jamais assez : cette manière de penser est essentielle pour faire progresser votre compréhension de Python. Vous verrez des applications plus complexe de ce concept tout au long de ce livre.

précédentsommairesuivant
Presque tout, en fait. Par défaut, les instances de classes valent vrai dans un contexte booléen mais vous pouvez définir des méthodes spéciales de votre classe pour faire qu'une instance vale faux. Vous apprendrez tout sur les classes et les méthodes spéciales au Chapitre 5.