|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665 |
- title: Bonnes pratiques et astuces Python
- slug: bonnes-pratiques-et-astuces-python
- date: 2008-05-11 20:50:44
- type: post
- vignette: images/logos/python_nouveau.png
- contextual_title1: ★ Astuces et bonnes pratiques Django
- contextual_url1: 20080211-astuces-et-bonnes-pratiques-django
- contextual_title2: Présentation de Django aux journées Python francophones
- contextual_url2: 20070519-presentation-de-django-aux-journees-python-francophones
- contextual_title3: ★ Pourquoi programmer en Python ?
- contextual_url3: 20060505-pourquoi-programmer-en-python
-
- Ça faisait un moment que je n'avais pas parlé des
- bonnes pratiques Python mais l'approche de
- [Pycon fr](http://fr.pycon.org/) (où je présenterai Django :
- [le pourquoi](http://fr.pycon.org/programme/pourquoi-django/) et
- [le comment](http://fr.pycon.org/programme/django-au-quotidien-qualite-et-performances/)
- le 18 mai), l'événement Python incontournable avec
- [un programme des plus alléchants](http://fr.pycon.org/programme/), m'a bien
- motivé pour effectuer la traduction de
- l'[une des meilleures présentation](http://python.net/~goodger/projects/pycon/2007/idiomatic/)
- par [David Goodger](http://python.net/~goodger/) que je connaisse qui remet
- les bonnes pratiques Python à plat, ce qui est toujours bon avant d'aller plus loin.
-
- La lisibilité est importante
- ----------------------------
-
- > Les programmes doivent être écrits pour être lus par des gens et
- accidentellement exécutés par les machines.
- >
- > -- Abelson & Sussman, *Structure and Interpretation of Computer Programs*
-
- Essayez de rendre vos programmes faciles à lire et évidents.
-
-
- PEP 8 : Style Guide pour le code Python
- ---------------------------------------
-
- Une lecture immanquable : [http://www.python.org/dev/peps/pep-0008/](http://www.python.org/dev/peps/pep-0008/)
- (PEP = Python Enhancement Proposal)
-
- Un PEP est une document procurant des informations à la communauté Python, ou
- décrivant une nouvelle fonctionnalité de Python et ses processus ou de son
- environnement.
-
- La communauté Python a ses propres standards sur ce à quoi doit ressembler le
- code, codifiés dans le PEP8. Ces standards sont différents de ceux des autres
- communautés, comme C, Java, etc.
-
- L'indentation et les espaces étant si importants en Python, ce Style Guide est
- une standard. Il est important que vous adhériez au guide ! La plupart des
- projets suivent ces conventions.
-
-
- Whitespace 1
- ------------
-
- * 4 espaces par niveau d'indentation.
- * Pas de tabs.
- * Ne **jamais** mixer des tabs et des espaces.
- * Un saut de ligne entre les fonctions.
- * Deux sauts de ligne entre les classes.
-
-
- Whitespace 2
- ------------
-
- * Ajoutez un espace après ", ", dans les dictionnaires, les listes, les
- tuples, les arguments d'une liste d'arguments et après ":" dans les
- dictionnaires mais pas avant.
- * Mettez des espaces autour des assignements et des comparaisons (excepté
- pour les arguments d'une liste).
- * Pas d'espace aux ouvertures/fermetures de parenthèses ou juste avant une
- liste d'arguments.
- * Pas d'espace en ouverture/fermeture de docstrings.
-
- def make_squares(key, value=0):
- """Return a dictionary and a list..."""
- d = {key: value}
- l = [key, value]
- return d, l
-
-
- Nommage
- -------
-
- * ``joined_lower`` pour les fonctions, méthodes et attributs
- * ``joined_lower`` ou ``ALL_CAPS`` pour les constantes
- * ``StudlyCaps`` pour les classes
- * ``camelCase`` **seulement** pour suivre des conventions pré-existantes
- * Attributs: ``interface``, ``_internal``, ``__private``
-
- Mais essayez d'éviter la forme ``__privée``. Je ne l'utilise jamais.
- Faites moi confiance. Si vous l'utilisez, vous le regretterez plus tard.
-
-
- Longues lignes et continuité
- ----------------------------
-
- Garder une taille de ligne inférieure à 80 caractères.
-
- Utilisez la continuité implicite des lignes au sein des
- parenthèses/crochets/accolades :
-
- def __init__(self, first, second, third,
- fourth, fifth, sixth):
- output = (first + second + third
- + fourth + fifth + sixth)
-
- Utilisez les backslashs en dernier recours :
-
- VeryLong.left_hand_side \
- = even_longer.right_hand_side()
-
- Les backslashs sont locaux, ils doivent terminer la ligne sur laquelle
- ils sont. Si vous ajoutez un espace après le backslash, ça ne sert à rien.
- Ah aussi, c'est laid.
-
- Longues chaînes de caractères
- -----------------------------
-
- Les chaînes de caractères adjacentes sont concaténées par le parser:
-
- >>> print 'o' 'n' "e"
- one
-
- Les espaces entre les chaînes ne sont pas requis, mais aident à la
- lisibilité. Tous les types de quotes sont utilisable :
-
- >>> print 't' r'\/\/' """o"""
- t\/\/o
-
- La chaîne précédée par "r" est une chaîne de type "raw". Les backslashs
- ne sont pas évalués comme étant des caractères d'échappement dans les
- chaînes de type raw. Elles sont utiles pour les expressions régulières
- et les chemins de fichiers Windows.
-
- Notez que les chaînes de caractères nommées **ne sont pas** concaténées :
-
- >>> a = 'three'
- >>> b = 'four'
- >>> a b
- File "<stdin>", line 1
- a b
- ^
- SyntaxError: invalid syntax
-
- Cela vient du fait que la concaténation automatique est une fonctionnalité
- du parser/compiler Python, pas de l'interpréteur. Vous devez utiliser le
- signe "+" pour concaténer des chaînes de caractères à l'éxecution.
-
- text = ('Long strings can be made up '
- 'of several shorter strings.')
-
-
- Les parenthèses autorisent la continuité implicite des lignes.
- Les chaînes de caractères sur plusieurs lignes utilisent les triple quotes :
-
- """Triple
- double
- quotes"""
-
- '''\
- Triple
- single
- quotes\
- '''
-
- Dans le dernier exemple ci-dessus (simple triple quotes), notez
- l'utilisation du backslash pour échapper les nouvelles lignes. Cela élimine
- les nouvelles lignes en conservant les quotes joliment alignées à gauche.
- Les backslashs doivent être à la fin de leurs lignes.
-
-
- Déclarations
- ------------
-
- Bon :
-
- if foo == 'blah':
- do_something()
- do_one()
- do_two()
- do_three()
-
- Mauvais :
-
- if foo == 'blah': do_something()
- do_one(); do_two(); do_three()
-
- Les espaces et l'indentation sont de bons indicateurs visuels du flot du
- programme. L'indentation de la seconde ligne du "Bon" ci-dessus montre
- au lecteur que quelque chose va se produire, alors que le manque
- d'indentation dans le "Mauvais" exemple cache le "if".
-
- Les déclarations multiples sur une même ligne sont une torture.
- En Python, *la lisibilité compte*.
-
-
- Docstrings et Commentaires
- --------------------------
-
- Docstrings = **Comment utiliser** le code
-
- Commentaires = **Pourquoi** (rationnel) et **comment le code fonctionne**
-
-
- Les docstrings expliquent **comment** utiliser le code et sont là pour
- **les utilisateurs** de votre code. Quelques usages :
-
- * Expliquer le but d'une fonction même si ça vous semble évident car ça ne
- semblera pas forcément évident à une personne plus tard.
- * Décrire les paramètres attendus, les valeurs retournées et les exceptions
- levées.
- * Si la méthode est fortement couplée à un seul appelant, mentionner
- la fonction appelante (attention au fait que celle-ci puisse changer).
-
- Les commentaires expliquent **pourquoi** et sont pour les mainteneurs de
- votre code. Examples incluant des notes pour vous-même, comme :
-
- # !!! BUG: ...
-
- # !!! FIX: This is a hack
-
- # ??? Why is this here?
-
- Les deux types sont de **votre** ressort donc écrivez de bonnes docstrings
- et de bons commentaires !
-
- Les docstrings sont utiles pour un usage interactif (``help()``) et pour
- les systèmes d'auto-documentation.
-
- Les commentaires et docstrings faux sont pire que tout. Donc conservez les
- à jour ! Lorsque vous effectuez des modifications, assurez vous que les
- commentaires et les docstrings sont cohérents avec le code.
-
- Il y a [un PEP entier consacré aux docstrings, PEP 257, "Docstring
- Conventions"](http://www.python.org/dev/peps/pep-0257/).
-
-
- La pratique a raison de la théorie
- ----------------------------------
-
- Il y a toujours des exceptions. Issu du PEP 8 :
-
- > Mais plus important : sachez être pertinents - parfois le style guide ne
- s'applique pas. Lorsque vous avez un doute, utilisez votre raison. Étudiez
- d'autres possibilités et décidez de ce qui vous semble le mieux.
- Et n'hésitez pas à demander !
- > Deux bonnes raisons de ne pas suivre une règle particulière :
- >
- > (1) Lorsque appliquer la règle va rendre le code moins lisible, même pour
- quelqu'un qui est habitué à lire du code qui suit les règles.
- >
- > (2) Pour être cohérent avec du code préexistant qui enfreint aussi ces règles
- (peut-être pour des raisons historiques) -- même si c'est aussi une opportunité
- pour faire un peu de nettoyage (dans un pur style XP).
-
- ... mais la pratique ne doit pas réduire la théorie à néant !
-
- On plonge maintenant au cœur du tutoriel : les astuces. On va commencer avec
- les plus faciles et augmenter progressivement le niveau.
-
-
- Variables intermédiaires
- ------------------------
-
- Dans les autres langages :
-
- temp = a
- a = b
- b = temp
-
- En Python :
-
- b, a = a, b
-
- Vous l'avez peut-être déjà rencontré mais savez vous comment ça fonctionne ?
-
- * La **virgule** est la syntaxe de construction du tuple.
- * Un tuple est créé à droite (tuple packing).
- * Un tuple en est la cible à gauche (tuple unpacking).
-
- La partie à droite est **unpackée** dans les noms de tuple de la partie à
- gauche.
-
- D'autres exemples:
-
- >>> l =['David', 'Pythonista', '+1-514-555-1234']
- >>> name, title, phone = l
- >>> name
- 'David'
- >>> title
- 'Pythonista'
- >>> phone
- '+1-514-555-1234'
-
- Utile dans les boucles sur des données structurées (la variable ``l``
- ci-dessus a été conservée) :
-
- >>> people = [l, ['Guido', 'BDFL', 'unlisted']]
- >>> for (name, title, phone) in people:
- ... print name, phone
- ...
- David +1-514-555-1234
- Guido unlisted
-
- Chaque item de ``people`` est unpacké dans le tuple ``(name, title, phone)``.
-
- Il est aussi possible de faire le chemin inverse, il faut juste s'assurer
- d'avoir la même structure à droite et à gauche :
-
- >>> david, (gname, gtitle, gphone) = people
- >>> gname
- 'Guido'
- >>> gtitle
- 'BDFL'
- >>> gphone
- 'unlisted'
- >>> david
- ['David', 'Pythonista', '+1-514-555-1234']
-
-
- Aller plus loin avec les tuples
- -------------------------------
-
- On a vu que la **virgule** était le constructeur du tuple, pas les
- parenthèses. Par exemple :
-
- >>> 1,
- (1,)
-
- L'interpréteur Python montre les parenthèses pour que ce soit plus clair
- et je vous conseille de faire de même :
-
- >>> (1,)
- (1,)
-
- Mais n'oubliez pas la virgule !
-
- >>> (1)
- 1
-
- Dans un tuple contenant un seul élément, la virgule est nécessaire. Dans
- un tuple avec plus de 2 éléments, la virgule finale est optionnelle.
- Pour un tuple vide, une paire de parenthèses suffit :
-
- >>> ()
- ()
-
- >>> tuple()
- ()
-
- Une erreur de typo courante est de laisser une virgule alors que vous ne
- souhaitez pas avoir un tuple. Il est très facile de l'oublier dans votre
- code :
-
- >>> value = 1,
- >>> value
- (1,)
-
- Donc si vous vous retrouvez avec un tuple alors que vous ne vous y
- attendiez pas, cherchez la virgule ! (*Note du traducteur* : de ma propre
- expérience, il est plus courant d'oublier la virgule pour un tuple ne
- contenant qu'un seul élément, dans les settings de Django par exemple,
- cherchez plutôt la virgule manquante dans ces cas là).
-
-
- Le "_" interactif
- -----------------
-
- C'est une fonctionnalité très utile que peu de développeurs connaissent.
- (*Note du traducteur* : bien entendu
- [vous n'en faites pas partie](https://larlet.fr/david/biologeek/archives/20060425-python-et-underscore/)
- et vous connaissez les dangers associés.)
-
- Dans un interpréteur interactif, que vous évaluiez une expression ou que
- vous appeliez une fonction, le résultat est stocké dans une variable
- temporaire, ``_`` (un underscore) :
-
- >>> 1 + 1
- 2
- >>> _
- 2
-
- ``_`` stocke la dernière valeur *affichée*.
-
- Lorsqu'un résultat vaut ``None``, rien n'est affiché, donc ``_`` ne change
- pas. C'est normal !
-
- Ça ne marche que dans un interpréteur interactif, pas dans un module.
-
- C'est particulièrement utile lorsque vous travaillez sur un problème de
- manière interactive, et que vous souhaitez stocker la valeur du dernier
- résultat :
-
- >>> import math
- >>> math.pi / 3
- 1.0471975511965976
- >>> angle = _
- >>> math.cos(angle)
- 0.50000000000000011
- >>> _
- 0.50000000000000011
-
-
- Construction de chaînes de caractères
- -------------------------------------
-
- Commençons avec une liste de chaînes de caractères :
-
- colors = ['red', 'blue', 'green', 'yellow']
-
- On veut concaténer ces chaînes ensemble pour en créer une longue.
- Particulièrement lorsque le nombre de sous-chaînes est gros...
-
- Ne faites pas :
-
- result = ''
- for s in colors:
- result += s
-
- C'est très lent.
- Ça utilise énormément de mémoire et de performances. La somme va additionner,
- stocker, et ensuite passer à la suite pour chaque étape intermédiaire.
-
- Faites plutôt ceci :
-
- result = ''.join(colors)
-
- La méthode ``join()`` fait toute la copie en une seule passe.
-
- Lorsque vous ne traitez qu'une petite centaine de chaînes de caractères,
- ça ne fait aucune différence. Mais prenez l'habitude de construire vos
- chaînes de façon optimale, car avec des milliers ou des boucles, ça **va**
- faire la différence.
-
-
- Construire des chaînes, solutions 1
- -----------------------------------
-
- Voici quelques techniques pour utiliser la méthode ``join()``.
-
- Si vous voulez un espace comme séparateur :
-
- result = ' '.join(colors)
-
- ou une virgule et un espace :
-
- result = ', '.join(colors)
-
- voici un cas courant d'utilisation :
-
- colors = ['red', 'blue', 'green', 'yellow']
- print 'Choose', ', '.join(colors[:-1]), \
- 'or', colors[-1]
-
- Pour faire une phrase grammaticalement correcte, on veut des virgules entre
- chaque valeurs sauf la dernière, où l'on préfère un "ou". La syntaxe de
- découpage d'une liste s'occupe du reste. La "partie jusqu'à -1" (``[:-1]``)
- retourne tout sauf la dernière valeur, que l'on peut concaténer avec nos
- virgules.
-
- Bien sûr, ce code ne fonctionnera pas avec les cas particuliers comme une
- liste de taille 0 ou 1. Ce qui retourne :
-
- Choose red, blue, green or yellow
-
-
- Construire des chaînes, solutions 2
- -----------------------------------
-
- Vous avez besoin d'appliquer une fonction pour générer les chaînes initiales :
-
- result = ''.join(fn(i) for i in items)
-
-
- Ça utilise une *generator expression*, dont on parlera plus tard.
-
- Si vous devez modifier les chaînes de manière incrémentale, commencez par
- les stocker dans une liste pour commencer :
-
- items = []
- ...
- items.append(item) # de nombreuses fois
- ...
- # une fois la liste complétée
- result = ''.join(fn(i) for i in items)
-
- On accumule les parties de la liste afin de pouvoir appliquer le ``join``,
- ce qui est plus rapide.
-
-
- Utilisez ``in`` lorsque c'est possible (1)
- ------------------------------------------
-
- Bon :
-
- for key in d:
- print key
-
- * ``in`` est généralement plus rapide.
- * Ce pattern marche aussi pour des items dans des containers arbitraires
- (comme les listes, les tuples ou les tests).
- * ``in`` est aussi un opérateur (comme on va le voir).
-
- Mauvais :
-
- for key in d.keys():
- print key
-
- C'est limité aux objects ayant une méthode ``keys()``.
-
-
- Utilisez ``in`` lorsque c'est possible (2)
- ------------------------------------------
-
- Mais ``.keys()`` est **nécessaire** lorsque vous modifiez le dictionnaire :
-
- for key in d.keys():
- d[str(key)] = d[key]
-
- ``d.keys()`` crée une liste statique des clés du dictionnaire. Sinon, vous
- allez lever une exception "RuntimeError: dictionary changed size during iteration".
-
- Utilisez ``key in dict``, et non ``dict.has_key()`` :
-
- # faites ça :
- if key in d:
- ...do something with d[key]
-
- # mais pas ça :
- if d.has_key(key):
- ...do something with d[key]
-
- ``in`` est ici utilisé comme un opérateur.
-
-
- La méthode ``get`` des dictionnaires
- ------------------------------------
-
- On doit souvent initialiser les entrées d'un dictionnaire avant de les utiliser:
-
- Voici la manière naïve de faire :
-
- navs = {}
- for (portfolio, equity, position) in data:
- if portfolio not in navs:
- navs[portfolio] = 0
- navs[portfolio] += position * prices[equity]
-
- ``dict.get(key, default)`` permet de ne pas avoir à se soucier du test :
-
- navs = {}
- for (portfolio, equity, position) in data:
- navs[portfolio] = (navs.get(portfolio, 0)
- + position * prices[equity])
-
- Beaucoup mieux.
-
-
- La méthode ``setdefault`` des dictionnaires (1)
- -----------------------------------------------
-
- Ici on doit initialiser les valeurs d'un dictionnaire mutables. Chaque valeur
- du dictionnaire sera une liste. Voici la manière naïve :
-
- equities = {}
- for (portfolio, equity) in data:
- if portfolio in equities:
- equities[portfolio].append(equity)
- else:
- equities[portfolio] = [equity]
-
- ``dict.setdefault(key, default)`` s'occupe de ça de manière beaucoup plus rapide :
-
- equities = {}
- for (portfolio, equity) in data:
- equities.setdefault(portfolio, []).append(equity)
-
- ``dict.setdefault()`` est équivalent à "get ou set & get". Ou
- "set si nécessaire, puis get". C'est particulièrement rapide si votre clé de
- dictionnaire est coûteuse à générer ou longue à taper.
-
- Le seul problème avec ``dict.setdefault()`` c'est que la valeur par défaut est
- évaluée, qu'elle soit utilisée ou non. Ça ne pose problème que si la clé est
- coûteuse à calculer.
-
- Si la valeur par défaut **est** coûteuse à calculer, vous devriez plutôt
- utiliser la classe ``defaultdict``.
-
-
- La méthode ``setdefault`` des dictionnaires (2)
- -----------------------------------------------
-
- On va voir qu'il est possible d'utiliser ``setdefault`` pour déclarer une valeur par défaut :
-
- navs = {}
- for (portfolio, equity, position) in data:
- navs.setdefault(portfolio, 0)
- navs[portfolio] += position * prices[equity]
-
- La méthode ``setdefault`` d'un dictionnaire retourne la valeur par défaut, mais
- nous l'ignorons ici. On tire profit d'une conséquence de l'utilisation de
- ``setdefault``, la valeur n'est initialisée que si elle n'existe pas déjà.
-
-
- ``defaultdict``
- ---------------
-
- Nouveau avec Python 2.5.
-
- ``defaultdict`` est nouveau dans Python 2.5, il fait partie du module
- ``collections``. ``defaultdict`` est identique aux dictionnaires classiques,
- excepté pour deux cas :
-
- * il prend un premier argument optionnel : une fonction factory par défaut
- * lorsqu'une clé de dictionnaire est rencontrée pour la première fois, la
- fonction factory par défaut est appelée et le résultat initialise la
- valeur du dictionnaire.
-
- Il y a deux manières d'accéder à ``defaultdict`` :
-
- * importer le module ``collections`` et l'appeler à travers le module :
-
- import collections
- d = collections.defaultdict(...)
-
- * ou importer ``defaultdict`` directement :
-
- from collections import defaultdict
- d = defaultdict(...)
-
- Voici l'exemple déjà traité, où chaque valeur du dictionnaire fois être
- initialisé pour être une liste vide, réécrit en utilisant ``defaultdict`` :
-
- from collections import defaultdict
-
- equities = defaultdict(list)
- for (portfolio, equity) in data:
- equities[portfolio].append(equity)
-
- Il n'y a plus d'astuce ici. Dans ce cas, la fonction factory par défaut est
- ``list``, ce qui retourne une liste vide.
-
- C'est la manière d'avoir un dictionnaire avec les valeurs par défaut à 0,
- utilisez ``int`` comme factory :
-
- navs = defaultdict(int)
- for (portfolio, equity, position) in data:
- navs[portfolio] += position * prices[equity]
-
- Il faut faire attention à ``defaultdict`` quand même. Vous ne pouvez pas
- utiliser l'exception ``KeyError`` sur un dictionnaire initialisé avec ``defaultdict``.
- Vous devez utiliser la condition "key in dict" si vous voulez vérifier
- l'existence d'une clé de manière spécifique.
-
-
- Construire et scinder des dictionnaires
- ---------------------------------------
-
- Voila une technique utile pour construire un dictionnaire à partir de deux
- listes (ou séquences), une liste pour les clés, une liste pour les valeurs :
-
- given = ['John', 'Eric', 'Terry', 'Michael']
- family = ['Cleese', 'Idle', 'Gilliam', 'Palin']
- pythons = dict(zip(given, family))
- >>> pprint.pprint(pythons)
- {'John': 'Cleese',
- 'Michael': 'Palin',
- 'Eric': 'Idle',
- 'Terry': 'Gilliam'}
-
- L'inverse est trivial bien sûr :
-
- >>> pythons.keys()
- ['John', 'Michael', 'Eric', 'Terry']
- >>> pythons.values()
- ['Cleese', 'Palin', 'Idle', 'Gilliam']
-
- Notez que l'ordre du résultat des .keys() et .values() à est différent des
- listes utilisées lors de la création du dictionnaire. L'ordre d'entrée est
- différent de l'ordre de sortie car un dictionnaire n'est pas ordonné.
- Par contre, l'ordre des clés est consistant avec celui des valeurs, à condition
- que le dictionnaire n'ait pas été modifié entre temps.
-
-
- Tester des valeurs vraies
- -------------------------
-
- Il est élégant et rapide de tirer partie des avantages de Python en ce qui
- concerne les valeurs booléennes :
-
- # faites ça : # et pas ça :
- if x: if x == True:
- pass pass
-
- Test d'une liste :
-
- # faites ça : # et pas ça :
- if items: if len(items) != 0:
- pass pass
-
- # et surtout pas ça :
- if items != []:
- pass
-
-
- Valeurs vraies
- --------------
-
- Les noms ``True`` et ``False`` sont des instances intrinsèques à Python de type
- ``bool``, des valeurs booléennes. Comme ``None``, il n'existe qu'une seule
- instance de chaque.
-
- <table>
- <thead>
- <tr>
- <th>
- False
- </th>
- <th>
- True
- </th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>
- <code>False</code> (== 0)
- </td>
- <td>
- <code>True</code> (== 1)
- </td>
- </tr>
- <tr>
- <td>
- <code>""</code> (empty string)
- </td>
- <td>
- toutes les chaînes à part <code>""</code> (<code>" "</code>,
- <code>"nimportequoi"</code>)
- </td>
- </tr>
- <tr>
- <td>
- <code>0</code>, <code>0.0</code>
- </td>
- <td>
- n'importe quel chiffre à part <code>0</code>
- (1, 0.1, -1, 3.14)
- </td>
- </tr>
- <tr>
- <td>
- <code>[]</code>, <code>()</code>,
- <code>{}</code>, <code>set()</code>
- </td>
- <td>
- n'importe quel container non vide
- (<code>[0]</code>, <code>(None,)</code>, <code>['']</code>)
- </td>
- </tr>
- <tr>
- <td>
- <code>None</code>
- </td>
- <td>
- pratiquement tous les objets qui ne sont
- explicitement équivalents à False
- </td>
- </tr>
- </tbody>
- </table>
-
- Voici par exemple un objet qui est toujours vrai :
-
- >>> class C:
- ... pass
- ...
- >>> o = C()
- >>> bool(o)
- True
- >>> bool(C)
- True
-
- Pour contrôler la valeur booléenne d'une instance ou d'une classe définie,
- utilisez les méthodes spéciales ``__nonzero__`` ou ``__len__``. Utilisez
- ``__len__`` si votre classe est un container qui a une taille :
-
- class MyContainer(object):
-
- def __init__(self, data):
- self.data = data
-
- def __len__(self):
- """Return my length."""
- return len(self.data)
-
- Si votre classe n'est pas un container, utilisez ``__nonzero__`` :
-
- class MyClass(object):
-
- def __init__(self, value):
- self.value = value
-
- def __nonzero__(self):
- """Return my truth value (True or False)."""
- # This could be arbitrarily complex:
- return bool(self.value)
-
- En Python 3.0, ``__nonzero__`` a été renommé ``__bool__`` afin d'être consistant
- avec le type ``bool`` natif. Pour être compatible, ajoutez ceci à la définition
- de votre classe :
-
- __bool__ = __nonzero__
-
-
- Index & Item (1)
- ----------------
-
- Voici une manière élégante de vous épargner quelques lignes si vous avez
- besoin d'une liste de mots :
-
- >>> items = 'zero one two three'.split()
- >>> print items
- ['zero', 'one', 'two', 'three']
-
- Prenons l'exemple d'un itération entre les items d'une liste, pour laquelle
- nous voulons à la fois l'item et la position (l'index) de cet item dans la liste :
-
- - ou -
- i = 0
- for item in items: for i in range(len(items)):
- print i, item print i, items[i]
- i += 1
-
-
- Index & Item (2): ``enumerate``
- -------------------------------
-
- La fonction ``enumerate`` prend une liste et retourne des paires (index, item) :
-
- >>> print list(enumerate(items))
- [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')]
-
- Il est nécessaire d'avoir recours à une ``list`` pour afficher les résultats
- car ``enumerate`` est une fonction fainéante, générant un item (une paire) à
- la fois, seulement lorsqu'il est demandé. Une boucle ``for`` nécessite un tel
- mécanisme. ``enumerate`` est un exemple de **générateur** dont on parlera plus
- tard des détails. ``print`` ne prend pas un résultat à la fois mais doit être
- en possession de la totalité du message à afficher. On a donc converti
- automatiquement le générateur en une liste avant d'utiliser print.
-
- Notre boucle devient beaucoup plus simple :
-
- for (index, item) in enumerate(items):
- print index, item
-
- # comparé à : # comparé à :
- index = 0 for i in range(len(items)):
- for item in items: print i, items[i]
- print index, item
- index += 1
-
- La version avec ``enumerate`` est plus courte et plus simple que la version
- de gauche, et plus facile à lire que les deux autres.
-
- Un exemple montrant que la fonction ``enumerate`` retourne un itérateur
- (un générateur est une sorte d'itérateur) :
-
- >>> enumerate(items)
- <enumerate object at 0x011EA1C0>
- >>> e = enumerate(items)
- >>> e.next()
- (0, 'zero')
- >>> e.next()
- (1, 'one')
- >>> e.next()
- (2, 'two')
- >>> e.next()
- (3, 'three')
- >>> e.next()
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- StopIteration
-
-
- Les autres langages ont des "variables"
- ---------------------------------------
-
- Dans de nombreux autres langages, assigner une variable revient à mettre une
- valeur dans une boîte.
-
- int a = 1;
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/a1box.png"
- alt="Boîte 1 a"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- La boîte "a" contient maintenant un integer 1.
-
- Assigner une autre valeur à la même variable remplace le contenu de la boîte :
-
- a = 2;
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/a2box.png"
- alt="Boîte 2 a"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- Maintenant la boîte "a" contient un integer 2.
-
- Assigner une variable à une autre crée une copie de la valeur et la met
- dans une nouvelle boîte :
-
- int b = a;
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/a2box.png"
- alt="Boîte 2 a"
- style="margin: 0pt auto; display: block;"/>
- </p>
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/b2box.png"
- alt="Boîte 2 b"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- "b" est une seconde boîte, avec une copie de l'entier 2. La boîte "a" en a
- une copie séparée.
-
-
- Python a des "noms"
- -------------------
-
- En Python, un "nom" ou "identifiant" est comme une étiquette attachée à un
- objet.
-
- a = 1
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/a1tag.png"
- alt="Tag 1 a"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- Ici, un objet integer 1 a une étiquette appelée "a".
-
- Si on réassigne "a", on déplace juste l'étiquette sur un autre objet :
-
- a = 2
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/a2tag.png"
- alt="Tag 2 a"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/1.png"
- alt="1"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- Maintenant le nom "a" est attaché à un objet entier 2.
-
- L'objet entier 1 n'a plus le tag "a". Il peut encore exister mais on n'y a
- plus accès via le nom "a". (Lorsqu'un objet n'a plus aucune référence ou
- étiquette, il est supprimé de la mémoire.)
-
- Si l'on assigne un nom à un autre, on attache juste une autre étiquette à
- un objet existant :
-
- b = a
-
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/ab2tag.png"
- alt="Tag 2 a b"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- Le nom "b" est juste une seconde étiquette attachée au même objet que "a".
-
- Bien que l'on réfère communément aux "variables" en Python (car c'est une
- terminologie commune aux autres langages), on manipule vraiment de "noms" ou
- "identifiants". En Python, les "variables" sont des étiquettes pour des
- valeurs, non des boîtes nommés.
-
- Si vous ne comprenez rien au reste de ce tutoriel, j'espère que vous aurez$
- au moins retenu la façon dont les noms fonctionnent. Une bonne compréhension
- vous permettra d'apprendre rapidement et d'éviter des erreurs comme celle-ci:
-
-
- Valeurs de paramètres par défaut
- --------------------------------
-
- C'est une erreur courante que les débutants font souvent. Même les
- développeurs plus expérimentés la font s'ils n'ont pas compris comment
- fonctionnent les noms en Python.
-
- def bad_append(new_item, a_list=[]):
- a_list.append(new_item)
- return a_list
-
- Le problème ici c'est que la valeur par défaut ``a_list``, une liste vide,
- est évaluée lors de la définition de la fonction. Ainsi à chaque fois que
- vous appelez la fonction vous obtenez la **même** valeur par défaut.
- Essayez plusieurs fois :
-
- >>> print bad_append('one')
- ['one']
-
- >>> print bad_append('two')
- ['one', 'two']
-
- Les listes sont modifiables, vous pouvez modifier leur contenu. La bonne
- manière d'avoir une liste par défaut (ou dictionnaire, ou set) est de la
- créer au moment du lancement, **au sein de la fonction** :
-
- def good_append(new_item, a_list=None):
- if a_list is None:
- a_list = []
- a_list.append(new_item)
- return a_list
-
-
- Formattage des chaînes de caractères avec %
- -------------------------------------------
-
- L'opérateur ``%`` fonctionne en Python comme la fonction ``sprintf`` de C.
-
- Bien sûr si vous ne connaissez pas C, ça ne vous aide pas. Pour faire simple,
- vous définissez un template ou format et des valeurs qui seront interprétées.
-
- Dans cet exemple, le template contient deux spécifications de conversion
- "%s" signifie "insérer une chaîne de caractère ici" et "%i" signifie
- "convertir un integer en string et l'insérer ici". "%s" est particulièrement
- utile car il utilise la fonction standard ``str()`` pour convertir un objet
- en une chaîne de caractères.
-
- Les valeurs proposées doivent correspondrent au template, on a deux valeurs
- ici, un tuple.
-
- name = 'David'
- messages = 3
- text = ('Hello %s, you have %i messages'
- % (name, messages))
- print text
-
- Ce qui donne :
-
- Hello David, you have 3 messages
-
- Les détails sont dans la *Python Library Reference*, section 2.3.6.2,
- "String Formatting Operations". Mettez cette page en favoris !
-
- Si vous ne l'avez pas encore fait, allez sur python.org, téléchargez la
- documentation en HTML (dans un .zip ou une archive), et installez la sur
- votre machine. Il n'y a rien de mieux que d'avoir la ressource de référence
- à portée de clavier.
-
-
- Formattage des chaînes de caractères avancé
- -------------------------------------------
-
- Pourquoi est-ce qu'il y a autant de personnes qui ne réalisent pas qu'il y
- a d'autres façons de formater les chaînes de caractères qui peuvent s'avérer
- plus puissantes ?
-
- Avec des noms grâce à un dictionnaire :
-
- values = {'name': name, 'messages': messages}
- print ('Hello %(name)s, you have %(messages)i '
- 'messages' % values)
-
- Ici on a spécifié les noms des valeurs interprétées, qui constituent les
- clés du dictionnaire.
-
- Vous trouvez qu'il y a de la redondance ? Les noms "name" et "messages"
- sont déjà définis localement. On peut en tirer parti.
-
-
- En utilisant les variables locales :
-
- print ('Hello %(name)s, you have %(messages)i '
- 'messages' % locals())
-
- La fonction ``locals()`` retourne un dictionnaire de toutes les variables
- locales disponibles.
-
- C'est très puissant. Grâce à ça, vous pouvez formater toutes les chaînes de
- caractères que vous voulez sans avoir à vous soucier de la correspondance
- positionnelle avec les valeurs soumises en argument.
-
- Mais le pouvoir peut être dangereux. ("With great power comes great
- responsibility.") Si vous utilisez ``locals()`` avec un template issu d'une
- ressource externe, vous exposez l'intégralité de votre espace de noms local.
- C'est une chose à garder en tête.
-
- Pour examiner votre espace de nom local :
-
- >>> from pprint import pprint
- >>> pprint(locals())
-
- ``pprint`` est un module très utile. Si vous ne le connaissiez pas déjà,
- essayez de jouer avec. Ça rend le debugging des données structurées beaucoup
- plus simple !
-
-
- Formattage des chaînes de caractères avancé
- -------------------------------------------
-
- L'espace de nom des attributs d'une instance d'objet est simplement un
- dictionnaire, ``self.__dict__``.
-
- En utilisant l'espace de nom d'une instance :
-
- print ("We found %(error_count)d errors"
- % self.__dict__)
-
- Equivalent à, mais moins flexible que :
-
- print ("We found %d errors"
- % self.error_count)
-
- Note: Les attributs d'une classe sont dans le ``__dict__`` de la classe.
- Les espaces de noms sont hérités et constituent donc des dictionnaires
- chaînés.
-
- List Comprehensions
- -------------------
-
- Les list comprehensions ("listcomps" pour les intimes) sont des raccourcis
- syntaxiques pour ce pattern généralement utilisé.
-
- La manière traditionnelle avec ``for`` et ``if`` :
-
- new_list = []
- for item in a_list:
- if condition(item):
- new_list.append(fn(item))
-
- En utilisant une list comprehension :
-
- new_list = [fn(item) for item in a_list
- if condition(item)]
-
- Les listcomps sont claires et concises, directes. Vous pouvez avoir plusieurs
- boucles ``for`` et conditions ``if`` au sein d'une même listcomp, mais
- au-delà de deux ou trois, ou si les conditions sont complexes, je vous
- suggère d'utiliser l'habituelle boucle ``for``. En appliquant le Zen de Python,
- utilisez la méthode la plus lisible.
-
- Par exemple, la liste des carrés de 0 à 9 :
-
- >>> [n ** 2 for n in range(10)]
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
-
- La liste des nombres impairs au sein de la précédente liste :
-
- >>> [n ** 2 for n in range(10) if n % 2]
- [1, 9, 25, 49, 81]
-
-
- Generator Expressions (1)
- -------------------------
-
- Faisons la somme des carrés des nombres inférieurs à 100 :
-
- Avec une boucle :
-
- total = 0
- for num in range(1, 101):
- total += num * num
-
- On peut aussi utiliser la fonction ``sum`` qui fait plus rapidement le
- travail pour nous en construisant la bonne séquence.
-
- Avec une list comprehension :
-
- total = sum([num * num for num in range(1, 101)])
-
- Avec une generator expression :
-
- total = sum(num * num for num in xrange(1, 101))
-
- Les generator expressions ("genexps") sont comme les list
- comprehensions, excepté dans leur calcul, les genexps sont fainéantes.
- Les listcomps calculent l'intégralité du résultat en une seule passe, pour
- le stocker dans une liste. Les generator expressions calculent une valeur à
- la fois, lorsqu'elle est nécessaire. C'est particulièrement utile lorsque la
- séquence est très longue lorsque la liste générée n'est qu'une étape
- intermédiaire et non le résultat final.
-
- Dans ce cas, on est uniquement intéressé par la somme, on n'a pas besoin de
- la liste des résultats intermédiaires. On utilise ``xrange`` pour la même
- raison, ça génère les valeurs une par une.
-
-
- Generator Expressions (2)
- -------------------------
-
- Par exemple si on doit faire la somme des carrés de plusieurs milliards
- d'entiers, on va arriver à une saturation de la mémoire avec une list
- comprehension, mais les generator expressions ne vont avoir aucun problème.
- Bon ça va prendre un certain temps par contre !
-
- total = sum(num * num
- for num in xrange(1, 1000000000))
-
- La différence de syntaxe est que les listcomps ont des crochets, alors que
- les genexps n'en ont pas. Les generator expressions nécessitent parfois des
- parenthèses par contre, vous devriez donc toujours les utiliser.
-
- En bref :
-
- * Utilisez une list comprehension lorsque le résultat escompté est la liste.
- * Utilisez une generator expression lorsque la liste n'est qu'un résultat
- intermédiaire.
-
- Voici un récent exemple de ce que j'ai vu au boulot.
-
- On avait besoin d'un dictionnaire qui corresponde aux chiffres des mois (à
- la fois via des chaînes de caractères et via des integers) au code des mois
- pour un client. Cela peut être fait avec une ligne de code.
-
- Ça fonctionne de la manière suivante :
-
- * La fonction de référence ``dict()`` prend en argument une liste de paires
- de clés/valeurs (2-tuples).
- * On a une liste des codes des mois (chaque code est une simple lettre, et
- une chaîne de caractères est aussi une simple liste de lettres). On parcours
- cette liste pour obtenir à la fois le code du mois et l'index.
- * Le nombre des mois commence à 1 mais Python commence l'indexation à 0,
- le nombre des mois correspond dont à index+1.
- * On veut avoir la correspondance à la fois avec les nombres et les chaînes
- de caractères. On peut utiliser les fonctions ``int()`` et ``str()`` pour
- ça et itérer dessus.
-
- L'exemple en question :
-
- month_codes = dict((fn(i+1), code)
- for i, code in enumerate('FGHJKMNQUVXZ')
- for fn in (int, str))
-
- Le résultat obtenu pour ``month_codes`` :
-
- { 1: 'F', 2: 'G', 3: 'H', 4: 'J', ...
- '1': 'F', '2': 'G', '3': 'H', '4': 'J', ...}
-
-
- Ordonner
- --------
-
- Il est très simple d'ordonner une liste en Python :
-
- a_list.sort()
-
- (Notez que la liste est ordonnée sur place, la liste originale est ordonnée
- et la fonction ``sort`` **ne retourne pas** une liste ou une copie.)
-
- Mais que faire lorsque vous avec une liste de données à ordonner, mais
- quelle ne s'ordonne pas de manière naturelle ? Par exemple ordonner selon
- la première colonne, puis la quatrième.
-
- On peut utiliser la fonction de référence ``sort`` avec une méthode définie
- par nos soins :
-
- def custom_cmp(item1, item2):
- returm cmp((item1[1], item1[3]),
- (item2[1], item2[3]))
-
- a_list.sort(custom_cmp)
-
- Ça marche, mais c'est extrêmement lent pour les listes énormes.
-
-
- Ordonner avec DSU
- -----------------
-
- DSU = Decorate-Sort-Undecorate
-
- Note: DSU n'est bien souvent plus nécessaire, cf. section suivante.
-
- Au lieu de créer une fonction de comparaison personnalisée, on crée une
- liste intermédiaire qui va pourvoir être ordonnée naturellement :
-
- # Decorate:
- to_sort = [(item[1], item[3], item)
- for item in a_list]
-
- # Sort:
- to_sort.sort()
-
- # Undecorate:
- a_list = [item[-1] for item in to_sort]
-
- La première ligne crée une liste contenant des tuples, une copie de la valeur
- à ordonner en premier argument, suivi de la valeur complète de la liste.
-
- La seconde ligne ordonne grâce à la fonction Python, ce qui est très rapide.
-
- La troisième ligne récupère la **dernière** valeur de la liste une fois
- ordonnée. Souvenez-vous, cette dernière valeur correspond à l'item complet.
- On n'utilise plus la partie ayant permis d'ordonner, elle a joué son rôle
- et n'est plus utile.
-
- C'est un compromis espace mémoire + complexité vs. temps. Plus simple et
- rapide mais on est obligé de dupliquer la liste originale.
-
-
- Ordonner avec keys
- ------------------
-
- Python 2.4 a introduit un nouvel argument à la méthode ``sort`` des listes,
- "key", qui permet de spécifier une fonction à un argument qui est utilisée
- pour comparer chaque élément d'une liste avec les autres. Par exemple :
-
- def my_key(item):
- return (item[1], item[3])
-
- to_sort.sort(key=my_key)
-
- La fonction ``my_key`` va être appelée une fois par item de la liste ``to_sort``.
-
- Vous pouvez utiliser votre propre fonction ou utiliser une fonction existante
- qui ne prend qu'un seul argument :
-
- * ``str.lower`` pour ordonner alphabétiquement sans tenir compte de la casse.
- * ``len`` pour ordonner selon la taille des items (chaînes de caractères ou containers).
- * ``int`` ou ``float`` pour ordonner numériquement avec des valeurs qui sont
- des chaînes de caractères comme "2", "123", "35".
-
-
- Generators
- ----------
-
- On a déjà vu les generator expressions. On peut créer nos propres generators,
- comme des fonctions :
-
- def my_range_generator(stop):
- value = 0
- while value < stop:
- yield value
- value += 1
-
- for i in my_range_generator(10):
- do_something(i)
-
- Le mot-clé ``yield`` transforme une fonction en generator. Lorsque vous
- appelez une fonction generator, au lieu d'exécuter le code directement,
- Python retourne un objet generator, qui est un itérateur. Il a une méthode
- ``next``. Les boucles ``for`` appellent la méthode ``next`` de l'itérateur,
- jusqu'à ce qu'une exception du type ``StopIteration`` soit levée.
- Vous pouvez lever l'exception ``StopIteration`` explicitement ou de manière
- implicite comme dans le code ci-dessous.
-
- Les générateurs peuvent simplifier la manière de gérer les
- séquences/itérateurs, car on n'a pas besoin de créer des listes
- intermédiaires. Ça ne génère qu'une valeur à la fois.
-
- Voici comment la boucle ``for`` fonctionne réellement. Python analyse la
- séquence déclarée avec le mot-clé ``in``. Si c'est un simple container (comme
- une liste, un tuple, un dictionnaire, un set ou un container défini par
- l'utilisateur) Python le converti en itérateur. Si c'est déjà un itérateur,
- Python ne fait rien.
-
- Python appelle ensuite de manière itérative la méthode ``next`` de
- l'itérateur, assignant la valeur retournée au compteur de la boucle (``i``
- dans notre cas), et exécute le code indenté. C'est répété, encore et encore
- jusqu'à ce que ``StopIteration`` soit levée, ou qu'un ``break`` soit exécuté.
-
- Une boucle ``for`` peut être dotée d'un ``else``, au sein de laquelle le
- code est exécuté si rien ne s'est produit dans la boucle for, mais **non**
- après un ``break``. Cette distinction permet de faire des choses élégantes.
- ``else`` est rarement utilisé avec la boucle ``for`` mais peut s'avérer
- très puissant lorsque la logique correspond à ce que vous souhaitez faire.
-
- Par exemple, si on doit vérifier qu'une condition est toujours remplie par
- tous les items d'une liste :
-
- for item in sequence:
- if condition(item):
- break
- else:
- raise Exception('Condition not satisfied.')
-
-
- Exemple de generator
- --------------------
-
- Filtrer les colonnes vides à partir d'un fichier CSV (ou des items d'une liste) :
-
- def filter_rows(row_iterator):
- for row in row_iterator:
- if row:
- yield row
-
- data_file = open(path, 'rb')
- irows = filter_rows(csv.reader(data_file))
-
-
- Lire les lignes d'un fichier de données
- ---------------------------------------
-
- datafile = open('datafile')
- for line in datafile:
- do_something(line)
-
- C'est possible car les fichiers sont dotés d'une méthode ``next``, comme
- d'autres itérateurs : les listes, les tuples, les dictionnaires (pour leurs
- clés), les generators.
-
- Il y a un piège ici : étant donnée la façon dont sont mises en cache les
- données, vous ne pouvez pas utiliser à la fois les méthodes ``.next`` et
- ``.read*`` à moins que vous n'utilisez Python 2.5+.
-
-
- EAFP vs. LBYL
- -------------
-
- Il est plus facile de demander le pardon que la permission (EAFP) vs. analyser
- avant d'échouer (LBYL). Généralement EAFP est préféré, mais pas toujours.
-
- * Duck typing
- Si ça marche comme un canard, parle comme un canard et ressemble à un
- canard : c'est un canard.
- * Exceptions
- Essayez de forcer le type si un objet doit être d'un type particulier.
- Si ``x`` doit être une chaîne de caractères pour que votre code
- fonctionne, pourquoi ne pas appeler ``str(x)`` au lieu d'essayer quelque`
- chose comme ``isinstance(x, str)``.
-
-
- Exemple EAFP ``try/except``
- ---------------------------
-
- Vous pouvez encapsuler vos exceptions dans un bloc ``try/except`` pour
- pour récupérer les erreurs et vous allez probablement arriver à une solution
- qui est beaucoup plus générale que si vous aviez essayer d'anticiper chaque
- cas.
-
- try:
- return str(x)
- except TypeError:
- ...
-
- Note: Spécifiez toujours l'exception à attraper. N'utilisez jamais
- ``except`` tout seul. Sinon ``except`` va cacher d'autres exceptions qui
- risquent d'être levées rendant votre code très difficile à debugger.
-
-
- Importer
- --------
-
- from module import *
-
- Vous avez probablement déjà vu cette manière de faires des imports avec une
- "étoile". Vous l'appréciez peut-être. **Ne l'utilisez pas.**
-
- Pour paraphraser un exemple très connu :
-
- > LUKE: Est-ce que ``from module import *`` est meilleur que des imports explicites ?
- > YODA: Non, pas meilleur. Plus rapide, plus simple, plus séduisant.
- > LUKE: Mais comment saurais-je pourquoi les imports explicites sont meilleurs que les formes étoilées ?
- > YODA: Tu sauras lorsque ton code dans 6 mois tu essayeras de lire.
-
- Les imports étoilés sont le mauvais côté de la Force en Python.
-
- Les imports de type ``from module import *`` polluent votre espace de nom.
- Vous allez avoir des choses que vous n'attendiez pas. Vous pouvez avoir des
- conflits avec les noms que vous avez défini localement. Vous n'allez plus
- savoir d'où viennent certains noms. Bien que ce soit un raccourci pratique,
- ça ne doit pas arriver en production.
-
- Morale : **n'utilisez pas d'imports étoilés !**
-
- Il est bien meilleur de référencer les noms à partir de leurs modules :
-
- import module
- module.name
-
- importer un module avec un nom plus court si nécessaire (avec ``alias``) :
-
- import long_module_name as mod
- mod.name
-
- ou importer juste les noms dont vous avez besoin de manière explicite :
-
- from module import name
- name
-
- Notez qu'il est nécessaire d'utiliser "reload()" sur un module lorsque vous
- utilisez le prompt interactif si vous éditez celui-ci.
-
-
- Modules et scripts
- ------------------
-
- Pour faire à la fois un module importable et un script exécutable :
-
- if __name__ == '__main__':
- # script code here
-
- Lorsqu'il est importé, un attribut``__name__`` est setté, correspondant au
- nom du fichier du module, sans ".py". Le code ci-dessus ne va donc pas être
- lancé lors d'un import. Lorsqu'il est lancé comme un script, l'attribut
- ``__name__`` est setté à "__main__" et le script va être exécuté.
-
- Excepté pour certains cas spéciaux, vous ne devriez placer aucun code
- important au plus haut niveau. Placez votre code dans des fonctions, classes,
- méthodes et protégez le avec ``if __name__ == '__main__'``.
-
-
- Structure d'un module
- ---------------------
-
- """module docstring"""
-
- # imports
- # constants
- # exception classes
- # interface functions
- # classes
- # internal functions & classes
-
- def main(...):
- ...
-
- if __name__ == '__main__':
- status = main()
- sys.exit(status)
-
- C'est la façon dont un module devrait être structuré.
-
-
- Utilisation de la ligne de commande
- -----------------------------------
-
- Exemple (*Note du traducteur* : [j'ai déjà parlé de ça aussi](https://larlet.fr/david/biologeek/archives/2006218-un-template-python-pour-parser-des-arguments/)) :
-
- #!/usr/bin/env python
-
- """
- Module docstring.
- """
-
- import sys
- import optparse
-
- def process_command_line(argv):
- """
- Return a 2-tuple: (settings object, args list).
- `argv` is a list of arguments, or `None` for ``sys.argv[1:]``.
- """
- if argv is None:
- argv = sys.argv[1:]
-
- # initialize the parser object:
- parser = optparse.OptionParser(
- formatter=optparse.TitledHelpFormatter(width=78),
- add_help_option=None)
-
- # define options here:
- parser.add_option( # customized description; put --help last
- '-h', '--help', action='help',
- help='Show this help message and exit.')
-
- settings, args = parser.parse_args(argv)
-
- # check number of arguments, verify values, etc.:
- if args:
- parser.error('program takes no command-line arguments; '
- '"%s" ignored.' % (args,))
-
- # further process settings & args if necessary
-
- return settings, args
-
- def main(argv=None):
- settings, args = process_command_line(argv)
- # application code here, like:
- # run(settings, args)
- return 0 # success
-
- if __name__ == '__main__':
- status = main()
- sys.exit(status)
-
-
- Packages
- --------
-
- package/
- __init__.py
- module1.py
- subpackage/
- __init__.py
- module2.py
-
- * Utilisés pour organiser un projet.
- * Réduisent le nombre d'entrées lors du chargement.
- * Réduisent les conflits en cas d'imports.
-
- Exemple :
-
- import package.module1
- from package.subpackage import module2
- from package.subpackage.module2 import name
-
- En Python 2.5 on a maintenant les imports absolus et relatifs via un import
- du futur :
-
- from __future__ import absolute_import
-
- Je n'ai pas encore eu l'occasion de tester ça moi-même, on va donc couper
- court à toute discussion à ce sujet.
-
-
- Simple is Better Than Complex
- -----------------------------
-
- > Débugger est deux fois plus difficile que d'écrire du code en premier jet.
- > De plus, si vous écrivez le code aussi intelligemment que possible, vous
- > êtes, par définition, pas assez intelligent pour le débugger.
- >
- > -- Brian W. Kernighan, co-auteur de *The C Programming Language*
- > et le "K" dans "AWK"
-
- En d'autres termes, gardez vos programmes simples !
-
- Ne réinventez pas la roue
- -------------------------
-
- Avant d'écrire une seule ligne de code,
-
- * Vérifiez que cela n'est pas dans la bibliothèque standard de Python.
- * Vérifiez que cela n'est pas dans le [Python Package Index](http://cheeseshop.python.org/pypi) (the "Cheese Shop")
- * Cherchez sur le web. *Google is your friend.*
-
-
- Retour du traducteur
- --------------------
-
- Pour terminer, et si vous souhaitez aller plus loin, une
- [excellente présentation sur les générateurs](http://www.dabeaz.com/generators/)
- que je n'aurais malheureusement pas le temps de traduire permet d'envisager la
- programmation Python d'une manière tout à fait différente. Elle est orientée
- administration système mais elle peut vraiment être appliquée à de nombreux cas.
- Et si vous voulez comprendre l'intérêt des décorateurs,
- [un bon exemple](http://avinashv.net/2008/04/python-decorators-syntactic-sugar/)
- vaut mieux que tous les discours. Mangez du Python, c'est bon pour la santé !
-
|