12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529 |
- <!doctype html>
- <html lang=fr>
- <head>
- <!-- Always define the charset before the title -->
- <meta charset=utf-8>
- <title>Bonnes pratiques et astuces Python — Biologeek — David Larlet</title>
- <!-- Define a viewport to mobile devices to use - telling the browser to assume that the page is as wide as the device (width=device-width) and setting the initial page zoom level to be 1 (initial-scale=1.0) -->
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
- <!-- Fake favicon, to avoid extra request to the server -->
- <link rel="icon" href="data:;base64,iVBORw0KGgo=">
- <link type="application/atom+xml" rel="alternate" title="Feed" href="/david/log/" />
- <link rel="manifest" href="/manifest.json">
-
- <link rel="stylesheet" href="/static/david/css/larlet-david-_J6Rv.css" data-instant-track />
-
- <noscript>
- <style type="text/css">
- /* Otherwise fonts are loaded by JS for faster initial rendering. See scripts at the bottom. */
- body {
- font-family: 'EquityTextB', serif;
- }
- h1, h2, h3, h4, h5, h6, time, nav a, nav a:link, nav a:visited {
- font-family: 'EquityCapsB', sans-serif;
- font-variant: normal;
- }
- </style>
- </noscript>
-
- <!-- Canonical URL for SEO purposes -->
- <link rel="canonical" href="https://larlet.fr/david/biologeek/archives/20080511-bonnes-pratiques-et-astuces-python">
-
- </head>
- <body>
- <div>
-
- <header>
- <nav>
- <p>
- <small>
- Je suis <a href="/david/" title="Profil public">David Larlet</a>, <a href="/david/pro/" title="Activité professionnelle">artisan</a> du web qui vous <a href="/david/pro/accompagnement/" title="Activité d’accompagnement">accompagne</a><span class="more-infos"> dans l’acquisition de savoirs pour concevoir des <a href="/david/pro/produits-essentiels/" title="Qu’est-ce qu’un produit essentiel ?">produits essentiels</a></span>. <span class="more-more-infos">Discutons ensemble d’une <a href="/david/pro/devis/" title="En savoir plus">non-demande de devis</a>.</span> Je partage ici mes <a href="/david/blog/" title="Expériences bienveillantes">réflexions</a> et <a href="/david/correspondances/2017/" title="Lettres hebdomadaires">correspondances</a>.
- </small>
- </p>
- </nav>
- </header>
-
-
- <section>
- <h1 property="schema:name">Bonnes pratiques et astuces Python</h1>
- <article typeof="schema:BlogPosting">
- <div property="schema:articleBody">
- <img src="/static/david/biologeek/images/logos/python_nouveau.png" alt="vignette" style="float:left; margin: 0.5em 1em;" property="schema:thumbnailUrl" />
- <p>Ça faisait un moment que je n'avais pas parlé des
- bonnes pratiques Python mais l'approche de
- <a href="http://fr.pycon.org/">Pycon fr</a> (où je présenterai Django :
- <a href="http://fr.pycon.org/programme/pourquoi-django/">le pourquoi</a> et
- <a href="http://fr.pycon.org/programme/django-au-quotidien-qualite-et-performances/">le comment</a>
- le 18 mai), l'événement Python incontournable avec
- <a href="http://fr.pycon.org/programme/">un programme des plus alléchants</a>, m'a bien
- motivé pour effectuer la traduction de
- l'<a href="http://python.net/~goodger/projects/pycon/2007/idiomatic/">une des meilleures présentation</a>
- par <a href="http://python.net/~goodger/">David Goodger</a> que je connaisse qui remet
- les bonnes pratiques Python à plat, ce qui est toujours bon avant d'aller plus loin.</p>
- <h2>La lisibilité est importante</h2>
- <blockquote>
- <p>Les programmes doivent être écrits pour être lus par des gens et
- accidentellement exécutés par les machines.</p>
- <p>-- Abelson & Sussman, <em>Structure and Interpretation of Computer Programs</em></p>
- </blockquote>
- <p>Essayez de rendre vos programmes faciles à lire et évidents.</p>
- <h2>PEP 8 : Style Guide pour le code Python</h2>
- <p>Une lecture immanquable : <a href="http://www.python.org/dev/peps/pep-0008/">http://www.python.org/dev/peps/pep-0008/</a>
- (PEP = Python Enhancement Proposal)</p>
- <p>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.</p>
- <p>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.</p>
- <p>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.</p>
- <h2>Whitespace 1</h2>
- <ul>
- <li>4 espaces par niveau d'indentation.</li>
- <li>Pas de tabs.</li>
- <li>Ne <strong>jamais</strong> mixer des tabs et des espaces.</li>
- <li>Un saut de ligne entre les fonctions.</li>
- <li>Deux sauts de ligne entre les classes.</li>
- </ul>
- <h2>Whitespace 2</h2>
- <ul>
- <li>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.</li>
- <li>Mettez des espaces autour des assignements et des comparaisons (excepté
- pour les arguments d'une liste).</li>
- <li>Pas d'espace aux ouvertures/fermetures de parenthèses ou juste avant une
- liste d'arguments.</li>
- <li>Pas d'espace en ouverture/fermeture de docstrings.<pre><code>def make_squares(key, value=0):
- """Return a dictionary and a list..."""
- d = {key: value}
- l = [key, value]
- return d, l
- </code></pre>
- </li>
- </ul>
- <h2>Nommage</h2>
- <ul>
- <li><code>joined_lower</code> pour les fonctions, méthodes et attributs</li>
- <li><code>joined_lower</code> ou <code>ALL_CAPS</code> pour les constantes</li>
- <li><code>StudlyCaps</code> pour les classes</li>
- <li><code>camelCase</code> <strong>seulement</strong> pour suivre des conventions pré-existantes</li>
- <li>Attributs: <code>interface</code>, <code>_internal</code>, <code>__private</code></li>
- </ul>
- <p>Mais essayez d'éviter la forme <code>__privée</code>. Je ne l'utilise jamais.
- Faites moi confiance. Si vous l'utilisez, vous le regretterez plus tard.</p>
- <h2>Longues lignes et continuité</h2>
- <p>Garder une taille de ligne inférieure à 80 caractères.</p>
- <p>Utilisez la continuité implicite des lignes au sein des
- parenthèses/crochets/accolades :</p>
- <pre><code>def __init__(self, first, second, third,
- fourth, fifth, sixth):
- output = (first + second + third
- + fourth + fifth + sixth)
- </code></pre>
- <p>Utilisez les backslashs en dernier recours :</p>
- <pre><code>VeryLong.left_hand_side \
- = even_longer.right_hand_side()
- </code></pre>
- <p>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.</p>
- <h2>Longues chaînes de caractères</h2>
- <p>Les chaînes de caractères adjacentes sont concaténées par le parser:</p>
- <pre><code>>>> print 'o' 'n' "e"
- one
- </code></pre>
- <p>Les espaces entre les chaînes ne sont pas requis, mais aident à la
- lisibilité. Tous les types de quotes sont utilisable :</p>
- <pre><code>>>> print 't' r'\/\/' """o"""
- t\/\/o
- </code></pre>
- <p>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.</p>
- <p>Notez que les chaînes de caractères nommées <strong>ne sont pas</strong> concaténées :</p>
- <pre><code>>>> a = 'three'
- >>> b = 'four'
- >>> a b
- File "<stdin>", line 1
- a b
- ^
- SyntaxError: invalid syntax
- </code></pre>
- <p>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.</p>
- <pre><code>text = ('Long strings can be made up '
- 'of several shorter strings.')
- </code></pre>
- <p>Les parenthèses autorisent la continuité implicite des lignes.
- Les chaînes de caractères sur plusieurs lignes utilisent les triple quotes :</p>
- <pre><code>"""Triple
- double
- quotes"""
-
- '''\
- Triple
- single
- quotes\
- '''
- </code></pre>
- <p>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.</p>
- <h2>Déclarations</h2>
- <p>Bon :</p>
- <pre><code>if foo == 'blah':
- do_something()
- do_one()
- do_two()
- do_three()
- </code></pre>
- <p>Mauvais :</p>
- <pre><code>if foo == 'blah': do_something()
- do_one(); do_two(); do_three()
- </code></pre>
- <p>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".</p>
- <p>Les déclarations multiples sur une même ligne sont une torture.
- En Python, <em>la lisibilité compte</em>.</p>
- <h2>Docstrings et Commentaires</h2>
- <p>Docstrings = <strong>Comment utiliser</strong> le code</p>
- <p>Commentaires = <strong>Pourquoi</strong> (rationnel) et <strong>comment le code fonctionne</strong></p>
- <p>Les docstrings expliquent <strong>comment</strong> utiliser le code et sont là pour
- <strong>les utilisateurs</strong> de votre code. Quelques usages :</p>
- <ul>
- <li>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.</li>
- <li>Décrire les paramètres attendus, les valeurs retournées et les exceptions
- levées.</li>
- <li>Si la méthode est fortement couplée à un seul appelant, mentionner
- la fonction appelante (attention au fait que celle-ci puisse changer).</li>
- </ul>
- <p>Les commentaires expliquent <strong>pourquoi</strong> et sont pour les mainteneurs de
- votre code. Examples incluant des notes pour vous-même, comme :</p>
- <pre><code># !!! BUG: ...
-
- # !!! FIX: This is a hack
-
- # ??? Why is this here?
- </code></pre>
- <p>Les deux types sont de <strong>votre</strong> ressort donc écrivez de bonnes docstrings
- et de bons commentaires !</p>
- <p>Les docstrings sont utiles pour un usage interactif (<code>help()</code>) et pour
- les systèmes d'auto-documentation.</p>
- <p>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.</p>
- <p>Il y a <a href="http://www.python.org/dev/peps/pep-0257/">un PEP entier consacré aux docstrings, PEP 257, "Docstring
- Conventions"</a>.</p>
- <h2>La pratique a raison de la théorie</h2>
- <p>Il y a toujours des exceptions. Issu du PEP 8 :</p>
- <blockquote>
- <p>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 :</p>
- <p>(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.</p>
- <p>(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).</p>
- </blockquote>
- <p>... mais la pratique ne doit pas réduire la théorie à néant !</p>
- <p>On plonge maintenant au cœur du tutoriel : les astuces. On va commencer avec
- les plus faciles et augmenter progressivement le niveau.</p>
- <h2>Variables intermédiaires</h2>
- <p>Dans les autres langages :</p>
- <pre><code>temp = a
- a = b
- b = temp
- </code></pre>
- <p>En Python :</p>
- <pre><code>b, a = a, b
- </code></pre>
- <p>Vous l'avez peut-être déjà rencontré mais savez vous comment ça fonctionne ?</p>
- <ul>
- <li>La <strong>virgule</strong> est la syntaxe de construction du tuple.</li>
- <li>Un tuple est créé à droite (tuple packing).</li>
- <li>Un tuple en est la cible à gauche (tuple unpacking).</li>
- </ul>
- <p>La partie à droite est <strong>unpackée</strong> dans les noms de tuple de la partie à
- gauche.</p>
- <p>D'autres exemples:</p>
- <pre><code>>>> l =['David', 'Pythonista', '+1-514-555-1234']
- >>> name, title, phone = l
- >>> name
- 'David'
- >>> title
- 'Pythonista'
- >>> phone
- '+1-514-555-1234'
- </code></pre>
- <p>Utile dans les boucles sur des données structurées (la variable <code>l</code>
- ci-dessus a été conservée) :</p>
- <pre><code>>>> people = [l, ['Guido', 'BDFL', 'unlisted']]
- >>> for (name, title, phone) in people:
- ... print name, phone
- ...
- David +1-514-555-1234
- Guido unlisted
- </code></pre>
- <p>Chaque item de <code>people</code> est unpacké dans le tuple <code>(name, title, phone)</code>.</p>
- <p>Il est aussi possible de faire le chemin inverse, il faut juste s'assurer
- d'avoir la même structure à droite et à gauche :</p>
- <pre><code>>>> david, (gname, gtitle, gphone) = people
- >>> gname
- 'Guido'
- >>> gtitle
- 'BDFL'
- >>> gphone
- 'unlisted'
- >>> david
- ['David', 'Pythonista', '+1-514-555-1234']
- </code></pre>
- <h2>Aller plus loin avec les tuples</h2>
- <p>On a vu que la <strong>virgule</strong> était le constructeur du tuple, pas les
- parenthèses. Par exemple :</p>
- <pre><code>>>> 1,
- (1,)
- </code></pre>
- <p>L'interpréteur Python montre les parenthèses pour que ce soit plus clair
- et je vous conseille de faire de même :</p>
- <pre><code>>>> (1,)
- (1,)
- </code></pre>
- <p>Mais n'oubliez pas la virgule !</p>
- <pre><code>>>> (1)
- 1
- </code></pre>
- <p>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 :</p>
- <pre><code>>>> ()
- ()
-
- >>> tuple()
- ()
- </code></pre>
- <p>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 :</p>
- <pre><code>>>> value = 1,
- >>> value
- (1,)
- </code></pre>
- <p>Donc si vous vous retrouvez avec un tuple alors que vous ne vous y
- attendiez pas, cherchez la virgule ! (<em>Note du traducteur</em> : 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à).</p>
- <h2>Le "_" interactif</h2>
- <p>C'est une fonctionnalité très utile que peu de développeurs connaissent.
- (<em>Note du traducteur</em> : bien entendu
- <a href="https://larlet.fr/david/biologeek/archives/20060425-python-et-underscore/">vous n'en faites pas partie</a>
- et vous connaissez les dangers associés.)</p>
- <p>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, <code>_</code> (un underscore) :</p>
- <pre><code>>>> 1 + 1
- 2
- >>> _
- 2
- </code></pre>
- <p><code>_</code> stocke la dernière valeur <em>affichée</em>.</p>
- <p>Lorsqu'un résultat vaut <code>None</code>, rien n'est affiché, donc <code>_</code> ne change
- pas. C'est normal !</p>
- <p>Ça ne marche que dans un interpréteur interactif, pas dans un module.</p>
- <p>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 :</p>
- <pre><code>>>> import math
- >>> math.pi / 3
- 1.0471975511965976
- >>> angle = _
- >>> math.cos(angle)
- 0.50000000000000011
- >>> _
- 0.50000000000000011
- </code></pre>
- <h2>Construction de chaînes de caractères</h2>
- <p>Commençons avec une liste de chaînes de caractères :</p>
- <pre><code>colors = ['red', 'blue', 'green', 'yellow']
- </code></pre>
- <p>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...</p>
- <p>Ne faites pas :</p>
- <pre><code>result = ''
- for s in colors:
- result += s
- </code></pre>
- <p>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.</p>
- <p>Faites plutôt ceci :</p>
- <pre><code>result = ''.join(colors)
- </code></pre>
- <p>La méthode <code>join()</code> fait toute la copie en une seule passe.</p>
- <p>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 <strong>va</strong>
- faire la différence.</p>
- <h2>Construire des chaînes, solutions 1</h2>
- <p>Voici quelques techniques pour utiliser la méthode <code>join()</code>.</p>
- <p>Si vous voulez un espace comme séparateur :</p>
- <pre><code>result = ' '.join(colors)
- </code></pre>
- <p>ou une virgule et un espace :</p>
- <pre><code>result = ', '.join(colors)
- </code></pre>
- <p>voici un cas courant d'utilisation :</p>
- <pre><code>colors = ['red', 'blue', 'green', 'yellow']
- print 'Choose', ', '.join(colors[:-1]), \
- 'or', colors[-1]
- </code></pre>
- <p>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" (<code>[:-1]</code>)
- retourne tout sauf la dernière valeur, que l'on peut concaténer avec nos
- virgules.</p>
- <p>Bien sûr, ce code ne fonctionnera pas avec les cas particuliers comme une
- liste de taille 0 ou 1. Ce qui retourne :</p>
- <pre><code>Choose red, blue, green or yellow
- </code></pre>
- <h2>Construire des chaînes, solutions 2</h2>
- <p>Vous avez besoin d'appliquer une fonction pour générer les chaînes initiales :</p>
- <pre><code>result = ''.join(fn(i) for i in items)
- </code></pre>
- <p>Ça utilise une <em>generator expression</em>, dont on parlera plus tard.</p>
- <p>Si vous devez modifier les chaînes de manière incrémentale, commencez par
- les stocker dans une liste pour commencer :</p>
- <pre><code>items = []
- ...
- items.append(item) # de nombreuses fois
- ...
- # une fois la liste complétée
- result = ''.join(fn(i) for i in items)
- </code></pre>
- <p>On accumule les parties de la liste afin de pouvoir appliquer le <code>join</code>,
- ce qui est plus rapide.</p>
- <h2>Utilisez <code>in</code> lorsque c'est possible (1)</h2>
- <p>Bon :</p>
- <pre><code>for key in d:
- print key
- </code></pre>
- <ul>
- <li><code>in</code> est généralement plus rapide.</li>
- <li>Ce pattern marche aussi pour des items dans des containers arbitraires
- (comme les listes, les tuples ou les tests).</li>
- <li><code>in</code> est aussi un opérateur (comme on va le voir).</li>
- </ul>
- <p>Mauvais :</p>
- <pre><code>for key in d.keys():
- print key
- </code></pre>
- <p>C'est limité aux objects ayant une méthode <code>keys()</code>.</p>
- <h2>Utilisez <code>in</code> lorsque c'est possible (2)</h2>
- <p>Mais <code>.keys()</code> est <strong>nécessaire</strong> lorsque vous modifiez le dictionnaire :</p>
- <pre><code>for key in d.keys():
- d[str(key)] = d[key]
- </code></pre>
- <p><code>d.keys()</code> crée une liste statique des clés du dictionnaire. Sinon, vous
- allez lever une exception "RuntimeError: dictionary changed size during iteration".</p>
- <p>Utilisez <code>key in dict</code>, et non <code>dict.has_key()</code> :</p>
- <pre><code># faites ça :
- if key in d:
- ...do something with d[key]
-
- # mais pas ça :
- if d.has_key(key):
- ...do something with d[key]
- </code></pre>
- <p><code>in</code> est ici utilisé comme un opérateur.</p>
- <h2>La méthode <code>get</code> des dictionnaires</h2>
- <p>On doit souvent initialiser les entrées d'un dictionnaire avant de les utiliser:</p>
- <p>Voici la manière naïve de faire :</p>
- <pre><code>navs = {}
- for (portfolio, equity, position) in data:
- if portfolio not in navs:
- navs[portfolio] = 0
- navs[portfolio] += position * prices[equity]
- </code></pre>
- <p><code>dict.get(key, default)</code> permet de ne pas avoir à se soucier du test :</p>
- <pre><code>navs = {}
- for (portfolio, equity, position) in data:
- navs[portfolio] = (navs.get(portfolio, 0)
- + position * prices[equity])
- </code></pre>
- <p>Beaucoup mieux.</p>
- <h2>La méthode <code>setdefault</code> des dictionnaires (1)</h2>
- <p>Ici on doit initialiser les valeurs d'un dictionnaire mutables. Chaque valeur
- du dictionnaire sera une liste. Voici la manière naïve :</p>
- <pre><code>equities = {}
- for (portfolio, equity) in data:
- if portfolio in equities:
- equities[portfolio].append(equity)
- else:
- equities[portfolio] = [equity]
- </code></pre>
- <p><code>dict.setdefault(key, default)</code> s'occupe de ça de manière beaucoup plus rapide :</p>
- <pre><code>equities = {}
- for (portfolio, equity) in data:
- equities.setdefault(portfolio, []).append(equity)
- </code></pre>
- <p><code>dict.setdefault()</code> 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.</p>
- <p>Le seul problème avec <code>dict.setdefault()</code> 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.</p>
- <p>Si la valeur par défaut <strong>est</strong> coûteuse à calculer, vous devriez plutôt
- utiliser la classe <code>defaultdict</code>.</p>
- <h2>La méthode <code>setdefault</code> des dictionnaires (2)</h2>
- <p>On va voir qu'il est possible d'utiliser <code>setdefault</code> pour déclarer une valeur par défaut :</p>
- <pre><code>navs = {}
- for (portfolio, equity, position) in data:
- navs.setdefault(portfolio, 0)
- navs[portfolio] += position * prices[equity]
- </code></pre>
- <p>La méthode <code>setdefault</code> 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
- <code>setdefault</code>, la valeur n'est initialisée que si elle n'existe pas déjà.</p>
- <h2><code>defaultdict</code></h2>
- <p>Nouveau avec Python 2.5.</p>
- <p><code>defaultdict</code> est nouveau dans Python 2.5, il fait partie du module
- <code>collections</code>. <code>defaultdict</code> est identique aux dictionnaires classiques,
- excepté pour deux cas :</p>
- <ul>
- <li>il prend un premier argument optionnel : une fonction factory par défaut</li>
- <li>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.</li>
- </ul>
- <p>Il y a deux manières d'accéder à <code>defaultdict</code> :</p>
- <ul>
- <li>
- <p>importer le module <code>collections</code> et l'appeler à travers le module :</p>
- <pre><code>import collections
- d = collections.defaultdict(...)
- </code></pre>
- </li>
- <li>
- <p>ou importer <code>defaultdict</code> directement :</p>
- <pre><code>from collections import defaultdict
- d = defaultdict(...)
- </code></pre>
- </li>
- </ul>
- <p>Voici l'exemple déjà traité, où chaque valeur du dictionnaire fois être
- initialisé pour être une liste vide, réécrit en utilisant <code>defaultdict</code> :</p>
- <pre><code>from collections import defaultdict
-
- equities = defaultdict(list)
- for (portfolio, equity) in data:
- equities[portfolio].append(equity)
- </code></pre>
- <p>Il n'y a plus d'astuce ici. Dans ce cas, la fonction factory par défaut est
- <code>list</code>, ce qui retourne une liste vide.</p>
- <p>C'est la manière d'avoir un dictionnaire avec les valeurs par défaut à 0,
- utilisez <code>int</code> comme factory :</p>
- <pre><code>navs = defaultdict(int)
- for (portfolio, equity, position) in data:
- navs[portfolio] += position * prices[equity]
- </code></pre>
- <p>Il faut faire attention à <code>defaultdict</code> quand même. Vous ne pouvez pas
- utiliser l'exception <code>KeyError</code> sur un dictionnaire initialisé avec <code>defaultdict</code>.
- Vous devez utiliser la condition "key in dict" si vous voulez vérifier
- l'existence d'une clé de manière spécifique.</p>
- <h2>Construire et scinder des dictionnaires</h2>
- <p>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 :</p>
- <pre><code>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'}
- </code></pre>
- <p>L'inverse est trivial bien sûr :</p>
- <pre><code> >>> pythons.keys()
- ['John', 'Michael', 'Eric', 'Terry']
- >>> pythons.values()
- ['Cleese', 'Palin', 'Idle', 'Gilliam']
- </code></pre>
- <p>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.</p>
- <h2>Tester des valeurs vraies</h2>
- <p>Il est élégant et rapide de tirer partie des avantages de Python en ce qui
- concerne les valeurs booléennes :</p>
- <pre><code># faites ça : # et pas ça :
- if x: if x == True:
- pass pass
- </code></pre>
- <p>Test d'une liste :</p>
- <pre><code># faites ça : # et pas ça :
- if items: if len(items) != 0:
- pass pass
-
- # et surtout pas ça :
- if items != []:
- pass
- </code></pre>
- <h2>Valeurs vraies</h2>
- <p>Les noms <code>True</code> et <code>False</code> sont des instances intrinsèques à Python de type
- <code>bool</code>, des valeurs booléennes. Comme <code>None</code>, il n'existe qu'une seule
- instance de chaque.</p>
- <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>
-
- <p>Voici par exemple un objet qui est toujours vrai :</p>
- <pre><code>>>> class C:
- ... pass
- ...
- >>> o = C()
- >>> bool(o)
- True
- >>> bool(C)
- True
- </code></pre>
- <p>Pour contrôler la valeur booléenne d'une instance ou d'une classe définie,
- utilisez les méthodes spéciales <code>__nonzero__</code> ou <code>__len__</code>. Utilisez
- <code>__len__</code> si votre classe est un container qui a une taille :</p>
- <pre><code>class MyContainer(object):
-
- def __init__(self, data):
- self.data = data
-
- def __len__(self):
- """Return my length."""
- return len(self.data)
- </code></pre>
- <p>Si votre classe n'est pas un container, utilisez <code>__nonzero__</code> :</p>
- <pre><code>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)
- </code></pre>
- <p>En Python 3.0, <code>__nonzero__</code> a été renommé <code>__bool__</code> afin d'être consistant
- avec le type <code>bool</code> natif. Pour être compatible, ajoutez ceci à la définition
- de votre classe :</p>
- <pre><code>__bool__ = __nonzero__
- </code></pre>
- <h2>Index & Item (1)</h2>
- <p>Voici une manière élégante de vous épargner quelques lignes si vous avez
- besoin d'une liste de mots :</p>
- <pre><code>>>> items = 'zero one two three'.split()
- >>> print items
- ['zero', 'one', 'two', 'three']
- </code></pre>
- <p>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 :</p>
- <pre><code> - ou -
- i = 0
- for item in items: for i in range(len(items)):
- print i, item print i, items[i]
- i += 1
- </code></pre>
- <h2>Index & Item (2): <code>enumerate</code></h2>
- <p>La fonction <code>enumerate</code> prend une liste et retourne des paires (index, item) :</p>
- <pre><code>>>> print list(enumerate(items))
- [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')]
- </code></pre>
- <p>Il est nécessaire d'avoir recours à une <code>list</code> pour afficher les résultats
- car <code>enumerate</code> est une fonction fainéante, générant un item (une paire) à
- la fois, seulement lorsqu'il est demandé. Une boucle <code>for</code> nécessite un tel
- mécanisme. <code>enumerate</code> est un exemple de <strong>générateur</strong> dont on parlera plus
- tard des détails. <code>print</code> 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.</p>
- <p>Notre boucle devient beaucoup plus simple :</p>
- <pre><code>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
- </code></pre>
- <p>La version avec <code>enumerate</code> est plus courte et plus simple que la version
- de gauche, et plus facile à lire que les deux autres.</p>
- <p>Un exemple montrant que la fonction <code>enumerate</code> retourne un itérateur
- (un générateur est une sorte d'itérateur) :</p>
- <pre><code>>>> 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
- </code></pre>
- <h2>Les autres langages ont des "variables"</h2>
- <p>Dans de nombreux autres langages, assigner une variable revient à mettre une
- valeur dans une boîte.</p>
- <pre><code>int a = 1;
- </code></pre>
- <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>
-
- <p>La boîte "a" contient maintenant un integer 1.</p>
- <p>Assigner une autre valeur à la même variable remplace le contenu de la boîte :</p>
- <pre><code>a = 2;
- </code></pre>
- <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>Maintenant la boîte "a" contient un integer 2.</p>
- <p>Assigner une variable à une autre crée une copie de la valeur et la met
- dans une nouvelle boîte :</p>
- <pre><code>int b = a;
- </code></pre>
- <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>
-
- <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.</p>
- <h2>Python a des "noms"</h2>
- <p>En Python, un "nom" ou "identifiant" est comme une étiquette attachée à un
- objet.</p>
- <pre><code>a = 1
- </code></pre>
- <p>
- <img
- src="/static/david/biologeek/images/python_astuces_bonnes_pratiques/a1tag.png"
- alt="Tag 1 a"
- style="margin: 0pt auto; display: block;"/>
- </p>
-
- <p>Ici, un objet integer 1 a une étiquette appelée "a".</p>
- <p>Si on réassigne "a", on déplace juste l'étiquette sur un autre objet :</p>
- <pre><code>a = 2
- </code></pre>
- <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>
-
- <p>Maintenant le nom "a" est attaché à un objet entier 2.</p>
- <p>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.)</p>
- <p>Si l'on assigne un nom à un autre, on attache juste une autre étiquette à
- un objet existant :</p>
- <pre><code>b = a
- </code></pre>
- <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>
-
- <p>Le nom "b" est juste une seconde étiquette attachée au même objet que "a".</p>
- <p>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.</p>
- <p>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:</p>
- <h2>Valeurs de paramètres par défaut</h2>
- <p>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.</p>
- <pre><code>def bad_append(new_item, a_list=[]):
- a_list.append(new_item)
- return a_list
- </code></pre>
- <p>Le problème ici c'est que la valeur par défaut <code>a_list</code>, 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 <strong>même</strong> valeur par défaut.
- Essayez plusieurs fois :</p>
- <pre><code>>>> print bad_append('one')
- ['one']
-
- >>> print bad_append('two')
- ['one', 'two']
- </code></pre>
- <p>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, <strong>au sein de la fonction</strong> :</p>
- <pre><code>def good_append(new_item, a_list=None):
- if a_list is None:
- a_list = []
- a_list.append(new_item)
- return a_list
- </code></pre>
- <h2>Formattage des chaînes de caractères avec %</h2>
- <p>L'opérateur <code>%</code> fonctionne en Python comme la fonction <code>sprintf</code> de C.</p>
- <p>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.</p>
- <p>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 <code>str()</code> pour convertir un objet
- en une chaîne de caractères.</p>
- <p>Les valeurs proposées doivent correspondrent au template, on a deux valeurs
- ici, un tuple.</p>
- <pre><code>name = 'David'
- messages = 3
- text = ('Hello %s, you have %i messages'
- % (name, messages))
- print text
- </code></pre>
- <p>Ce qui donne :</p>
- <pre><code>Hello David, you have 3 messages
- </code></pre>
- <p>Les détails sont dans la <em>Python Library Reference</em>, section 2.3.6.2,
- "String Formatting Operations". Mettez cette page en favoris !</p>
- <p>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.</p>
- <h2>Formattage des chaînes de caractères avancé</h2>
- <p>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 ?</p>
- <p>Avec des noms grâce à un dictionnaire :</p>
- <pre><code>values = {'name': name, 'messages': messages}
- print ('Hello %(name)s, you have %(messages)i '
- 'messages' % values)
- </code></pre>
- <p>Ici on a spécifié les noms des valeurs interprétées, qui constituent les
- clés du dictionnaire.</p>
- <p>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.</p>
- <p>En utilisant les variables locales :</p>
- <pre><code>print ('Hello %(name)s, you have %(messages)i '
- 'messages' % locals())
- </code></pre>
- <p>La fonction <code>locals()</code> retourne un dictionnaire de toutes les variables
- locales disponibles.</p>
- <p>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.</p>
- <p>Mais le pouvoir peut être dangereux. ("With great power comes great
- responsibility.") Si vous utilisez <code>locals()</code> 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.</p>
- <p>Pour examiner votre espace de nom local :</p>
- <pre><code>>>> from pprint import pprint
- >>> pprint(locals())
- </code></pre>
- <p><code>pprint</code> 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 !</p>
- <h2>Formattage des chaînes de caractères avancé</h2>
- <p>L'espace de nom des attributs d'une instance d'objet est simplement un
- dictionnaire, <code>self.__dict__</code>.</p>
- <p>En utilisant l'espace de nom d'une instance :</p>
- <pre><code>print ("We found %(error_count)d errors"
- % self.__dict__)
- </code></pre>
- <p>Equivalent à, mais moins flexible que :</p>
- <pre><code>print ("We found %d errors"
- % self.error_count)
- </code></pre>
- <p>Note: Les attributs d'une classe sont dans le <code>__dict__</code> de la classe.
- Les espaces de noms sont hérités et constituent donc des dictionnaires
- chaînés.</p>
- <h2>List Comprehensions</h2>
- <p>Les list comprehensions ("listcomps" pour les intimes) sont des raccourcis
- syntaxiques pour ce pattern généralement utilisé.</p>
- <p>La manière traditionnelle avec <code>for</code> et <code>if</code> :</p>
- <pre><code>new_list = []
- for item in a_list:
- if condition(item):
- new_list.append(fn(item))
- </code></pre>
- <p>En utilisant une list comprehension :</p>
- <pre><code>new_list = [fn(item) for item in a_list
- if condition(item)]
- </code></pre>
- <p>Les listcomps sont claires et concises, directes. Vous pouvez avoir plusieurs
- boucles <code>for</code> et conditions <code>if</code> 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 <code>for</code>. En appliquant le Zen de Python,
- utilisez la méthode la plus lisible.</p>
- <p>Par exemple, la liste des carrés de 0 à 9 :</p>
- <pre><code>>>> [n ** 2 for n in range(10)]
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- </code></pre>
- <p>La liste des nombres impairs au sein de la précédente liste :</p>
- <pre><code>>>> [n ** 2 for n in range(10) if n % 2]
- [1, 9, 25, 49, 81]
- </code></pre>
- <h2>Generator Expressions (1)</h2>
- <p>Faisons la somme des carrés des nombres inférieurs à 100 :</p>
- <p>Avec une boucle :</p>
- <pre><code>total = 0
- for num in range(1, 101):
- total += num * num
- </code></pre>
- <p>On peut aussi utiliser la fonction <code>sum</code> qui fait plus rapidement le
- travail pour nous en construisant la bonne séquence.</p>
- <p>Avec une list comprehension :</p>
- <pre><code>total = sum([num * num for num in range(1, 101)])
- </code></pre>
- <p>Avec une generator expression :</p>
- <pre><code>total = sum(num * num for num in xrange(1, 101))
- </code></pre>
- <p>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.</p>
- <p>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 <code>xrange</code> pour la même
- raison, ça génère les valeurs une par une.</p>
- <h2>Generator Expressions (2)</h2>
- <p>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 !</p>
- <pre><code>total = sum(num * num
- for num in xrange(1, 1000000000))
- </code></pre>
- <p>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.</p>
- <p>En bref :</p>
- <ul>
- <li>Utilisez une list comprehension lorsque le résultat escompté est la liste.</li>
- <li>Utilisez une generator expression lorsque la liste n'est qu'un résultat
- intermédiaire.</li>
- </ul>
- <p>Voici un récent exemple de ce que j'ai vu au boulot.</p>
- <p>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.</p>
- <p>Ça fonctionne de la manière suivante :</p>
- <ul>
- <li>La fonction de référence <code>dict()</code> prend en argument une liste de paires
- de clés/valeurs (2-tuples).</li>
- <li>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.</li>
- <li>Le nombre des mois commence à 1 mais Python commence l'indexation à 0,
- le nombre des mois correspond dont à index+1.</li>
- <li>On veut avoir la correspondance à la fois avec les nombres et les chaînes
- de caractères. On peut utiliser les fonctions <code>int()</code> et <code>str()</code> pour
- ça et itérer dessus.</li>
- </ul>
- <p>L'exemple en question :</p>
- <pre><code> month_codes = dict((fn(i+1), code)
- for i, code in enumerate('FGHJKMNQUVXZ')
- for fn in (int, str))
- </code></pre>
- <p>Le résultat obtenu pour <code>month_codes</code> :</p>
- <pre><code>{ 1: 'F', 2: 'G', 3: 'H', 4: 'J', ...
- '1': 'F', '2': 'G', '3': 'H', '4': 'J', ...}
- </code></pre>
- <h2>Ordonner</h2>
- <p>Il est très simple d'ordonner une liste en Python :</p>
- <pre><code>a_list.sort()
- </code></pre>
- <p>(Notez que la liste est ordonnée sur place, la liste originale est ordonnée
- et la fonction <code>sort</code> <strong>ne retourne pas</strong> une liste ou une copie.)</p>
- <p>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.</p>
- <p>On peut utiliser la fonction de référence <code>sort</code> avec une méthode définie
- par nos soins :</p>
- <pre><code>def custom_cmp(item1, item2):
- returm cmp((item1[1], item1[3]),
- (item2[1], item2[3]))
-
- a_list.sort(custom_cmp)
- </code></pre>
- <p>Ça marche, mais c'est extrêmement lent pour les listes énormes.</p>
- <h2>Ordonner avec DSU</h2>
- <p>DSU = Decorate-Sort-Undecorate</p>
- <p>Note: DSU n'est bien souvent plus nécessaire, cf. section suivante.</p>
- <p>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 :</p>
- <pre><code># 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]
- </code></pre>
- <p>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.</p>
- <p>La seconde ligne ordonne grâce à la fonction Python, ce qui est très rapide.</p>
- <p>La troisième ligne récupère la <strong>dernière</strong> 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.</p>
- <p>C'est un compromis espace mémoire + complexité vs. temps. Plus simple et
- rapide mais on est obligé de dupliquer la liste originale.</p>
- <h2>Ordonner avec keys</h2>
- <p>Python 2.4 a introduit un nouvel argument à la méthode <code>sort</code> 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 :</p>
- <pre><code>def my_key(item):
- return (item[1], item[3])
-
- to_sort.sort(key=my_key)
- </code></pre>
- <p>La fonction <code>my_key</code> va être appelée une fois par item de la liste <code>to_sort</code>.</p>
- <p>Vous pouvez utiliser votre propre fonction ou utiliser une fonction existante
- qui ne prend qu'un seul argument :</p>
- <ul>
- <li><code>str.lower</code> pour ordonner alphabétiquement sans tenir compte de la casse.</li>
- <li><code>len</code> pour ordonner selon la taille des items (chaînes de caractères ou containers).</li>
- <li><code>int</code> ou <code>float</code> pour ordonner numériquement avec des valeurs qui sont
- des chaînes de caractères comme "2", "123", "35".</li>
- </ul>
- <h2>Generators</h2>
- <p>On a déjà vu les generator expressions. On peut créer nos propres generators,
- comme des fonctions :</p>
- <pre><code>def my_range_generator(stop):
- value = 0
- while value < stop:
- yield value
- value += 1
-
- for i in my_range_generator(10):
- do_something(i)
- </code></pre>
- <p>Le mot-clé <code>yield</code> 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
- <code>next</code>. Les boucles <code>for</code> appellent la méthode <code>next</code> de l'itérateur,
- jusqu'à ce qu'une exception du type <code>StopIteration</code> soit levée.
- Vous pouvez lever l'exception <code>StopIteration</code> explicitement ou de manière
- implicite comme dans le code ci-dessous.</p>
- <p>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.</p>
- <p>Voici comment la boucle <code>for</code> fonctionne réellement. Python analyse la
- séquence déclarée avec le mot-clé <code>in</code>. 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.</p>
- <p>Python appelle ensuite de manière itérative la méthode <code>next</code> de
- l'itérateur, assignant la valeur retournée au compteur de la boucle (<code>i</code>
- dans notre cas), et exécute le code indenté. C'est répété, encore et encore
- jusqu'à ce que <code>StopIteration</code> soit levée, ou qu'un <code>break</code> soit exécuté.</p>
- <p>Une boucle <code>for</code> peut être dotée d'un <code>else</code>, au sein de laquelle le
- code est exécuté si rien ne s'est produit dans la boucle for, mais <strong>non</strong>
- après un <code>break</code>. Cette distinction permet de faire des choses élégantes.
- <code>else</code> est rarement utilisé avec la boucle <code>for</code> mais peut s'avérer
- très puissant lorsque la logique correspond à ce que vous souhaitez faire.</p>
- <p>Par exemple, si on doit vérifier qu'une condition est toujours remplie par
- tous les items d'une liste :</p>
- <pre><code>for item in sequence:
- if condition(item):
- break
- else:
- raise Exception('Condition not satisfied.')
- </code></pre>
- <h2>Exemple de generator</h2>
- <p>Filtrer les colonnes vides à partir d'un fichier CSV (ou des items d'une liste) :</p>
- <pre><code>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))
- </code></pre>
- <h2>Lire les lignes d'un fichier de données</h2>
- <pre><code>datafile = open('datafile')
- for line in datafile:
- do_something(line)
- </code></pre>
- <p>C'est possible car les fichiers sont dotés d'une méthode <code>next</code>, comme
- d'autres itérateurs : les listes, les tuples, les dictionnaires (pour leurs
- clés), les generators.</p>
- <p>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 <code>.next</code> et
- <code>.read*</code> à moins que vous n'utilisez Python 2.5+.</p>
- <h2>EAFP vs. LBYL</h2>
- <p>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.</p>
- <ul>
- <li>Duck typing
- Si ça marche comme un canard, parle comme un canard et ressemble à un
- canard : c'est un canard.</li>
- <li>Exceptions
- Essayez de forcer le type si un objet doit être d'un type particulier.
- Si <code>x</code> doit être une chaîne de caractères pour que votre code
- fonctionne, pourquoi ne pas appeler <code>str(x)</code> au lieu d'essayer quelque`
- chose comme <code>isinstance(x, str)</code>.</li>
- </ul>
- <h2>Exemple EAFP <code>try/except</code></h2>
- <p>Vous pouvez encapsuler vos exceptions dans un bloc <code>try/except</code> 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.</p>
- <pre><code>try:
- return str(x)
- except TypeError:
- ...
- </code></pre>
- <p>Note: Spécifiez toujours l'exception à attraper. N'utilisez jamais
- <code>except</code> tout seul. Sinon <code>except</code> va cacher d'autres exceptions qui
- risquent d'être levées rendant votre code très difficile à debugger.</p>
- <h2>Importer</h2>
- <pre><code>from module import *
- </code></pre>
- <p>Vous avez probablement déjà vu cette manière de faires des imports avec une
- "étoile". Vous l'appréciez peut-être. <strong>Ne l'utilisez pas.</strong></p>
- <p>Pour paraphraser un exemple très connu :</p>
- <blockquote>
- <p>LUKE: Est-ce que <code>from module import *</code> 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.</p>
- </blockquote>
- <p>Les imports étoilés sont le mauvais côté de la Force en Python.</p>
- <p>Les imports de type <code>from module import *</code> 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.</p>
- <p>Morale : <strong>n'utilisez pas d'imports étoilés !</strong></p>
- <p>Il est bien meilleur de référencer les noms à partir de leurs modules :</p>
- <pre><code>import module
- module.name
- </code></pre>
- <p>importer un module avec un nom plus court si nécessaire (avec <code>alias</code>) :</p>
- <pre><code>import long_module_name as mod
- mod.name
- </code></pre>
- <p>ou importer juste les noms dont vous avez besoin de manière explicite :</p>
- <pre><code>from module import name
- name
- </code></pre>
- <p>Notez qu'il est nécessaire d'utiliser "reload()" sur un module lorsque vous
- utilisez le prompt interactif si vous éditez celui-ci.</p>
- <h2>Modules et scripts</h2>
- <p>Pour faire à la fois un module importable et un script exécutable :</p>
- <pre><code>if __name__ == '__main__':
- # script code here
- </code></pre>
- <p>Lorsqu'il est importé, un attribut<code>__name__</code> 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
- <code>__name__</code> est setté à "<strong>main</strong>" et le script va être exécuté.</p>
- <p>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 <code>if __name__ == '__main__'</code>.</p>
- <h2>Structure d'un module</h2>
- <pre><code>"""module docstring"""
-
- # imports
- # constants
- # exception classes
- # interface functions
- # classes
- # internal functions & classes
-
- def main(...):
- ...
-
- if __name__ == '__main__':
- status = main()
- sys.exit(status)
- </code></pre>
- <p>C'est la façon dont un module devrait être structuré.</p>
- <h2>Utilisation de la ligne de commande</h2>
- <p>Exemple (<em>Note du traducteur</em> : <a href="https://larlet.fr/david/biologeek/archives/2006218-un-template-python-pour-parser-des-arguments/">j'ai déjà parlé de ça aussi</a>) :</p>
- <pre><code>#!/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)
- </code></pre>
- <h2>Packages</h2>
- <pre><code>package/
- __init__.py
- module1.py
- subpackage/
- __init__.py
- module2.py
- </code></pre>
- <ul>
- <li>Utilisés pour organiser un projet.</li>
- <li>Réduisent le nombre d'entrées lors du chargement.</li>
- <li>Réduisent les conflits en cas d'imports.</li>
- </ul>
- <p>Exemple :</p>
- <pre><code>import package.module1
- from package.subpackage import module2
- from package.subpackage.module2 import name
- </code></pre>
- <p>En Python 2.5 on a maintenant les imports absolus et relatifs via un import
- du futur :</p>
- <pre><code>from __future__ import absolute_import
- </code></pre>
- <p>Je n'ai pas encore eu l'occasion de tester ça moi-même, on va donc couper
- court à toute discussion à ce sujet.</p>
- <h2>Simple is Better Than Complex</h2>
- <blockquote>
- <p>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.</p>
- <p>-- Brian W. Kernighan, co-auteur de <em>The C Programming Language</em>
- et le "K" dans "AWK"</p>
- </blockquote>
- <p>En d'autres termes, gardez vos programmes simples !</p>
- <h2>Ne réinventez pas la roue</h2>
- <p>Avant d'écrire une seule ligne de code,</p>
- <ul>
- <li>Vérifiez que cela n'est pas dans la bibliothèque standard de Python.</li>
- <li>Vérifiez que cela n'est pas dans le <a href="http://cheeseshop.python.org/pypi">Python Package Index</a> (the "Cheese Shop")</li>
- <li>Cherchez sur le web. <em>Google is your friend.</em></li>
- </ul>
- <h2>Retour du traducteur</h2>
- <p>Pour terminer, et si vous souhaitez aller plus loin, une
- <a href="http://www.dabeaz.com/generators/">excellente présentation sur les générateurs</a>
- 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,
- <a href="http://avinashv.net/2008/04/python-decorators-syntactic-sugar/">un bon exemple</a>
- vaut mieux que tous les discours. Mangez du Python, c'est bon pour la santé !</p>
- </div>
- </article>
- <footer>
- <h6 property="schema:datePublished">— 11/05/2008</h6>
- </footer>
- </section>
- <section>
- <div>
- <h3>Articles peut-être en rapport</h3>
- <ul>
- <li><a href="/david/biologeek/archives/20080211-astuces-et-bonnes-pratiques-django/" title="Accès à ★ Astuces et bonnes pratiques Django">★ Astuces et bonnes pratiques Django</a></li>
- <li><a href="/david/biologeek/archives/20070519-presentation-de-django-aux-journees-python-francophones/" title="Accès à Présentation de Django aux journées Python francophones">Présentation de Django aux journées Python francophones</a></li>
- <li><a href="/david/biologeek/archives/20060505-pourquoi-programmer-en-python/" title="Accès à ★ Pourquoi programmer en Python ?">★ Pourquoi programmer en Python ?</a></li>
- </ul>
- </div>
- </section>
- <section>
- <div id="comments">
- <h3>Commentaires</h3>
-
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">Gilles</span> le <span class="comment-date" property="schema:commentTime">11/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Salut David,</p>
-
- <p>Merci pour cette traduction :) Mémo très utile.</p>
-
- <p>Bonne soirée.</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">Oncle Tom</span> le <span class="comment-date" property="schema:commentTime">11/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Tip-top cet article. On aurait presque pu croire que c'est toi qui l'avait écrit ;-)</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">NiKo</span> le <span class="comment-date" property="schema:commentTime">11/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Je trouve ça dommage de demander à contraindre les lignes à 80 caractères tout en demandant 4 espaces comme intervalle d'indentation, ça fait perdre pas mal d'espace. Perso j'aime bien 2 espaces, je ne trouve pas que cela rendre le code illisible pour autant.</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">Thesa</span> le <span class="comment-date" property="schema:commentTime">12/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Super traduction ! Un billet à conserver dans un coin, ça sera utile !</p>
-
- <p>Merci :-)</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">thibault</span> le <span class="comment-date" property="schema:commentTime">12/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>L'éternelle guerre entre les espaces et les tabulations. Perso, je ne comprends vraiment pas l'intérêt d'indenter à coup d'espaces, mais bon... (troll inside)</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">David, biologeek</span> le <span class="comment-date" property="schema:commentTime">14/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Merci pour vos encouragements, c'était tellement loooooong que ça fait plaisir ;-).</p>
-
- <p>@NiKo : perso j'utilise 4 espaces pour la lisibilité mais je ne m'impose pas absolument de rester sous les 80 lignes, ça dépend des cas. Je sais plus où Guido disait : corporate == 2 espaces, autre == 4 espaces.</p>
-
- <p>@thibault : il n'y a pas de guerre, c'est surtout un problème d'éditeurs qui finissent par mixer les deux et là c'est mal. Dans l'idéal il faudrait utiliser des tabs pour laisser la liberté à l'utilisateur de configurer la largeur de tab qui lui plait (2 ou 4 espaces), dans la pratique il vaut mieux imposer les espaces (les bons éditeurs font ça).</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">Fabien</span> le <span class="comment-date" property="schema:commentTime">14/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Ce qui est intéressant de faire, c'est de mettre un hook svn en pre-commit, pour vérifier le coding style avec le script pep8.py (et en profiter pour utiliser d'autres logiciels ... pyflakes & co).</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">Ikipou</span> le <span class="comment-date" property="schema:commentTime">15/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Excellent article.</p>
-
- <p>Est-ce que tu le publies sous licence libre? J'aimerais beaucoup le republier sur le site <a href="http://OpenYourCode.org">http://OpenYourCode.org</a> dans la partie Python.</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">David, biologeek</span> le <span class="comment-date" property="schema:commentTime">15/05/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>C'est du CC BY-SA originellement donc vas-y fais toi plaisir :-).</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">Frédéric Péters</span> le <span class="comment-date" property="schema:commentTime">08/06/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Très utile traduction; une petite erreur dans « La liste des nombres premiers au sein de la précédente liste »; il s'agit de la liste des nombres impairs, pas des nombres premiers.</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">David, biologeek</span> le <span class="comment-date" property="schema:commentTime">09/06/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>@Frédéric Péters : merci c'est corrigé.</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">loupblanc</span> le <span class="comment-date" property="schema:commentTime">09/07/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Merci beaucoup pour cette excellente traduction !</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">radada</span> le <span class="comment-date" property="schema:commentTime">28/11/2008</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Merci ! Plein d'infos enfin claires et compréhensibles ! Juste une toute petite erreur je crois dans l'explication sur les package : choisir entre 'package' et 'packages' ;)</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">tarball</span> le <span class="comment-date" property="schema:commentTime">20/03/2009</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>Salut David,</p>
-
- <p>dans ta rubrique "GENERATOR EXPRESSIONS (2)", à la fin, n'aurait-il pas été plus pertinent d'écrire :</p>
-
- <p>month_codes = dict(map(lambda(x,y):(x + 1,y), enumerate('FGHJKMNQUVXZ')))</p>
-
- <p>qui est quand même plus simple et plus direct ?</p>
-
- <p>merci pour ce mémo, excellent travail ! ;-)</p>
- </div>
- </div>
- <div class="comment" typeof="schema:UserComments">
- <p class="comment-meta">
- <span class="comment-author" property="schema:creator">experts CMS django</span> le <span class="comment-date" property="schema:commentTime">01/03/2011</span> :
- </p>
- <div class="comment-content" property="schema:commentText">
- <p>L'année 2011 s'annonce chargée pour les éditeurs de CMS. Les principales solutions du marché viennent toutes de proposer en quelques semaines une nouvelle version de leur outil. Après Django</p>
- </div>
- </div>
-
- </div>
- </section>
-
-
- <footer>
- <nav>
- <p>
- <small>
- Je réponds quasiment toujours aux <a href="mailto:david%40larlet.fr" title="Envoyer un email">emails</a> (<a href="/david/signature/" title="Ma signature actuelle avec possibilité de chiffrement">signés</a>) et vous pouvez me rencontrer à Montréal. <span class="more-infos">N’hésitez pas à <a href="/david/log/" title="Être tenu informé des mises à jour">vous abonner</a> pour être tenu informé des publications récentes.</span>
- </small>
- </p>
- </nav>
- </footer>
-
- </div>
-
- <script src="/static/david/js/larlet-david-3ee43f.js" data-no-instant></script>
-
- <script data-no-instant>InstantClick.init()</script>
-
- </body>
- </html>
|