Table des matières Préface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii 1. Texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 Traiter une chaîne caractère par caractère 7 1.2 Convertir des caractères en leurs codes numériques et réciproquement 8 1.3 Tester si un objet est assimilé à une chaîne 1.4 Aligner des chaînes 1.5 Supprimer les espaces aux extrémités d’une chaîne 1.6 Combiner des chaînes 1.7 Inverser une chaîne par mots ou par caractères 1.8 Tester si une chaîne contient certains caractères 1.9 Simplifier l’utilisation de la méthode translate des chaînes 1.10 Filtrer une chaîne par rapport à un ensemble de caractères 1.11 Tester si une chaîne contient du texte ou du binaire 1.12 Contrôler la casse 1.13 Accéder à des sous-chaînes 1.14 Modifier l’indentation d’une chaîne de plusieurs lignes 1.15 Convertir les tabulations 1.16 Interpréter des variables dans une chaîne 1.17 Interpréter des variables dans des chaînes avec Python 2.4 1.18 Remplacer plusieurs motifs en une seule passe 1.19 Tester si une chaîne a plusieurs fins possibles 1.20 Gérer du texte international avec Unicode 1.21 Convertir des chaînes Unicode en chaînes normales et réciproquement 1.22 Afficher des caractères Unicode sur la sortie standard 1.23 Encoder des données Unicode pour XML et HTML 9 11 11 12 15 16 19 22 25 26 28 31 32 35 36 38 41 43 45 47 49 vi Table des matières 1.24 Rendre des chaînes insensibles à la casse 1.25 Convertir des documents HTML en texte sur un terminal Unix 51 55 2. Fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 2.1 Lire un fichier 2.2 Écrire dans un fichier 2.3 Rechercher et remplacer du texte dans un fichier 2.4 Lire une ligne spécifique dans un fichier 2.5 Compter les lignes d’un fichier 2.6 Traiter tous les mots d’un fichier 2.7 Utiliser des entrées/sorties à accès direct 2.8 Modifier un fichier à accès direct 2.9 Lire des données contenues dans un fichier zip 2.10 Gérer un fichier zip dans une chaîne 2.11 Archiver une arborescence de fichiers dans une archive tar compressée 2.12 Envoyer des données binaires sur la sortie standard de Windows 63 67 68 69 70 73 75 76 78 79 2.15 Adapter un objet apparenté à un fichier à un véritable objet fichier 2.16 Parcourir des arborescences de répertoires 81 82 83 85 88 89 2.17 Remplacer une extension de fichier par une autre dans toute une arborescence de répertoires 2.18 Trouver un fichier dans un chemin donné 90 92 2.13 Utiliser une syntaxe comme celle de iostream en C++ 2.14 Revenir au début d’un fichier d’entrée 2.19 Chercher dans un chemin les fichiers font les noms correspondent à un certain motif 2.20 Rechercher un fichier dans le chemin de recherche de Python 2.21 Modifier dynamiquement le chemin de recherche de Python 2.22 Calculer le chemin relatif d’un répertoire par rapport à un autre 2.23 Lire un caractère non tamponné de façon portable 2.24 Compter les pages d’un document PDF sur Mac OS X 2.25 Modifier les attributs de fichiers sur Windows 2.26 Extraire du texte d’un document OpenOffice.org 2.27 Extraire le texte d’un document Word de Microsoft 2.28 Verrouiller un fichier avec une API portable 2.29 Ajouter des numéros de versions à des noms de fichiers 2.30 Calculer une somme de contrôle CRC-64 93 94 95 96 98 100 101 102 103 104 106 108 3. Temps et argent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 3.1 Calculer les dates d’hier et de demain 117 Table des matières 3.2 Trouver le vendredi précédent 3.3 Calculer l’écart séparant deux dates 3.4 Totaliser des durées de chansons 3.5 Calculer le nombre de jours de travail entre deux dates 3.6 Rechercher automatiquement les jours fériés 3.7 Analyser des dates de manière floue 3.8 Vérifier si l’heure d’été est active 3.9 Convertir des fuseaux horaires 3.10 Lancer plusieurs fois une commande 3.11 Planifier des commandes 3.12 Utiliser l’arithmétique décimale 3.13 Formater des décimaux comme des valeurs monétaires 3.14 Utiliser Python comme une simple calculatrice 3.15 Vérifier la somme de contrôle d’une carte bancaire 3.16 Surveiller les taux de changes monétaires vii 119 120 121 123 125 128 129 130 132 133 135 137 140 143 144 4. Raccourcis Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 4.1 Copier un objet 4.2 Construire des listes avec des listes en intension 4.3 Renvoyer un élément d’une liste s’il existe 4.4 Boucler sur les éléments et les indices d’une séquence 4.5 Créer des listes de listes sans partager les références 4.6 Aplatir une séquence imbriquée 4.7 Supprimer ou réordonner des colonnes dans une liste de lignes 4.8 Transposer des tableaux à deux dimensions 4.9 Obtenir une valeur dans un dictionnaire 4.10 Ajouter une entrée à un dictionnaire 4.11 Construire un dictionnaire sans mettre les clés entre apostrophes 4.12 Construire un dictionnaire à partir d’une liste de clés et de valeurs alternées 4.13 Extraire un sous-ensemble d’un dictionnaire 4.14 Inverser un dictionnaire 4.15 Associer plusieurs valeurs à une clé de dictionnaire 4.16 Utiliser un dictionnaire pour lancer des méthodes ou des fonctions 4.17 Trouver l’union et l’intersection de deux dictionnaires 4.18 Rassembler plusieurs éléments nommés 4.19 Affecter et tester en une seule instruction 4.20 Utiliser printf avec Python 149 152 154 155 156 158 160 162 164 165 167 168 170 172 173 175 176 179 181 183 viii Table des matières 4.21 Choisir aléatoirement des éléments avec des probabilités données 4.22 Gérer les exceptions dans une expression 4.23 Vérifier qu’un nom est défini dans un module donné 184 185 187 5. Programmation orientée objet . . . . . . . . . . . . . . . . . 191 5.1 Convertir des températures 5.5 Déléguer automatiquement plutôt qu’hériter 198 200 202 204 206 5.6 Déléguer des méthodes spéciales dans les mandataires 209 5.7 Implémenter des tuples avec des éléments nommés 212 5.8 Éviter les accesseurs passe-partout pour les propriétés 214 5.9 Faire une copie rapide d’un objet 216 5.2 Définir des constantes 5.3 Restreindre l’ajout d’attributs 5.4 Chaîner des recherches dans des dictionnaires 5.10 Conserver des références vers des méthodes liées sans empêcher le travail du ramasse-miettes 218 5.11 Implémenter un tampon circulaire 221 5.12 Tester les changements d’état d’une instance 224 5.13 Vérifier qu’un objet a les attributs nécessaires 228 5.14 Implémenter le motif de conception « État » 231 5.15 Implémenter le motif de conception « Singleton » 233 5.16 Éviter le motif de conception Singleton grâce à l’idiome Borg 235 5.17 Implémenter le motif de conception Objet nul 239 5.18 Initialiser automatiquement les variables d’instances à partir des paramètres de __init__ 242 5.19 Appeler la méthode __init__ d’une super-classe si elle existe 244 5.20 Utiliser les appels coopératifs aux super-classes de façon concise et sûre 247 6. Persistance et bases de données . . . . . . . . . . . . . . . . 251 6.1 Sérialiser des données avec le module marshal 254 6.2 Sérialiser des données avec les modules pickle et cPickle 256 6.3 Utiliser la compression avec la sérialisation 259 6.4 Utiliser le module cPickle avec des classes et des instances 260 6.5 Sérialiser des objets contenant des méthodes liées 263 6.6 Sérialiser des objets code 265 6.7 Modifier des objets avec shelve 267 6.8 Utiliser des fichiers Berkeley DB 270 Table des matières 6.9 Accéder à une base de données MySQL 6.10 Stocker un BLOB dans une base de données MySQL 6.11 Stocker un BLOB dans une base de données PostgreSQL 6.12 Stocker un BLOB dans une base de données SQLite 6.13 Créer un dictionnaire associant les noms de colonnes à leurs numéros 6.14 Utiliser dtuple pour accéder simplement aux résultats des requêtes 6.15 Afficher proprement le contenu des curseurs de bases de données 6.16 Utiliser le même style de passage de paramètres pour tous les modules de l’API DB 6.17 Utiliser Microsoft Jet via ADO 6.18 Accéder à une base de données JDBC à partir d’une servlet Jython 6.19 Utiliser ODBC pour récupérer des données Excel à partir de Jython ix 273 274 275 277 279 281 283 285 287 289 292 7. Débogage et tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 7.1 Empêcher l’exécution de certaines instructions conditionnelles et de certaines boucles 7.2 Mesurer l’utilisation de la mémoire avec Linux 7.3 Déboguer le processus de ramasse-miettes 7.4 Capturer et enregistrer les exceptions 7.5 Tracer les expressions et les commentaires pendant le débogage 7.6 Obtenir plus d’informations de la pile des appels 7.7 Lancer automatiquement le débogueur après une exception non capturée 7.8 Lancer des tests unitaires le plus simplement possible 7.9 Lancer automatiquement des test unitaires 7.10 Utiliser doctest avec unittest et Python 2.4 296 297 299 300 302 305 308 309 311 312 7.11 Vérifier que des valeurs appartiennent à des intervalles dans les tests 315 unitaires 8. Traitement de XML . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 8.1 Vérifier qu’un document XML est bien formé 8.2 Compter les balises d’un document 8.3 Extraire le texte d’un document XML 8.4 Détecter automatiquement l’encodage d’un document XML 8.5 Convertir un document XML en arborescence d’objets Python 8.6 Supprimer les nœuds textuels ne contenant que des espaces dans le sous-arbre DOM d’un nœud 8.7 Analyser un document XML produit par Microsoft Excel 319 320 321 323 325 327 328 x Table des matières 8.8 Valider des documents XML 8.9 Filtrer les éléments et les attributs qui appartiennent à un certain espace de noms 8.10 Fusionner des événements textes contigus avec un filtre SAX 8.11 Utiliser MSHTML pour traiter un document XML ou HTML 330 331 333 336 9. Programmation réseau . . . . . . . . . . . . . . . . . . . . . . . . 339 9.1 Envoyer des messages en utilisant des datagrammes 9.2 Récupérer un document sur le Web 9.3 Filtrer une liste de sites FTP 9.4 Obtenir l’heure à partir d’un serveur SNTP 9.5 Envoyer du mail en HTML 9.6 Intégrer des fichiers dans un message MIME 9.7 Décomposer un message MIME multi-volet 9.8 Supprimer les pièces jointes d’un courrier électronique 9.9 Corriger les messages traités par le module email.FeedParser de Python 2.4 9.10 Consulter une boîte POP3 de façon interactive 9.11 Détecter les ordinateurs inactifs 9.12 Surveiller un réseau avec HTTP 9.13 Faire suivre et rediriger des ports 9.14 Créer un tunnel SSL à travers un proxy 9.15 Implémenter le protocole Dynamic IP 9.16 Se connecter à IRC et enregistrer les messages sur disque 9.17 Accéder à des serveurs LDAP 341 343 344 345 346 348 351 352 355 357 359 364 367 369 372 376 378 10. Programmation web . . . . . . . . . . . . . . . . . . . . . . . . . . 381 10.1 Vérifier que CGI fonctionne 10.2 Gérer des URL dans un script CGI 10.3 Déposer des fichiers sur un serveur web avec CGI 10.4 Vérifier qu’une page web existe 10.5 Tester le type de contenu avec HTTP 10.6 Reprendre le téléchargement d’un fichier par HTTP 10.7 Traiter les cookies pendant la récupération des pages web 10.8 S’authentifier sur un proxy pour naviguer en HTTPS 10.9 Exécuter une servlet avec Jython 10.10 Trouver un cookie d’Internet Explorer 10.11 Produire des fichiers OPML 382 385 386 388 389 391 392 395 396 398 400 Table des matières xi 10.12 Agréger des flux RSS 403 10.13 Transformer des données en pages web grâce aux « templates » 406 10.14 Formater des objets avec Nevow 409 11. Itérateurs et générateurs . . . . . . . . . . . . . . . . . . . . . . . 413 11.1 Écrire une fonction comme range, mais avec des valeurs flottantes 416 11.2 Construire une liste à partir d’un itérable quelconque 418 11.3 Générer la suite de Fibonacci 420 11.4 N’obtenir que quelques éléments d’une séquence avec une affectation multiple 422 11.5 Obtenir automatiquement le nombre voulu d’éléments d’une séquence 423 11.6 Diviser un itérable en n tranches de pas n 425 11.7 Boucler sur une séquence avec des fenêtres glissantes 427 11.8 Parcourir en parallèle plusieurs itérables 431 11.9 Parcourir le produit cartésien de plusieurs itérables 433 11.10 Lire un fichier texte paragraphe par paragraphe 436 11.11 Lire des lignes avec des caractères de continuation 438 11.12 Parcourir un flux de blocs de données comme un flux de lignes 440 11.13 Utiliser un générateur pour récupérer de gros ensembles résultats à partir d’une base de données 441 11.14 Fusionner des séquences triées 443 11.15 Effectuer des permutations, des combinaisons et des sélections 447 11.16 Produire les partitions d’un entier 449 11.17 Dupliquer un itérateur 451 11.18 Rechercher vers l’avant dans un itérateur 454 11.19 Simplifier l’écriture des threads consommateurs 456 11.20 Exécuter un itérateur dans un autre thread 457 11.21 Établir un rapport récapitulatif avec itertools.groupby 459 12. Descripteurs, décorateurs et métaclasses . . . . . . . . 463 12.1 Obtenir de nouvelles valeurs par défaut à chaque appel de fonction 465 12.2 Coder des propriétés comme des fonctions imbriquées 468 12.3 Créer des alias pour les valeurs des attributs 470 12.4 Mettre des valeurs d’attributs en cache 472 12.5 Utiliser une seule méthode pour accéder à plusieurs attributs 475 12.6 Ajouter une fonctionnalité à une classe en enveloppant une méthode 476 xii Table des matières 12.7 Ajouter une fonctionnalité à une classe en enrichissant toutes ses méthodes 12.8 Ajouter en cours d’exécution une méthode à une instance de classe 12.9 Vérifier que des classes implémentent des interfaces 12.10 Utiliser correctement __new__ et __init__ avec les métaclasses personnalisées 12.11 Permettre l’enchaînement de méthodes de modification de listes 12.12 Utiliser des appels coopératifs à super avec une syntaxe plus courte 12.13 Initialiser des attributs d’instance sans utiliser __init__ 12.14 Initialiser automatiquement les attributs d’instance 12.15 Mettre automatiquement à jour des instances de classes lorsqu’elles sont rechargées 12.16 Lier des constantes à la compilation 12.17 Résoudre les conflits de métaclasses 479 481 484 485 487 489 491 493 496 500 505 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513 CHAPITRE 2 Fichiers Introduction Par Mark Lutz, auteur de Programming Python et Python Quick Reference, et co-auteur de Learning Python. Tout programmeur raisonnablement pragmatique exigera d’un langage de programmation des outils performants pour traiter les fichiers. Le traitement des fichiers externes étant une tâche très réelle, la qualité des interfaces permettant de les manipuler est effectivement un bon moyen d’évaluer l’aspect pratique d’un outil de programmation. Comme le prouvent les recettes de ce chapitre, Python excelle dans cette tâche puisqu’il permet de gérer les fichiers à différents niveaux : de la fonction prédéfinie open (un synonyme du type objet file standard) aux outils tiers disponibles sur le Web, en passant par les outils spécialisés des modules de la bibliothèque standard, comme os. En d’autres termes, l’arsenal de Python pour les fichiers offre des outils puissants dont vos scripts pourront tirer parti. Introduction aux fichiers Un objet fichier Python est une instance du type prédéfini file. La fonction prédéfinie open crée et renvoie un objet fichier. Son premier paramètre, une chaîne, indique le chemin du fichier (c’est-à-dire son nom précédé éventuellement de son chemin d’accès). Le second paramètre, qui est également une chaîne, précise son mode d’ouverture. Voici un exemple : entree = open('donnees', 'r') sortie = open('/tmp/spam', 'w') Quelle que soit la syntaxe utilisée par le système d’exploitation sous-jacent, le chemin d’accès passé en paramètre à open est formé de noms de répertoires et d’un nom de fichier, séparés par des caractères « barre de fraction » (/). Sur les systèmes qui n’utilisent pas les barres de fraction, vous pouvez utiliser à la place le caractère anti-slash (\), mais il n’y a pas de raison réelle de le faire : les anti-slash sont plus difficiles à placer dans les littéraux chaînes car vous devez les doubler ou utiliser des chaînes « brutes ». Si le chemin d’accès passé en paramètre ne contient pas le nom du réper- 60 Chapitre 2 — Fichiers toire du fichier, celui-ci est supposé se trouver dans le répertoire courant (qu’il ne faut pas confondre avec le chemin de recherche sys.path des modules Python). Pour le paramètre du mode, 'r' signifie que l’on souhaite lire le fichier en mode texte ; c’est la valeur par défaut et elle est fréquemment omise de sorte que open n’est alors appelée qu’avec un seul paramètre. Les autres modes courants sont 'rb' pour lire le fichier en mode binaire, 'w' pour créer et écrire dans le fichier en mode texte et 'wb' pour créer et écrire dans le fichier en mode binaire. 'rU' est une variante parfois précieuse de 'r' : elle demande à Python de lire le fichier en mode texte en utilisant des « caractères de nouvelle ligne universels » : ce mode permet de lire des fichiers texte indépendamment de la convention de terminaison des lignes utilisée, que ce soit celle d’Unix, de Windows ou même des anciens Mac (le Mac OS X actuel est un Unix sous tous rapports, mais les versions de Mac OS 9 et antérieures, qui remontent seulement à quelques années, étaient très différentes). Sur les plates-formes non Unix, la distinction entre mode texte et mode binaire est importante à cause des caractères de fin de ligne utilisés par ces systèmes. Lorsque vous ouvrez un fichier en mode binaire, Python sait qu’il n’a pas à se soucier de ces caractères : il se contente de déplacer les octets entre le fichier et les chaînes en mémoire sans effectuer aucun traitement. Lorsque vous ouvrez un fichier en mode texte sur des systèmes non Unix, par contre, Python sait qu’il doit effectuer une traduction entre les caractères de fin de ligne '\n' utilisés dans les chaînes et les caractères de fin de ligne qu’utilise la plate-forme courante dans les fichiers. Tant que vous indiquez correctement le mode binaire ou texte lorsque vous ouvrez un fichier, votre code Python peut toujours considérer que '\n' est le caractère de fin de ligne. Une fois l’objet fichier obtenu, toutes les opérations d’entrées-sorties sur le fichier s’effectuent en appelant les méthodes de cet objet. Lorsque vous avez terminé de travailler avec le fichier, vous devriez terminer en appelant la méthode close de l’objet, qui ferme la connexion avec le fichier : entree.close() Dans les petits scripts, les programmeurs omettent souvent cette étape car Python ferme automatiquement le fichier lorsqu’un objet fichier est nettoyé par le ramassemiettes (ce qui, en Python classique, signifie que le fichier n’est fermé qu’une seule fois, bien que d’autres implémentations importantes du langage, comme Jython et IronPython, aient des stratégies de ramasse-miettes moins rigides). Quoi qu’il en soit, fermer vos fichiers le plus tôt possible est une bonne habitude de programmation, notamment conseillée dans les programmes plus gros qui, sinon, prennent le risque d’avoir un nombre excessif de fichiers ouverts inutilement. L’idiome try/finally est particulièrement bien adapté pour garantir qu’un fichier sera correctement fermé, même si une fonction se termine à cause d’une exception non capturée. Pour écrire dans un fichier, utilisez la méthode write : sortie.write(chaine) où chaine est une chaîne que vous pouvez considérer comme une chaîne de caractères si sortie est ouvert en mode d’écriture texte ou d’octets s’il est ouvert en mode d’écriture binaire. Les fichiers disposent d’autres méthodes liées à l’écriture, comme flush qui écrit toutes les données encore dans le tampon et writelines qui écrit une séquence de chaînes d’un seul coup. Cependant, write est, de loin, la méthode d’écriture la plus utilisée. Introduction 61 La lecture d’un fichier est une opération plus courante que l’écriture, qui provoque plus de problèmes. C’est la raison pour laquelle les objets fichiers possèdent plus de méthodes de lecture que de méthodes d’écriture. La méthode readline lit et renvoie la ligne suivante d’un fichier texte. La boucle suivante : while True: ligne = entree.readline() if not ligne: break traiter(ligne) était, à une époque, l’idiome classique en Python, mais ce n’est plus le meilleur moyen de lire et de traiter toutes les lignes d’un fichier. Une autre possibilité ancienne consiste à utiliser la méthode readlines, qui lit tout le fichier et renvoie une liste de lignes : for ligne in entree.readlines(): traiter(ligne) readlines n’est utile que pour les fichiers qui peuvent tenir confortablement dans la mémoire physique. Si le fichier est vraiment énorme, readlines peut échouer ou, au moins, ralentir considérablement le traitement (la mémoire virtuelle se remplit et le système d’exploitation doit commencer à écrire des parties de la mémoire physique sur le disque). Avec les versions actuelles de Python, il suffit de boucler sur l’objet fichier lui-même pour obtenir une ligne à la fois avec d’excellentes performances, tant au niveau de l’exécution qu’au niveau de la mémoire : for ligne in entree: traiter(ligne) Bien sûr, on ne souhaite pas toujours lire un fichier ligne par ligne ; vous pouvez vouloir lire quelques octets du fichiers, voire tous, notamment si vous l’avez ouvert en mode de lecture binaire, où le concept de lignes ne peut bien sûr pas s’appliquer. En ce cas, vous pouvez utiliser la méthode read qui, lorsqu’elle est appelée sans paramètre, lit et renvoie tous les octets restants du fichier. Avec un paramètre entier N, read lit et renvoie les N octets suivants (ou tous ceux qui restent s’il y en a moins que N). Les autres méthodes méritant d’être mentionnées sont seek et tell, qui permettent d’effectuer des accès directs dans les fichiers ; elles sont généralement utilisées avec des fichiers binaires formés d’enregistrements de longueur fixe. Portabilité et souplesse Vue de l’extérieur, la gestion des fichiers par Python est intuitive. Cependant, avant d’utiliser les codes de ce chapitre, je voudrais souligner deux aspects de la gestion des fichiers par Python : la portabilité du code et la souplesse des interfaces. N’oubliez pas que la plupart des interfaces de Python pour les fichiers sont entièrement portables entre les différentes plates-formes. Il serait difficile d’exagérer l’importance de cette fonctionnalité. Un script Python qui parcourt tous les fichiers d’une arborescence de répertoires à la recherche d’un texte, par exemple, peut sans problème passer d’une plate-forme à une autre sans devoir modifier son code source : il suffit de copier le fichier source du script sur la nouvelle machine. Je le fais d’ailleurs constamment — si souvent que je peux avec bonheur rester à l’écart de la guerre des systèmes d’exploitation. Grâce à la portabilité de Python, la plate-forme sous-jacente n’a quasiment pas d’importance. 62 Chapitre 2 — Fichiers En outre, le fait que les interfaces de traitement des fichiers en Python ne soit pas restreintes aux fichiers réels, physiques, m’a toujours frappé. En réalité, la plupart des outils pouvant servir aux fichiers fonctionnent avec n’importe quel type d’objet présentant la même interface qu’un véritable objet fichier. Ainsi, le lecteur d’un fichier ne se soucie que des méthodes de lecture et celui qui écrit ne s’occupe que des méthodes d’écriture. Tant que l’objet cible implémente le protocole attendu, tout va bien. La fonction qui suit applique la fonction qui lui est passée en paramètre à chaque ligne d’un fichier en entrée : def scanner(objet_fichier, gestionnaire_ligne): for ligne in objet_fichier: gestionnaire_ligne(ligne) Si vous codez cette fonction dans un fichier module et que vous placez ce fichier dans un répertoire du chemin de recherche de Python (sys.path), vous pourrez l’utiliser à chaque fois que vous avez besoin d’analyser un fichier ligne par ligne. À titre d’exemple, voici un script client qui se contente d’afficher le premier mot de chaque ligne : from outils import scanner def premierMot(ligne): print ligne.split()[0] fichier = open('donnees') scanner(fichier, premierMot) Pour l’instant, nous avons simplement codé un petit composant logiciel réutilisable, mais vous remarquerez qu’il n’y a aucune déclaration de type dans la fonction scanner : uniquement une contrainte d’interface — n’importe quel objet itérable ligne par ligne conviendra. Si, par exemple, vous voulez fournir un test de saisie pour un objet chaîne au lieu d’un vrai fichier physique, vous pouvez utiliser le module StringIO ou son équivalent plus rapide cStringIO, qui fournissent tout le nécessaire pour envelopper les chaînes et créer les interfaces : from cStringIO import StringIO from outils import scanner def premierMot(ligne): print ligne.split()[0] chaine = StringIO('un\ndeux xxx\ntrois\n') scanner(chaine, premierMot) Les objets StringIO étant compatibles avec l’interface des objets fichiers, scanner prend ses trois lignes de texte à partir d’un objet chaîne en mémoire au lieu de les lire dans un vrai fichier externe. Vous n’avez pas besoin de modifier le code de la fonction pour que cela marche : il suffit de lui passer le bon type d’objet. Pour être plus général encore, vous pouvez même utiliser une classe pour implémenter l’interface attendue : class MonFlux(object): def __iter__(self): # Recupere et renvoie du texte return iter(['a\n', 'b c d\n']) from outils import scanner def premierMot(ligne): print ligne.split()[0] objet = MonFlux() scanner(objet, premierMot) Lire un fichier 63 Cette fois-ci, lorsque scanner tente de lire le fichier, il appelle en fait la méthode __iter__ que vous avez codée dans votre classe. En pratique, une telle méthode peut utiliser d’autres outils standard de Python pour récupérer du texte à partir d’une variété de sources : un utilisateur interactif, une boîte de saisie d’une interface graphique, un objet shelve, une base de données SQL, un document XML ou HTML, une socket réseau, etc. Le point important est que scanner ne connaît pas et ne s’occupe pas de savoir quel type d’objet implémente l’interface qu’il attend, ni ce que fait vraiment cette interface. Les programmeurs utilisant l’approche orientée objet connaissent cette naïveté délibérée sous le terme de polymorphisme. Le type de l’objet traité détermine ce qu’une opération, comme la boucle for de scanner, fait vraiment. En Python, ce sont les interfaces des objets, plutôt que les types de données spécifiques, qui constituent les unités de couplage. L’effet pratique est que les fonctions peuvent souvent s’appliquer à un domaine de problèmes bien plus large que celui auquel vous pouviez vous attendre (c’est particulièrement vrai si vous connaissez déjà des langages utilisant un typage statique comme C ou C++). Le code a une souplesse innée provenant du typage fort mais dynamique de Python. En fait, la portabilité et la souplesse du code sont des caractéristiques omniprésentes dans le développement en Python : elles ne sont pas confinées aux interfaces des fichiers. Ce sont des fonctionnalités du langage qui bénéficient simplement aux scripts de traitement des fichiers. D’autres avantages de Python, comme sa facilité d’écriture et la lisibilité de son code, sont également des points clés lorsque le moment est venu de modifier des programmes. Mais plutôt que de chanter les louanges de Python ici, je préfère déléguer aux merveilleuses recettes de ce chapitre et de ce livre la découverte de tous ces détails. Amusez-vous bien ! 2.1 Lire un fichier Par Luther Blissett Problème Vous voulez lire du texte ou des données à partir d’un fichier. Solution Voici le moyen le plus pratique de lire le contenu entier d’un fichier dans une seule grande chaîne en une seule opération : # tout le texte d'un fichier texte tout_le_texte = open('unfichier.txt').read() # toutes les donnees d'un fichier binaire toutes_les_donnees = open('unfichierbinaire', 'rb').read() Cependant, il est plus sûr de lier l’objet fichier à un nom, afin de pouvoir appeler close sur cet objet dès que vous en aurez terminé avec lui : cela évite de garder des fichiers ouverts inutilement. Voici un exemple pour un fichier texte : objet_fichier = open('unfichier.txt') try: tout_le_texte = objet_fichier.read() 64 Chapitre 2 — Fichiers finally: objet_fichier.close() L’instruction try/finally n’est pas nécessaire ici, mais il est conseillé de l’utiliser car elle garantit que le fichier sera fermé, même si une erreur survient pendant sa lecture. Le moyen le plus simple, le plus rapide et le plus pythonique de lire en une seule opération le contenu d’un fichier dans une liste de chaînes, à raison d’une par ligne, est le suivant : liste_de_toutes_les_lignes = objet_fichier.readlines() Cette instruction laisse le '\n' à la fin de chaque ligne ; si vous ne voulez pas d’un tel comportement, vous avez plusieurs autres possibilités : liste_de_toutes_les_lignes = objet_fichier.read().splitlines() liste_de_toutes_les_lignes = objet_fichier.read().split('\n') liste_de_toutes_les_lignes = [L.rstrip('\n') for L in objet_fichier] Le moyen le plus simple et le plus rapide de traiter ligne par ligne un fichier texte consiste simplement à boucler sur l’objet fichier avec une instruction for : for ligne in objet_fichier: ## Traiter ligne Cette approche laisse également un '\n' à la fin de chaque ligne ; vous pouvez l’ôter en commençant le corps de la boucle for par : ligne = ligne.rstrip('\n') Si cela ne vous gêne pas de supprimer les espaces terminaux de chaque ligne (pas seulement le '\n' final), vous pouvez même utiliser la forme générale suivante, qui est plus pratique : ligne = ligne.rstrip() Discussion À moins que le fichier que vous vouliez lire soit vraiment énorme, le placer entièrement en mémoire en une seule opération est souvent le moyen le plus rapide et le plus commode pour les traitements ultérieurs. La fonction prédéfinie open crée un objet fichier Python (vous pouvez aussi appeler le type prédéfini file). Vous appelez ensuite la méthode read de cet objet pour obtenir tout son contenu (que ce soit du texte ou du binaire) sous la forme d’une seule longue chaîne. Si le contenu est du texte, vous pouvez choisir de découper immédiatement cette chaîne en une liste de lignes grâce à la méthode split ou à l’aide de la méthode spécialisée splitlines. Le découpage en lignes étant souvent nécessaire, vous pouvez également appeler directement readlines sur l’objet fichier pour effectuer plus rapidement et plus commodément cette opération. Vous pouvez également boucler directement sur l’objet fichier ou le passer à des appelables qui attendent un objet itérable, comme list ou max — lorsqu’un objet fichier ouvert en lecture est traité comme un itérable, ses éléments sont ses lignes de texte (cela ne doit donc être fait qu’avec des fichiers texte). Ce type d’itération ligne par ligne est économique en termes de consommation mémoire, tout en restant assez rapide. Sur les systèmes Unix et apparentés, comme Linux, Mac OS X et les autres variantes BSD, il n’existe pas de réelle distinction entre les fichiers texte et les fichiers de don- Lire un fichier 65 nées binaires. Sur Windows et les très anciens systèmes Macintosh, cependant, les fins de lignes dans les fichiers texte sont encodées, non pas avec le séparateur standard '\n' mais, respectivement, avec '\r\n' et '\r'. Python traduit ces caractères de fins de lignes en '\n' à notre place. Ceci signifie que, lorsque vous ouvrez un fichier binaire, vous devez l’indiquer à Python afin qu’il n’effectue pas cette traduction : il suffit, pour cela, d’utiliser 'rb' comme second paramètre de open. Ce paramètre est inoffensif sur les plates-formes Unix, mais il est conseillé de distinguer les fichiers binaires des fichiers texte, bien que ce ne soit pas obligatoire dans ce cas-là. Ces bonnes habitudes rendront vos programmes plus facilement compréhensibles et plus compatibles avec les différentes plates-formes. Si vous ne connaissez pas la convention utilisée par un fichier texte pour ses fins de lignes, utilisez 'rU' comme second paramètre de open afin de demander une traduction universelle des fins de lignes. Cela vous permet d’échanger librement et sans souci des fichiers texte entre Windows, Unix (dont Mac OS X) et les anciens systèmes Macintosh : tous les types de conventions pour les fins de lignes seront converties en '\n', quelle que soit la plate-forme sur laquelle s’exécute votre code. Comme le montre le premier extrait de code de la solution, vous pouvez appeler des méthodes comme read directement sur l’objet fichier produit par la fonction open mais, dans ce cas, vous n’aurez plus de référence à cet objet lorsque l’opération de lecture se sera terminée. En pratique, Python note instantanément l’absence de référence sur l’objet et ferme immédiatement le fichier, mais il est préférable de lier un nom au résultat de open afin de pouvoir appeler explicitement close lorsque vous avez terminé de travailler avec le fichier. De la sorte, il restera ouvert le moins longtemps possible, même sur des plates-formes comme Jython, IronPython et d’éventuelles versions futures de Python, sur lesquelles des mécanismes de ramasse-miettes plus élaborés peuvent retarder la fermeture automatique que la version actuelle de Python, reposant sur C, effectue immédiatement. Pour garantir qu’un objet fichier est fermé même si des erreurs surviennent pendant son traitement, l’approche la plus robuste et la plus prudente consiste à utiliser l’instruction try/finally : objet_fichier = open('unfichier.txt') try: for ligne in objet_fichier: ## Traiter la ligne finally: objet_fichier.close() Faites attention à ne pas placer l’appel à open dans la clause try de cette instruction try/finally (c’est une erreur assez classique chez les débutants). En effet, si une erreur survient pendant l’ouverture, il n’y aura rien à fermer et, en outre, rien ne sera lié au nom objet_fichier : vous ne devriez donc vraiment pas appeler objet_ fichier.close() ! Si vous choisissez de lire le fichier par morceaux au lieu de le lire entièrement d’un seul coup, la technique sera différente. Voici comment lire un fichier binaire par morceaux de 100 octets jusqu’à atteindre la fin : 66 Chapitre 2 — Fichiers objet_fichier = open('unfichierbinaire', 'rb') try: while True: morceau = objet_fichier.read(100) if not morceau: break traiter(morceau) finally: objet_fichier.close() Lorsque vous passez un paramètre N à la méthode read, cela vous garantit que cette méthode ne lira que les N octets suivants (ou moins, s’il reste moins de N octets à lire dans le fichier). read renvoie la chaîne vide lorsqu’il atteint la fin du fichier. Les boucles compliquées gagnent à être encapsulées sous la forme de générateurs réutilisables. Ici, nous ne pouvons effectuer qu’une encapsulation partielle car on ne peut pas utiliser le mot-clé yield d’un générateur dans la clause try d’une instruction try/ finally. Comme try/finally nous garantit que le fichier sera fermé, nous pouvons donc nous contenter de ce code : def lire_fichier_par_morceaux(nom_fichier, taille_morceau = 100): objet_fichier = open(nom_fichier, 'rb') while True: morceau = objet_fichier.read(taille_morceau) if not morceau: break yield morceau objet_fichier.close() Maintenant que ce générateur lire_fichier_par_morceaux est disponible, le code de notre application pour lire et traiter un fichier binaire par morceaux de taille fixe devient extrêmement simple : for morceau in lire_fichier_par_morceaux('unfichierbinaire'): traiter(morceau) Lire un fichier texte ligne par ligne est une opération fréquente. Il suffit pour cela de boucler sur l’objet fichier, comme dans cet exemple : for ligne in open('unfichier.txt', 'rU'): traiter(ligne) Ici aussi, pour être sûr et certain qu’aucun fichier ne restera ouvert inutilement, vous pouvez écrire cet exemple de façon plus rigoureuse et plus prudente : objet_fichier = open('unfichier.txt', 'rU'): try: for ligne in objet_fichier: traiter(ligne) finally: objet_fichier.close() Voir aussi La recette 2.2 ; la documentation sur la fonction prédéfinie open et sur les objets fichiers dans la Library Reference et dans Python en concentré. Écrire dans un fichier 67 2.2 Écrire dans un fichier Par Luther Blissett Problème Vous voulez écrire du texte ou des données dans un fichier. Solution Voici le moyen le plus commode pour écrire une seule longue chaîne dans un fichier : # texte dans un fichier texte open('unfichier.txt', 'w').write(tout_le_texte) # donnees dans un fichier binaire open('unfichierbinaire', 'wb').write(toutes_les_donnees) Cependant, il est plus sûr de lier l’objet fichier à un nom afin de pouvoir appeler close sur cet objet dès que vous n’en aurez plus besoin. Voici un exemple pour un fichier texte : objet_fichier = open('unfichier.txt', 'w') objet_fichier.write(tout_le_texte) objet_fichier.close() Souvent, les données que vous souhaitez écrire ne se trouvent pas dans une seule grosse chaîne mais dans une liste (ou toute autre séquence) de chaînes. En ce cas, vous devriez utiliser la méthode writelines qui, malgré son nom, n’est pas limitée aux lignes mais fonctionne aussi bien avec des données binaires qu’avec des fichiers texte : objet_fichier.writelines(liste_de_chaines_de_texte) open('unfichierbinaire', 'wb').writelines(liste_de_chaines_de_donnees) L’utilisation de writelines est bien plus rapide que les autres solutions consistant à joindre les chaînes pour former une seule grande chaîne (avec ''.join, par exemple) pour appeler ensuite write, ou consistant à appeler write de façon répétée dans une boucle. Discussion Pour créer un objet fichier afin d’y écrire, vous devez toujours passer un deuxième paramètre à open (ou à file) :'w' pour écrire des données textuelles, 'wb' pour écrire des données binaires. Les différentes questions évoquées précédemment dans la recette 2.1 s’appliquent également ici, sauf qu’un appel explicite à close lorsque l’on écrit dans un fichier est encore plus recommandé que lorsqu’on le lit. En effet, ce n’est qu’en fermant le fichier que vous pouvez être à peu près sûr que les données sont écrites sur le disque et ne sont pas encore dans un tampon temporaire en mémoire. L’écriture morceau par morceau dans un fichier est une opération encore plus fréquente que la lecture par morceaux. Vous pouvez vous contenter d’appeler de façon répétée write et/ou writelines au fur et à mesure que chaque chaîne ou séquence de chaînes à écrire est prête. Chaque opération d’écriture ajoute les données à la fin du fichier, après toutes les données qui y ont déjà été écrites. Lorsque vous avez terminé de travailler avec le fichier, appelez la méthode close sur l’objet fichier. Si toutes les données sont disponibles immédiatement, un seul appel à writelines est plus 68 Chapitre 2 — Fichiers rapide et plus simple. Par contre, si elles ne sont disponibles que par morceaux à la fois, il est préférable d’appeler write à mesure qu’elles arrivent plutôt que de construire une liste temporaire des morceaux (avec append par exemple), uniquement pour pouvoir les écrire en une seule fois à la fin avec writelines. Vous remarquerez donc qu’il y a une certaine différence entre la lecture et l’écriture en ce qui concerne les performances et la facilité de manipulation des données. Lorsque vous ouvrez un fichier en écriture avec le paramètre 'w' (ou 'wb'), toutes les données qui pourraient déjà se trouver dans le fichier sont immédiatement supprimées ; même si vous fermez le fichier aussitôt après l’avoir ouvert, vous vous retrouverez avec un fichier vide sur le disque. Si vous voulez ajouter des données après le contenu existant, ouvrez plutôt le fichier avec le paramètre 'a' (ou 'ab'). Il existe également des options d’ouverture plus avancées qui permettent à la fois de lire et d’écrire dans le même objet fichier — la recette 2.8 présentera notamment l’option 'r+b' qui, en pratique, est la seule des options avancées à être couramment utilisée. Voir aussi Les recettes 2.1 et 2.8 ; la documentation sur la fonction prédéfinie open et les objets fichiers dans la Library Reference et dans Python en concentré. 2.3 Rechercher et remplacer du texte dans un fichier Par Jeff Bauer et Adam Krieg Problème Vous devez remplacer une chaîne par une autre dans un fichier. Solution Pour substituer des chaînes, le plus simple consiste à utiliser la méthode replace des objets chaînes. L’exemple qui suit effectue la substitution à partir du fichier indiqué (ou de l’entrée standard) et écrit le résultat dans le fichier indiqué (ou sur la sortie standard) : #!/usr/bin/env python import os, sys nb_params = len(sys.argv) if not 3 <= nb_params <= 5: print "usage: %s ancien_texte nouveau_texte [fichier_src [fichier_dest]]" % \ os.path.basename(sys.argv[0]) else: ancien_texte = sys.argv[1] nouveau_texte = sys.argv[2] fichier_src = sys.stdin fichier_dest = sys.stdout if nb_params > 3: fichier_src = open(sys.argv[3]) Lire une ligne spécifique dans un fichier 69 if nb_params > 4: fichier_dest = open(sys.argv[4], 'w') for s in fichier_src: fichier_dest.write(s.replace(ancien_texte, nouveau_texte)) fichier_dest.close() fichier_src.close() Discussion Ce qui fait la beauté de cette recette, c’est sa simplicité — pourquoi faire compliqué lorsque l’on peut faire simple ? Comme l’indique sa première ligne, qui est une ligne de « shebang », cette recette est conçue pour être lancée directement à partir de la ligne de commande du shell, c’est-à-dire comme un script ; elle n’a pas été écrite comme un module destiné à être importé dans un autre programme. Ce script examine les paramètres qui lui ont été passés pour en extraire le texte à rechercher, le texte de remplacement, le fichier d’entrée (qui est, par défaut, l’entrée standard) et le fichier de sortie (qui est, par défaut, la sortie standard). Puis, il boucle sur chaque ligne du fichier d’entrée, en écrivant chacune d’elles dans le fichier de sortie après avoir effectué la substitution, c’est tout ! Pour être le plus propre possible, le script ferme les deux fichiers avant de se terminer. Si le fichier d’entrée peut confortablement tenir en deux exemplaires dans la mémoire (un avant, l’autre après le remplacement puisque les chaînes ne sont pas modifiables), nous pourrions accélérer la vitesse d’exécution en traitant d’un seul coup tout le contenu du fichier au lieu de boucler sur ses lignes. Cela ne devrait pas poser de problème car le moindre PC actuel est doté d’au moins 256 Mo de mémoire, ce qui permet de gérer des fichiers pouvant atteindre environ 100 Mo : peu de fichiers texte dépassent cette taille. Pour cela, il suffit de remplacer la boucle for par une seule instruction : fichier_dest.write(fichier_src.read().replace(ancien_texte, nouveau_texte)) Comme vous pouvez le constater, c’est encore plus simple qu’avec la boucle de la recette. Voir aussi La documentation sur la fonction prédéfinie open, sur les objets fichiers et sur la méthode replace des chaînes dans la Library Reference et dans Python en concentré. 2.4 Lire une ligne spécifique dans un fichier Par Luther Blissett Problème Vous souhaitez lire une ligne précise, dont vous connaissez le numéro, dans un fichier texte. 70 Chapitre 2 — Fichiers Solution Le module linecache de la bibliothèque standard rend cette opération très simple : import linecache ligne = linecache.getline(chemin_fichier, num_ligne) Discussion Le module standard linecache constitue généralement la solution optimale pour ce genre d’opération. Il est particulièrement utile si vous devez effectuer plusieurs fois cette tâche pour plusieurs lignes d’un fichier car il met les informations en cache afin d’éviter de répéter un travail inutile. Si vous savez que vous n’aurez plus besoin d’autres lignes du fichier pendant un moment, appelez la fonction clearcache de ce module afin de libérer la mémoire occupée par le cache. Vous pouvez également utiliser checkcache si le fichier risque d’avoir changé sur le disque et que vous voulez vous assurer que vous lisez la dernière version de celui-ci. linecache lit et met en cache le contenu complet du fichier texte dont vous avez passé le nom en paramètre : si ce fichier est très gros et que vous n’avez besoin que d’une seule ligne, linecache peut donc faire plus de travail que nécessaire. Si cela pose des problèmes de performances à votre programme, vous pouvez accélérer le traitement en écrivant une boucle explicite encapsulée dans une fonction : def getline(chemin_fichier, num_ligne): if num_ligne < 1: return '' for num_ligne_courante, ligne in enumerate(open(chemin_fichier, 'rU')): if num_ligne_courante == num_ligne - 1: return ligne return '' Le -1 dans la comparaison == est nécessaire car enumerate compte à partir de 0 et nous supposons que le paramètre num_ligne débute à 1. Voir aussi La documentation du module linecache dans la Library Reference et dans Python en concentré ; la recette 8.8 de Perl en action. 2.5 Compter les lignes d’un fichier Par Luther Blissett Problème Vous voulez compter les lignes d’un fichier. Solution L’approche la plus simple pour les fichiers de taille raisonnable consiste à lire le fichier comme une liste de lignes : le nombre de lignes sera donc la longueur de cette liste. Si le chemin du fichier est lié à la variable chemin_fichier, le seul code nécessaire pour implémenter cette solution est le suivant : nbre_lignes = len(open(chemin_fichier, 'rU').readlines()) Compter les lignes d’un fichier 71 Si le fichier est vraiment énorme, cette approche peut être très lente, voire échouer. En ce cas, une boucle de parcours du fichier fonctionnera toujours : nbre_lignes = -1 for nbre_lignes, ligne in enumerate(open(chemin_fichier, 'rU')): pass nbre_lignes += 1 Voici une autre solution astucieuse, potentiellement plus rapide pour les fichiers vraiment énormes lorsque la fin de ligne est '\n' (ou contient '\n', comme avec Windows) : nbre_lignes = 0 fichier = open(chemin_fichier, 'rb') while True: tampon = fichier.read(8192*1024) if not tampon: break nbre_lignes += tampon.count('\n') fichier.close() Si vous recherchez la vitesse, le paramètre 'rb' de open est nécessaire ; sans lui, ce code serait très lent sous Windows. Discussion Si vous disposez déjà d’un programme externe qui compte les lignes d’un fichier, comme wc -l sur les plates-formes Unix, vous pouvez bien sûr l’utiliser (via os.popen, par exemple). Cependant, il est généralement plus simple, plus rapide et plus portable de compter les lignes avec votre propre programme. Vous pouvez considérer que quasiment tous les fichiers texte ont une taille raisonnable, ce qui vous autorise à transférer d’un seul coup tout leur contenu en mémoire. En ce cas, la longueur du résultat de readlines, renvoyée par len, vous donnera le nombre de lignes de la façon la plus simple qui soit. Si le fichier est plus gros que la mémoire disponible (quelques centaines de mégaoctets sur un PC actuel), cette solution peut devenir trop lente car le système d’exploitation devra lutter pour faire tenir le contenu du fichier dans la mémoire virtuelle. Il peut même échouer si la mémoire virtuelle n’est pas suffisante. Sur un PC classique doté de 256 Mo de RAM et d’un espace disque quasiment illimité, vous pouvez quand même rencontrer de sérieux problèmes si vous tentez de lire en mémoire des fichiers dépassant un ou deux giga-octets (certains systèmes d’exploitation sont bien plus fragiles que d’autres dans la gestion des problèmes de mémoire virtuelle lors de situations épineuses comme celle-ci). En ce cas, il est préférable de boucler sur l’objet fichier, comme on le montre dans la solution de cette recette. La fonction prédéfinie enumerate mémorise le nombre de lignes sans que votre code ait à le faire explicitement. L’idée maîtresse de la troisième approche consiste à compter les caractères de fin de ligne pendant la lecture octet par octet du fichier dans des blocs de taille raisonnable. C’est sûrement la moins intuitive au premier abord et elle n’est pas totalement portable, mais c’est probablement la plus rapide (si on la compare, par exemple, à la recette 8.2 de Perl en action). 72 Chapitre 2 — Fichiers Cependant, dans la plupart des cas, les performances ne sont pas vraiment un problème. Si elles le devenaient, la partie de votre programme qui consomme le plus de temps d’exécution ne sera peut-être pas celle à laquelle vous pensez : ne faites jamais confiance à votre intuition à ce sujet — faites toujours des mesures et des comparaisons des temps d’exécution. Considérons, par exemple, un fichier syslog classique d’Unix, de taille moyenne (un peu plus de 18 Mo répartis sur 230000 lignes de texte) : [situ@tioni nuc]$ wc nuc 231581 2312730 18508908 nuc Soit le script de test et de mesure des temps d’exécution, nommé bench.py : import time def chrono(fction, n = 10): debut = time.clock() for i in xrange(n): fction() fin = time.clock() temps = fin - debut return fction.__name__, temps import os def compter_lignes_w(): return int(os.popen('wc -l nuc').read().split()[0]) def compter_lignes_1(): return len(open('nuc').readlines()) def compter_lignes_2(): nbre_lignes = -1 for nbre_lignes, ligne in enumerate(open('nuc')): pass return nbre_lignes + 1 def compter_lignes_3(): nbre_lignes = 0 fichier = open('nuc', 'rb') while True: tampon = fichier.read(65536) if not tampon: break nbre_lignes += tampon.count('\n') return nbre_lignes for f in compter_lignes_w, compter_lignes_1, compter_lignes_2, compter_lignes_3: print f.__name__, f() for f in compter_lignes_1, compter_lignes_2, compter_lignes_3: print "%s: %.2f" % chrono(f) Le programme affiche d’abord les nombres de lignes obtenus par toutes les solutions, afin de vérifier qu’aucune anomalie ou erreur ne se produit (les opérations de comptage sont connues pour être sujettes aux erreurs de type « un en trop » ou « un en moins »). Puis, il lance 10 fois chaque solution sous le contrôle de la fonction de chronométrage chrono afin d’examiner les résultats que voici, tels qu’ils ont été produits sur une machine ancienne mais fiable : [situ@tioni nuc]$ python -O bench.py compter_lignes_w 231581 compter_lignes_1 231581 compter_lignes_2 231581 compter_lignes_3 231581 Traiter tous les mots d’un fichier 73 compter_lignes_1: 4.84 compter_lignes_2: 4.54 compter_lignes_3: 5.02 Comme vous pouvez le constater, les écarts de performance sont faibles : vos utilisateurs ne remarqueront jamais une différence d’environ 10% dans une tâche annexe. Cependant, l’approche la plus rapide (dans mon cas particulier, sur une machine ancienne utilisant une distribution classique de Linux et pour cette mesure spécifique) est l’humble technique consistant à lire ligne par ligne, tandis que la plus lente est l’approche rusée, ambitieuse, consistant à compter les fins de lignes au cours d’une lecture bloc par bloc du fichier. En pratique, sauf si je dois gérer des fichiers de plusieurs centaines de méga-octets, j’utilise toujours l’approche la plus simple, qui est la première présentée dans cette recette. Plutôt qu’utiliser aveuglément des approches compliquées en espérant qu’elles seront plus rapides, il est très important de mesurer les temps d’exécution exacts des portions de codes — c’est si important que la bibliothèque standard de Python fournit un module, timeit, spécialement conçu à cet effet. Je vous conseille de l’utiliser plutôt que de coder vos propres fonctions de mesures comme je l’ai fait ici. En fait, j’ai écrit cette fonction il y a bien longtemps, bien avant que timeit n’apparaisse : je crois donc que je peux être pardonné de ne pas utiliser timeit dans ce cas précis ! Voir aussi Les sections sur les objets fichiers, sur la fonction prédéfinie enumerate, sur os.popen et sur les modules time et timeit dans la Library Reference et dans Python en concentré ; la recette 8.2 de Perl en action. 2.6 Traiter tous les mots d’un fichier Par Luther Blissett Problème Vous devez appliquer un traitement à chaque mot d’un fichier. Solution Pour effectuer cette opération, le plus simple consiste à utiliser deux boucles imbriquées, une pour parcourir les lignes, une autre pour parcourir les mots de chaque ligne : for ligne in open(chemin_fichier): for mot in ligne.split(): traiter(mot) L’en-tête de l’instruction for interne définit implicitement les mots comme des séquences de caractères non espace séparés par des séquences d’espaces (comme la commande wc d’Unix). Si vous préférez donner d’autres définitions aux mots, vous pouvez utiliser des expressions régulières : 74 Chapitre 2 — Fichiers import re re_mot = re.compile(r"[\w'-]+") for ligne in open(chemin_fichier): for mot in re_mot.finditer(ligne): traiter(mot.group(0)) Ici, un mot est défini comme une suite de caractères alphanumériques, de tirets et d’apostrophes. Discussion Pour d’autres définitions des mots, vous utiliserez évidemment d’autres expressions régulières. La boucle externe, qui parcourt toutes les lignes du fichier, ne changera pas. Il est souvent souhaitable d’envelopper les itérations dans des objets itérateurs ; le plus souvent, on réalisera ce type d’encapsulation à l’aide d’un simple générateur : def mots_du_fichier(chemin_fichier, ligne_en_mots = str.split): fichier = open(chemin_fichier): for ligne in fichier: for mot in ligne_en_mots(ligne): yield mot fichier.close() for mot in mots_du_fichier(chemin_fichier): traiter(mot) Cette approche permet de séparer, proprement et efficacement, deux problèmes différents : le parcours de tous les éléments (ici les mots d’un fichier) et ce qu’il convient de faire avec chaque élément de l’itération. Lorsque les problèmes d’itération ont été encapsulés dans un objet itérateur (souvent, comme ici, un générateur), la plupart des utilisations de l’itération deviennent de simples instructions for. Vous pouvez souvent réutiliser l’itérateur en de nombreux points de votre programme et, si vous devez corriger ce code, vous n’aurez besoin de n’intervenir qu’à un seul endroit — dans la définition de l’itérateur — plutôt que de devoir partir à la recherche de tous les endroits où il est utilisé. Les avantages sont donc très similaires à ceux obtenus dans n’importe quel autre langage de programmation lorsque l’on définit et utilise des fonctions de façon appropriée au lieu de copier et coller des portions de code un peu partout. Avec les itérateurs de Python, vous pouvez également bénéficier de cet avantage de la réutilisation pour toutes vos structures de boucles. Nous avons profité de l’opportunité fournie par l’encapsulation de la boucle dans un générateur pour apporter deux petites améliorations — on ferme explicitement le fichier, ce qui est toujours conseillé et on généralise la façon dont chaque ligne est découpée en mots (on utilise par défaut la méthode split des objets chaînes mais il est possible de choisir une autre méthode). Grâce à ce « hook », nous pouvons écrire une autre enveloppe autour de mots_du_fichier si, par exemple, nous souhaitons définir les mots par une expression régulière : import re def mots_par_re(chemin_fichier, motif = r"[\w'-]+"): re_mot = re.compile(motif) def ligne_en_mots(ligne): for mo in re_mot.finditer(ligne): Utiliser des entrées/sorties à accès direct 75 return mo.group(0) return mots_du_fichier(chemin_fichier, ligne_en_mots) Ici aussi, nous fournissons une expression régulière par défaut pour définir un mot tout en laissant la possibilité d’en passer une autre dans les cas où une autre définition serait nécessaire. Une généralisation excessive est une tentation pernicieuse mais, employée à bon escient, une généralisation guidée par l’expérience remboursera souvent amplement le petit effort consenti pour la mettre en place. L’un des moyens les plus simples et les plus commodes pour implémenter ce type de généralisation consiste à utiliser une fonction qui attend un paramètre facultatif valant par défaut la valeur la plus probable. Voir aussi Le chapitre 11 pour plus d’informations sur les itérateurs et les générateurs ; les sections de la Library Reference et de Python en concentré consacrées aux objets fichiers et au module re ; la recette 8.3 de Perl en action. 2.7 Utiliser des entrées/sorties à accès direct Par Luther Blissett Problème Vous voulez lire un enregistrement binaire situé quelque part dans un gros fichier formé d’enregistrements de taille fixe, sans devoir lire tous les enregistrements qui le précèdent. Solution Sachant que la position d’octet du début d’un enregistrement dans le fichier est la taille d’un enregistrement en octets multipliée par le nombre d’enregistrements qui le précèdent (en partant de 0), vous pouvez atteindre directement le bon emplacement puis lire les données. Pour, par exemple, lire le septième enregistrement d’un fichier binaire formé d’enregistrements de 48 octets : fichier = open('fichier_binaire', 'rb') taille_enreg = 48 num_enreg = 6 fichier.seek(taille_enreg * num_enreg) tampon = fichier.read(taille_enreg) Vous remarquerez que le numéro du septième enregistrement est 6 puisque cette numérotation commence à zéro ! Discussion Cette approche ne fonctionne qu’avec des fichiers (généralement binaires) formés d’enregistrements de même taille ; elle ne convient pas aux fichiers texte classiques. La recette ouvre le fichier en mode binaire en passant 'rb' comme second paramètre de open, juste avant l’appel à seek, mais c’est uniquement pour clarifier la situation : tant que l’objet fichier est ouvert en lecture binaire, vous pouvez effectuer autant 76 Chapitre 2 — Fichiers d’opérations seek et read que vous le souhaitez avant de le fermer — il n’est pas nécessaire d’ouvrir le fichier juste avant d’effectuer un seek. Voir aussi La section sur les objets fichiers de la Library Reference et de Python en concentré ; la recette 8.12 de Perl en action. 2.8 Modifier un fichier à accès direct Par Luther Blissett Problème Vous voulez lire un enregistrement binaire dans un gros fichier contenant des enregistrements de taille fixe, modifier un ou plusieurs champs de cet enregistrement puis le réécrire dans le fichier. Solution Lisez l’enregistrement, décompactez-le, effectuez les traitements correspondant aux modifications, ré-empaquetez les champs dans l’enregistrement, positionnez-vous sur le début de l’enregistrement dans le fichier et réécrivez-le... Rassurez-vous, c’est plus rapide à coder qu’à énoncer : import struct chaine_format = '8l' # pour un enregistrement de 8 entiers de 4 octets fichier = open('fichier_binaire', 'r+b') taille_enreg = struct.calcsize(chaine_format) num_enreg = 6 fichier.seek(taille_enreg * num_enreg) tampon = fichier.read(taille_enreg) champs = list(struct.unpack(chaine_format, tampon)) # Effectue les traitements, modifie les champs puis : tampon = struct.pack(chaine_format, *champs) fichier.seek(taille_enreg * num_enreg) fichier.write(tampon) fichier.close() Discussion Cette approche ne fonctionne qu’avec des fichiers (généralement binaires) contenant des enregistrements ayant tous la même taille ; elle ne convient pas aux fichiers texte classiques. En outre, la taille de chaque enregistrement doit être définie par une chaîne de format struct, comme le montre le code de la recette. La chaîne de format '8l', par exemple, indique que chaque enregistrement est formé de 8 entiers de quatre octets, chacun devant être interprété comme une valeur signée et décompacté dans un int Python. Ici, la variable champs de la recette serait donc liée à une liste de huit int. La fonction struct.unpack renvoie un tuple : les tuples étant non modifiables, le calcul devrait donc réaffecter la variable champs complète. Une liste, par contre, est modifiable et l’on peut donc modifier chaque champ individuellement, Modifier un fichier à accès direct 77 c’est la raison pour laquelle on demande explicitement une liste lorsque l’on affecte champs. Prenez garde, cependant, à ne pas modifier la longueur de la liste : ici, elle ne doit comporter que huit entiers, sinon struct.pack déclenchera une exception si on l’appelle avec une chaine_format de '8l'. N’oubliez pas, cette recette ne fonctionne pas lorsque les enregistrements ne sont pas tous de la même taille. Pour revenir au début de l’enregistrement, vous pouvez effectuer un déplacement relatif au lieu d’utiliser le déplacement taille_enreg * num_enreg par rapport au début du fichier : fichier.seek(-taille_enreg, 1) Le second paramètre de la méthode seek (1) demande à l’objet fichier de se déplacer par rapport à la position courante (ici, un déplacement vers le début du fichier puisque le premier paramètre est négatif). Par défaut, seek effectue un déplacement absolu (à partir du début du fichier), mais vous pouvez également demander explicitement ce comportement par défaut en appelant seek avec un second paramètre valant 0. Il n’est pas nécessaire d’ouvrir le fichier juste avant le premier seek, ni de le fermer juste après l’appel à write. Lorsqu’un objet fichier a été correctement ouvert (en mise à jour et en mode binaire, pas en mode texte), vous pouvez le modifier autant de fois que vous le souhaitez avant de le refermer. Ces appels ne sont montrés ici que pour insister sur le mode d’ouverture approprié aux mises à jour en accès direct et sur l’importance de refermer un fichier lorsqu’on n’a plus besoin de le manipuler. Le fichier doit être ouvert en mise à jour (il doit pouvoir être lu et écrit). C’est ce que signifie le paramètre 'r+b' de l’appel à open : ouverture en lecture et en écriture, sans appliquer de transformations implicites sur le contenu car c’est un fichier binaire. Sur les systèmes Unix ou apparentés, la partie 'b' n’est pas nécessaire, mais elle est conseillée pour améliorer la lisibilité ; elle est essentielle sur les autres plates-formes comme Windows. Si vous créez le fichier binaire en partant de zéro, mais que vous voulez quand même pouvoir revenir en arrière, relire et modifier des enregistrements sans devoir fermer et rouvrir le fichier, vous pouvez utiliser plutôt 'w+b' comme deuxième paramètre. Cela dit, je n’ai jamais rencontré cette étrange combinaison d’exigences ; les fichiers binaires sont généralement d’abord créés (en les ouvrant avec 'wb', en écrivant des données et en fermant le fichier) pour être réouverts plus tard avec 'r+b' lorsqu’on souhaite les modifier. Bien que cette approche ne soit généralement utile qu’avec un fichier dont tous les enregistrements sont de la même taille, on peut également atteindre directement des enregistrements à l’aide d’une méthode plus élaborée : en utilisant un « fichier index » fournissant la position et la longueur de chaque enregistrement dans le « fichier des données ». Ce type d’accès séquentiel indexé n’est plus tellement à la mode mais, à une époque, il était très utilisé. De nos jours, on ne s’occupe plus guère que de fichiers texte (de différentes sortes, le plus souvent en XML), de bases de données et, à l’occasion, de fichiers binaires contenant des enregistrements de taille fixe. Cependant, si vous avez besoin d’accéder à un fichier binaire séquentiel indexé, le code sera assez similaire à celui présenté dans cette recette, sauf que vous devrez obtenir la taille_enreg et le paramètre de déplacement passé à fichier.seek en les lisant dans le fichier index au lieu de les calculer vous-même comme dans cette recette. 78 Chapitre 2 — Fichiers Voir aussi Les sections de la Library Reference et de Python en concentré consacrées aux objets fichiers et au module struct ; la recette 8.13 de Perl en action. 2.9 Lire des données contenues dans un fichier zip Par Paul Prescod et Alex Martelli Problème Vous voulez examiner directement un ou plusieurs fichiers contenus dans une archive au format zip, sans les décompacter sur le disque. Solution Les fichiers zip sont des fichiers archives très employés et reconnus sur toutes les plates-formes. La bibliothèque standard de Python fournit un module zipfile permettant d’accéder facilement à ce type de fichier : import zipfile z = zipfile.ZipFile("archive.zip", "r") for nomfic in z.namelist(): print 'Le fichier', nomfic, nb_octets = z.read(nomfic) print 'contient', len(nb_octets), 'octets.' Discussion Python peut travailler directement sur les données contenues dans les fichiers zip. Vous pouvez examiner la liste des éléments du répertoire de l’archive et travailler sur les « fichiers de données » eux-mêmes. Dans cette recette, nous affichons les noms et les tailles des fichiers contenus dans l’archive zip nommée archive.zip. Le module zipfile ne sait pas encore gérer les fichiers zip multi-volumes, ni ceux qui contiennent des commentaires. Faites attention à bien utiliser le paramètre r et pas rb, qui pourrait pourtant sembler plus naturel (sous Windows, par exemple). Avec ZipFile, en effet, ce paramètre n’est pas utilisé de la même façon que dans l’ouverture d’un fichier et rb n’est pas reconnu. Le paramètre r gère la nature binaire intrinsèque de tous les fichiers zip sur toutes les plates-formes. Si un fichier zip contient des modules Python (donc des fichiers .py ou, mieux, .pyc) en plus d’autres fichiers de données, vous pouvez ajouter le chemin de cette archive au sys.path de Python et utiliser ensuite l’instruction import pour importer ces modules. Voici un exemple purement académique démontrant comment créer à la volée un fichier zip, importer un module qu’il contient puis le supprimer — juste pour vous montrer comment faire : import zipfile, tempfile, os, sys descfic, nomfic = tempfile.mkstemp('.zip') os.close(descfic) z = zipfile.ZipFile(nomfic, 'w') z.writestr('bonjour.py', 'def f(): return "Bonjour de " + __file__\n') Gérer un fichier zip dans une chaîne 79 z.close() sys.path.insert(0, nomfic) import bonjour print bonjour.f() os.unlink(nomfic) L’exécution de ce script produira une ligne comme celle-ci : Bonjour de /tmp/tmpJ-pXnH.zip/bonjour.py Outre qu’il illustre la possibilité d’importer un module à partir d’un fichier zip, cet exemple montre également comment créer (et ensuite supprimer) un fichier temporaire et comment utiliser la méthode writestr pour ajouter un élément à une archive zip sans l’écrire d’abord dans un fichier sur disque. Vous remarquerez que le chemin d’accès au fichier zip à partir duquel vous faites l’import est traité un peu comme un répertoire (dans notre exemple d’exécution, ce chemin est /tmp/tmpJ-pXnH.zip mais, bien sûr, cette valeur peut changer à chaque exécution puisque nous utilisons un fichier temporaire et qu’il dépend également de votre plate-forme). La variable globale __file__ du module bonjour qui est importé du fichier zip, notamment, contient la valeur /tmp/tmpJ-pXnH.zip/bonjour.py — un pseudo-chemin composé du chemin d’accès au fichier zip considéré comme un « répertoire », suivi du chemin relatif de bonjour.py dans cette archive. Si vous importez d’un fichier zip un module traitant les chemins relatifs par rapport à lui-même pour obtenir les fichiers de données, vous devrez adapter le module. En effet, vous ne pouvez pas simplement ouvrir avec open un tel « pseudo-chemin » pour obtenir un objet fichier : pour lire ou écrire des fichiers contenus dans une archive zip, vous devez utiliser les fonctions du module standard zipfile, comme on l’a montré dans la solution. Voir aussi La documentation des modules zipfile, tempfile, os et sys dans la Library Reference et dans Python en concentré ; la recette 2.11 explique comment archiver une arborescence de fichiers. 2.10 Gérer un fichier zip dans une chaîne Par Indyana Jones Problème Votre programme reçoit un fichier zip sous la forme d’une chaîne d’octets en mémoire et vous devez lire les informations contenues dans cette archive. Solution Le module cStringIO de la bibliothèque standard est conçu exactement pour régler ce type de problème : import cStringIO, zipfile class ZipString(ZipFile): def __init__(self, chaine_donnees): ZipFile.__init__(self, cStringIO.StringIO(chaine_donnees)) 80 Chapitre 2 — Fichiers Discussion J’ai souvent été confronté à des fichiers zip provenant, par exemple, de champs BLOB d’une base de données ou reçus via une connexion réseau. Pour résoudre ce problème, j’avais pris l’habitude de sauvegarder ces données binaires dans un fichier temporaire que j’ouvrais ensuite avec le module standard zipfile. Je devais, bien sûr, vérifier que le fichier temporaire était détruit à la fin de l’opération. J’ai ensuite pensé à utiliser le module cStringIO pour cette tâche... et je ne suis jamais revenu en arrière. Le module cStringIO permet d’envelopper une chaîne d’octets pour qu’elle soit manipulée comme un objet fichier. Vous pouvez aussi effectuer des opérations telles qu’écrire dans une instance de cStringIO.StringIO comme s’il s’agissait d’un objet fichier et éventuellement récupérer son contenu sous la forme d’une chaîne d’octets. La plupart des modules Python manipulant des objets fichiers ne testent pas si vous leur passez un vrai fichier — tout objet apparenté à un fichier fait l’affaire ; le code du module se contente d’appeler sur l’objet les méthodes de fichiers dont il a besoin. Tant que l’objet fournit ces méthodes et qu’il répond correctement lorsqu’elles sont appelées, tout fonctionne très bien. Cet exemple démontre la puissance effrayante du polymorphisme par signature et explique pourquoi vous ne devriez quasiment jamais tester les types (écrire des abominations comme if type(x) is y ou même l’horreur un peu moins terrible if isinstance(x, y)) dans votre code ! Quelques modules de bas niveau, comme marshal, sont malheureusement inflexibles et exigent de « vrais » fichiers, mais zipfile n’en fait pas partie et cette recette montre comment vous simplifier la vie ! Le c au début du nom du module cStringIO indique qu’il s’agit d’un module spécifique au versions de Python écrites en C (CPython), optimisé pour la vitesse mais pas nécessairement inclus dans la bibliothèque standard des autres implémentations. Parmi ces implémentations alternatives, on en trouve qui peuvent être utilisées en production (comme Jython, qui est codé en Java et s’exécute sur une JVM) et d’autres, qui sont au stade expérimental (comme pypy, qui est codé en Python et qui produit du code machine, ainsi que IronPython, qui est codé en C# et s’exécute sur .NET de Microsoft). Cependant, la bibliothèque standard de Python contient toujours le module StringIO, codé en Python pur (par conséquent utilisable avec n’importe quelle implémentation de Python). Ce module implémente la même fonctionnalité que cStringIO (mais peut-être un peu plus lentement que la version de CPython). Il suffit donc de modifier légèrement votre instruction import pour importer cStringIO s’il est disponible ou StringIO sinon. Cette recette pourrait, par exemple, être réécrite de cette façon : import zipfile try: from cStringIO import StringIO except ImportError: from StringIO import StringIO class ZipString(ZipFile): def __init__(self, chaine_donnees): ZipFile.__init__(self, StringIO(chaine_donnees)) Avec cette modification, la recette peut fonctionner avec Jython et toutes les autres implémentations. Archiver une arborescence de fichiers dans une archive tar compressée 81 Voir aussi La documentation sur les modules zipfile et cStringIO dans la Library Reference et dans Python en concentré ; le site consacré à Jython, http://www.jython.org/ ; le site consacré à pypy, http://codespeak.net/pypy/ ; le site consacré à IronPython, http:// ironpython.com/. 2.11 Archiver une arborescence de fichiers dans une archive tar compressée Par Ed Gordon et Ravi Teja Bhupatiraju Problème Vous devez archiver tous les fichiers et répertoires d’une arborescence dans un fichier tar, en compressant les données avec le programme gzip bien connu ou avec le programme bzip2, qui garantit un fort taux de compression. Solution Le module tarfile de la bibliothèque standard reconnaît ces deux types de compression : il suffit de passer en paramètre à tarfile.TarFile.open la compression que vous souhaitez utiliser lorsque vous créez le fichier archive. Voici un exemple : import tarfile, os def creer_tar(rep_a_sauvegarder, rep_dest, compression = 'bz2'): if compression: extension_dest = '.' + compression else: extension_dest_ext = '' nom_archive = os.path.basename(rep_a_sauvegarder) nom_dest = '%s.tar%s' % (nom_archive, extension_dest) chemin_dest = os.path.join(rep_dest, nom_dest) if compression: comp_dest = ':' + compression else: comp_dest = '' resultat = tarfile.TarFile.open(chemin_dest, 'w' + comp_dest) resultat.add(rep_a_sauvegarder, nom_archive) resultat.close() return chemin_dest Discussion Le paramètre compression de la fonction creer_tar peut recevoir la chaîne 'gz' pour obtenir une compression par gzip au lieu de la compression bzip2 par défaut ou la chaîne vide si vous n’en souhaitez aucune. Il permet de créer une extension de fichier appropriée (.tar, .tar.gz ou .tar.bz2) et sert à déterminer la chaîne qui sera passée au deuxième paramètre de tarfile.TarFile.open : 'w' si l’on ne souhaite aucune compression, 'w:gz' ou 'w:bz2' pour obtenir les deux types de compression. Outre open, la classe tarfile.TarFile offre plusieurs autres classmethod permettant de créer une instance adaptée à vos besoins. Je trouve que open est plus pratique et 82 Chapitre 2 — Fichiers plus souple car on peut lui passer des informations sur la compression via son paramètre mode. Cependant, si vous voulez être sûr que la compression bzip2 sera toujours utilisée, par exemple, vous pourriez appeler plutôt bz2open. Une fois que l’on dispose d’une instance de la classe tarfile.TarFile configurée avec le type de compression choisi, la méthode add de cette instance suffit à nos besoins. Lorsque la chaîne rep_a_sauvegarder contient un nom de répertoire au lieu d’un nom de fichier, notamment, add ajoute récursivement toute l’arborescence partant de ce répertoire. Si, pour une autre occasion, nous voulons modifier ce comportement pour disposer d’un contrôle précis sur ce qui est archivé, nous pouvons passer à add le paramètre nommé supplémentaire recursive = False pour empêcher cette récursivité implicite. Après l’appel à add, il suffit que la fonction creer_tar ferme l’instance de TarFile et renvoie le chemin d’accès du fichier tar qui a été créé, au cas où l’appelant aurait besoin de cette information. Voir aussi La documentation sur le module tarfile dans la Library Reference. 2.12 Envoyer des données binaires sur la sortie standard de Windows Par Hamish Lawson Problème Vous voulez envoyer des données binaires (une image, par exemple) sur stdout avec Windows. Solution La fonction setmode du module msvcrt de la bibliothèque standard, spécifique à Windows, a été conçue pour cela : import sys if sys.platform == "win32": import os, msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) Vous pouvez maintenant appeler sys.stdout.write avec n’importe quelle chaîne d’octets en paramètre afin qu’elle soit produite sans modification sur la sortie standard. Discussion Bien qu’Unix ne fasse pas de différence entre les modes texte et binaire (il n’a pas besoin de la faire), Windows vous impose d’ouvrir un fichier en mode binaire si vous souhaitez lire des informations binaires ou y écrire des données binaires, comme des images. C’est un problème pour les programmes qui écrivent des données binaires sur la sortie standard (un script CGI, par exemple) car Python ouvre l’objet fichier sys.stdout pour vous, généralement en mode texte. Utiliser une syntaxe comme celle de iostream en C++ 83 Vous pouvez ouvrir stdout en mode binaire en utilisant l’option -u de la ligne de commande de l’interpréteur Python. Si vous utilisez Python 2.3 avec une installation standard et que vous savez, par exemple, que votre script CGI s’exécutera sur un serveur web Apache, vous pouvez le débuter par une ligne comme celle-ci : #! c:/python23/python.exe -u Malheureusement, vous ne pourrez pas toujours contrôler la ligne de commande sous laquelle votre script s’exécutera. L’approche utilisée par la solution de cette recette offre une alternative : la fonction setmode fournie par le module msvcrt spécifique à Windows permet de changer le mode du descripteur de fichier sous-jacent de stdout. Grâce à elle, vous pouvez être sûr que votre programme utilisera sys.stdout en mode binaire. Voir aussi La documentation du module msvcrt dans la Library Reference et dans Python en concentré. 2.13 Utiliser une syntaxe comme celle de iostream en C++ Par Erik Max Francis Problème Vous appréciez l’approche de C++ pour les entrées/sorties, qui repose sur des ostream et des manipulateurs (objets spéciaux provoquant des effets particuliers sur un flux lorsqu’il y sont insérés) et vous voulez l’utiliser dans vos programmes Python. Solution Python permet de surcharger les opérateurs en définissant des méthodes spéciales dans vos classes (des méthodes dont les noms débutent et se terminent par deux blancs soulignés). Pour utiliser << pour les sorties, comme en C++, il suffit de créer une classe pour les flux de sortie qui définit la méthode spéciale __lshift__ : # -*- coding: utf-8 -*class IOManipulator(object): def __init__(self, fonction = None): self.fonction = fonction def do(self, sortie): self.fonction(sortie) def do_endl(flux): flux.sortie.write('\n') flux.sortie.flush() endl = IOManipulator(do_endl) class OStream(object): def __init__(self, sortie = None): if sortie is None: import sys sortie = sys.stdout 84 Chapitre 2 — Fichiers self.sortie = sortie self.format = '%s' def __lshift__(self, truc): ''' Méthode spéciale appelée par Python lorsqu'on utilise l'opérateur << avec une opérande gauche de type OStream ''' if isinstance(truc, IOManipulator): truc.do(self) else: self.sortie.write(self.format % truc) self.format = '%s' return self def exemple_main(): cout = OStream() cout << "La moyenne de " << 1 << " et " << 3 << " est " << (1+3)/2 << endl # Produit la moyenne de 1 et 3 est 2 if __name__ == '__main__': exemple_main() Discussion Avec Python, il est assez facile d’envelopper des objets apparentés à des fichiers afin de simuler la syntaxe des ostream de C++. Cette recette montre comment coder l’opérateur d’insertion << dans ce but. Elle implémente également une classe IOManipulator (comme en C++) permettant d’appeler des fonctions quelconques sur un flux lors de son insertion, ainsi qu’un manipulateur prédéfini endl (devinez d’où vient son nom) pour produire un retour à la ligne et vider le flux. Les instances de la classe OStream contiennent un attribut format et le réinitialisent à la valeur par défaut '%s' après chaque appel à self.sortie.write : vous pouvez donc détourner des manipulateurs pour qu’ils sauvegardent temporairement le formatage de l’objet flux. Voici un exemple : #-*- coding: utf-8 -*def do_hex(stream): stream.format = '%x' hex = IOManipulator(do_hex) cout << 23 << ' en hexadécimal = ' << hex << 23 << ', en décimal = ' << 23 << endl # Produit : 23 en hexadécimal = 17, en décimal = 23 Certains détestent la syntaxe cout << qquechose de C++, d’autres l’adorent. Dans les cas comme celui de l’exemple de la recette, cette syntaxe est quand même plus simple et plus lisible que : print >> qquepart, "La moyenne de %d et %d est %f\n" % (1, 3, (1+3)/2) qui est la solution « native » de Python (et qui ressemble beaucoup à C dans ce cas précis), même si cela dépend en partie du fait que vous soyez habitués à C++ ou à C. Quoi qu’il en soit, cette recette vous donne le choix ! Même si vous n’utiliserez jamais cette approche, cette recette a permis de montrer la simplicité avec laquelle on peut surcharger un opérateur en Python. Revenir au début d’un fichier d’entrée 85 Voir aussi La documentation sur les objets fichiers et les méthodes spéciales comme __lshift__ dans la Library Reference et dans Python en concentré ; la recette 4.20 implémente une version Python de la fonction printf du langage C. 2.14 Revenir au début d’un fichier d’entrée Par Andrew Dalke Problème Vous voulez revenir au début d’un objet fichier d’entrée (contenant des données provenant d’une socket ou d’un autre descripteur de fichier ouvert en lecture) pour pouvoir le relire. Solution Enveloppez l’objet fichier dans une classe adaptée : #-*- coding: utf-8 -*from cStringIO import StringIO class RewindableFile(object): """ Enveloppe un descripteur de fichier pour pouvoir revenir au début. """ def __init__(self, fichier_entree): """ Enveloppe fichier_entree dans un objet apparenté à un fichier avec rembobinage. """ self.fichier = fichier_entree self.tampon_fichier = StringIO() self.au_debut = True try: self.debut = fichier_entree.tell() except (IOError, AttributeError): self.debut = 0 self._utilise_tampon = True def seek(self, deplacement, a_partir_de = 0): """ Positionnement sur une position d'octet. Doit être : a_partir_de == 0 et deplacement == self.debut """ if a_partir_de != 0: raise ValueError("whence= %r ; valeur attendue 0" % (a_partir_de,)) if deplacement != self.debut: raise ValueError("deplacement = %r ; valeur attendue %s" % (offset, self.debut)) self.rewind() def rewind(self): """ Méthode simple pour revenir au début. """ self.tampon_fichier.seek(0) self.au_debut = True def tell(self): """ Renvoie la position courante dans le fichier (doit être au début). """ 86 Chapitre 2 — Fichiers def def def def def if not self.au_debut: raise TypeError("RewindableFile ne peut renvoyer que le début du fichier") return self.debut _read(self, taille): if taille < 0: # Lit tout jusqu'à la fin du fichier y = self.fichier.read() if self._utilise_tampon: self.tampon_fichier.write(y) return self.tampon_fichier.read() + y elif taille == 0: # Inutile de lire la chaîne vide return "" x = self.tampon_fichier.read(taille) if len(x) < taille: y = self.fichier.read(taille - len(x)) if self._utilise_tampon: self.tampon_fichier.write(y) return x + y return x read(self, taille = -1): """ Lit jusqu'à 'taille' octets du fichier. La taille par défaut est -1, qui indique de lire jusqu'à la fin. """ x = self._read(taille) if self.au_debut and x: self.au_debut = False self._verif_pas_de_tampon() return x readline(self): """ Lit une ligne du fichier. """ # Peut-on l'obtenir à partir de tampon_fichier ? s = self.tampon_fichier.readline() if s[-1:] == "\n": return s # Non, on lit une ligne dans le fichier d'entrée. t = self.fichier.readline() if self._utilise_tampon: self.tampon_fichier.write(t) self._verif_pas_de_tampon() return s + t readlines(self): """Lit toutes les lignes restantes dans le fichier.""" return self.read().splitlines(True) _verif_pas_de_tampon(self): # Si 'pas_de_tampon' a été appelée et que l'on n'a plus besoin du tampon, # on le supprime et on redirige tout vers le fichier d'entrée initial. if not self._utilise_tampon and \ self.tampon_fichier.tell() == len(self.tampon_fichier.getvalue()): # Pour de meilleures performances, on lie à nouveau toutes les # méthodes concernées dans self for n in 'seek tell read readline readlines'.split(): setattr(self, n, getattr(self.fichier, n, None)) del self.tampon_fichier Revenir au début d’un fichier d’entrée 87 def pas_de_tampon(self): """Demande à RewindableFile d'arrêter d'utiliser le tampon une fois qu'il est plein""" self._utilise_tampon = False Discussion Parfois, les données provenant d’une socket ou d’un autre descripteur de fichier ne sont pas celles attendues. Supposons, par exemple, que vous lisiez des données sur un serveur bogué censé renvoyer un flux XML, mais produisant parfois un message d’erreur non formaté (ce scénario arrive souvent car de nombreux serveurs ne gèrent pas très bien les entrées incorrectes). La classe RewindableFile de cette recette permet de résoudre ce problème. r = RewindableFile(f) enveloppe f, le flux d’entrée initial, dans une instance de « fichier rembobinable », r, qui imite essentiellement le comportement de f tout en fournissant également un tampon. Les requêtes de lecture adressées à r sont transmises à f et les données lues sont ajoutées à un tampon qui est ensuite renvoyé à l’appelant. Ce tampon contient toutes les données qui ont été lues. On peut demander à r de se rembobiner avec rewind, c’est-à-dire de revenir à la position de départ. La prochaine requête de lecture lira alors les données dans le tampon, jusqu’à ce que celui-ci ait été totalement lu, auquel cas les données viendront à nouveau du flux d’entrée. Les nouvelles données lues sont également ajoutées au tampon. Lorsque vous n’avez plus besoin du tampon, appelez la méthode pas_de_tampon de r. Celle-ci indique à r qu’il peut se débarrasser du tampon après avoir lu son contenu. Après l’appel de cette méthode, le comportement de seek n’est plus défini. Supposons, par exemple, que votre serveur produise soit un message d’erreur de la forme ERREUR : impossible d'effectuer cette opération, soit un flux XML commençant par '<?xml'... : #-*- coding: utf-8 -*import RewindableFile fichier_source = urllib2.urlopen("http://qquepart/") fichier_source = RewindableFile.RewindableFile(fichier_source) s = fichier_source.readline() if s.startswith("ERREUR:"): raise Exception(s[:-1]) fichier_source.seek(0) fichier_source.pas_de_tampon() # On ne met plus les données dans le tampon ## ... Traitement du contenu XML de fichier_source ... La classe de cette recette n’utilise pas un idiome Python parfois utile : vous ne pouvez pas stocker de façon fiable les méthodes liées d’une instance RewindableFile (si vous ne savez pas ce qu’est une méthode liée, cela ne pose évidemment pas de problème car, en ce cas, vous ne souhaiterez sûremement pas les stocker non plus !). La raison de cette restriction est que lorsque le tampon est vide, le code de RewindableFile réaffecte les méthodes read, readlines, etc. du fichier d’entrée comme des variables d’instances de self. Cela améliore légèrement les performances, mais vous ne pouvez plus sauvegarder les méthodes liées (cela dit, c’est une fonctionnalité rarement utilisée). La recette 5.11 donne un autre exemple d’une technique similaire, où une instance modifie définitivement ses propres méthodes. 88 Chapitre 2 — Fichiers La méthode tell, qui renvoie la position courante dans un fichier, ne peut être appelée sur une instance de RewindableFile qu’immédiatement après sa création et avant toute lecture afin d’obtenir la position de début en octets. Son implémentation dans la classe RewindableFile tente d’obtenir la position réelle dans le fichier enveloppé et l’utilise comme position de début. Si le fichier enveloppé ne dispose pas de la méthode tell, l’implémentation de tell dans RewindableFile se contente de renvoyer 0. Voir aussi Le site http://www.dalkescientific.com/Python/ contient la dernière version du code de cette recette ; la documentation sur les objets fichiers et sur le module cStringIO dans la Library Reference et dans Python en concentré ; la recette 5.11 présente un exemple d’instance modifiant de façon irréversible son comportement en réaffectant ses méthodes. 2.15 Adapter un objet apparenté à un fichier à un véritable objet fichier Par Michael Kent Problème Vous devez passer un objet apparenté à un fichier (le résultat d’un appel à urllib.urlopen, par exemple) à une fonction ou une méthode qui attend de recevoir un vrai objet fichier (la fonction marshal.load, par exemple). Solution Nous devons écrire toutes les données de l’objet de départ dans un fichier temporaire sur disque, puis utiliser un vrai objet fichier pour ce fichier temporaire. Voici une fonction qui utilise cette approche : import types, os TAILLE_BLOC = 16 * 1024 def adapter_fichier(objFichier): if isinstance(objFichier, file): return objFichier objFichierTmp = os.tmpfile() while True: donnees = objFichier.read(TAILLE_BLOC) if not donnees: break objFichierTmp.write(donnees) objFichier.close() objFichierTmp.seek(0) return objFichierTmp Discussion Cette recette montre une application inhabituelle du motif de conception « Adaptateur » (ce qu’il faut faire lorsque l’on a un X et qu’on a plutôt besoin d’un Y). Bien que les motifs de conception soient généralement pensés selon une approche orientée Parcourir des arborescences de répertoires 89 objet et donc implémentés par des classes, rien n’oblige à utiliser ce paradigme. Ici, par exemple, nous n’avons pas vraiment besoin d’introduire une nouvelle classe car la fonction adapter_fichier suffit largement. Par conséquent, nous respectons le principe du rasoir d’Occam en n’introduisant pas d’entités inutiles. En règle générale, vous devriez raisonner en termes d’adaptation plutôt qu’en termes de tests de types, même si vous devez utiliser un utilitaire de bas niveau exigeant des types précis. Au lieu de déclencher une exception lorsque vous recevez un objet totalement approprié, mais n’appartenant pas au type attendu, pensez à la possibilité d’adapter ce que l’on vous a passé en ce dont vous avez besoin. De cette façon, votre code sera plus souple et plus facilement réutilisable. Voir aussi La documentation sur les objets fichiers prédéfinis et sur les modules tempfile et marshal dans la Library Reference et dans Python en concentré. 2.16 Parcourir des arborescences de répertoires Par Robin Parmar et Alex Martelli Problème Vous devez examiner un « répertoire » ou toute une arborescence de répertoires située dans un répertoire donné pour itérer sur les fichiers (et, éventuellement, les sous-répertoires) dont les noms correspondent à certains motifs. Solution Le générateur os.walk du module os de la bibliothèque standard suffit à cette tâche, mais vous pouvez l’améliorer un peu en écrivant votre propre fonction pour envelopper os.walk : #-*- coding: utf-8 -*import os, fnmatch def tous_les_fichiers(racine, motifs = '*', un_seul_niveau = False, repertoires = False): # Traduit en liste les motifs séparés par des points-virgules motifs = motifs.split(';') for chemin, sous_reps, fichiers in os.walk(racine): if repertoires: fichiers.extend(sous_reps) fichiers.sort() for nom in fichiers: for motif in motifs: if fnmatch.fnmatch(nom, motif): yield os.path.join(chemin, nom) break if un_seul_niveau: break 90 Chapitre 2 — Fichiers Discussion Le générateur standard de parcours d’arborescences de répertoires, os.walk, est puissant, simple et souple. Cependant, il lui manque quelques raffinements pouvant être utiles aux applications, comme la possibilité de choisir des fichiers correspondant à des motifs, un parcours linéaire de tous les fichiers (et, éventuellement, des répertoires) dans un ordre donné, ainsi que la possibilité d’examiner un seul répertoire, sans descendre dans ses sous-répertoires. Cette recette montre comment facilement ajouter ces fonctionnalités, en enveloppant os.walk dans un autre générateur et en utilisant le module standard fnmatch pour rechercher les noms de fichiers correspondant à des motifs. Les motifs de fichiers peuvent être insensibles à la casse (cela dépend des plates-formes) mais, sinon, ce sont des motifs de style Unix tels qu’ils sont fournis par le module fnmatch utilisé par cette recette. Pour préciser plusieurs motifs, séparez-les par des points-virgules : cela signifie donc qu’un motif ne pourra pas contenir de point-virgule. La ligne suivante, par exemple, permet d’obtenir la liste de tous les fichiers Python et HTML contenus dans le répertoire /tmp ou ses sous-répertoires : fichiers = list(tous_les_fichiers('/tmp', '*.py;*.htm;*.html')) Si vous souhaitez simplement traiter ces noms de fichiers les uns après les autres (les afficher chacun sur une ligne, par exemple), vous n’avez pas besoin de construire une liste : il suffit de boucler sur le résultat de l’appel à tous_les_fichiers : for chemin in tous_les_fichiers('/tmp', '*.py;*.htm;*.html'): print chemin Si votre plate-forme tient compte de la casse dans les noms de fichiers et que vous souhaitez effectuer des correspondances sensibles à la casse, vous devrez utiliser des motifs plus complexes : '*.[Hh][Tt][Mm][Ll]' au lieu de '*.html', par exemple. Voir aussi La documentation du module os.path et du générateur os.walk, ainsi que celle du module fnmatch dans la Library Reference et dans Python en concentré. 2.17 Remplacer une extension de fichier par une autre dans toute une arborescence de répertoires Par Julius Welby Problème Vous devez renommer des fichiers dans toute une arborescence de répertoires, en modifiant les noms de tous les fichiers ayant une extension donnée pour la remplacer par une autre. Remplacer une extension de fichier par une autre dans toute une arborescence de répertoires 91 Solution Avec la fonction os.walk de la bibliothèque standard de Python, il est assez facile de travailler sur tous les fichiers d’une arborescence : import os def echange_extensions(rep, ancienne, nouvelle): if ancienne[:1] != '.': ancienne = '.' + ancienne longueur = -len(ancienne) if nouvelle[:1] != '.': nouvelle = '.' + nouvelle for chemin, sous_reps, fichiers in os.walk(rep): for ancien_fichier in fichiers: if ancien_fichier[longueur:] == ancienne: ancien_fichier = os.path.join(chemin, ancien_fichier) nouveau_fichier = ancien_fichier[:longueur] + nouvelle os.rename(ancien_fichier, nouveau_fichier) if __name__=='__main__': import sys if len(sys.argv) != 4: print "Usage: echange_extensions racine ancienne nouvelle" sys.exit(100) echange_extensions(sys.argv[1], sys.argv[2], sys.argv[3]) Discussion Cette recette montre comment remplacer les extensions des noms de tous les fichiers d’un répertoire, de tous ses sous-répertoires, de tous leurs sous-répertoires, etc. Cette technique est utile pour modifier les extensions d’un lot de fichiers dans une structure de répertoires comme un site web, mais vous pouvez également l’utiliser pour corriger les erreurs faites lors d’une sauvegarde automatisée d’un ensemble de fichiers. Cette recette est utilisable à la fois comme module à importer et comme script à lancer à partir de la ligne de commande ; en outre, elle a été soigneusement écrite pour être indépendante des plates-formes. Vous pouvez passer les extensions en les précédant ou non d’un point car, si nécessaire, la recette l’insérera (cependant, à cause de cette fonctionnalité, ce code ne peut pas gérer les fichiers n’ayant aucune extension, même pas le point, ce qui peut être ennuyeux sur les systèmes Unix). Notre implémentation utilise des techniques que les puristes pourraient considérer comme étant de trop bas niveau : notamment, elle gère essentiellement les noms de fichiers et les extensions par des manipulations directes de chaînes plutôt qu’en utilisant les fonctions du module os.path. Ce n’est pas un gros problème : l’utilisation de os.path comme celle des fonctions puissantes de Python sur les chaînes conviennent toutes les deux. Voir aussi La page web de l’auteur, http://www.outwardlynormal.com/python/swapextensions.htm. 92 Chapitre 2 — Fichiers 2.18 Trouver un fichier dans un chemin donné Par Chui Tey Problème Vous voulez retrouver le premier fichier portant un certain nom le long d’un chemin de recherche (une chaîne de répertoires séparés par un caractère particulier). Solution Le travail consiste essentiellement à boucler sur les répertoires du chemin de recherche indiqué : #-*- coding: utf-8 -*import os def chercher_fichier(nom_fichier, chemin_rech, sep_chemin = os.pathsep): """ Recherche le fichier de nom indiqué dans le chemin spécifié.""" for chemin in chemin_rech.split(sep_chemin): candidat = os.path.join(chemin, nom_fichier) if os.path.isfile(candidat): return os.path.abspath(candidat) return None if __name__ == '__main__': chemin_rech = '/bin' + os.pathsep + '/usr/bin' # ; avec Windows, : avec Unix fichier = chercher_fichier('ls', chemin_rech) if fichier: print "Le fichier 'ls' se trouve dans %s." % fichier else: print "Le fichier 'ls' est introuvable." Discussion Le « problème » de cette recette est une tâche relativement fréquente qui peut être résolue très facilement en Python. D’autres recettes résolvent des problèmes similaires et apparentés : la recette 2.19 montre comment trouver tous les fichiers correspondant à un motif le long d’un chemin de recherche et la recette 2.20 explique comment rechercher des fichiers le long du propre chemin de recherche de Python. La boucle de recherche peut être codée de nombreuses façons différentes mais la méthode la plus simple et la plus rapide consiste à renvoyer le chemin (sous la forme d’un chemin absolu, pour des raisons d’uniformité et de commodité) dès qu’on l’a trouvé. L’instruction explicite return None après la boucle n’est pas strictement nécessaire, car Python renvoie None lorsqu’une fonction atteint la fin de son code. Cependant, grâce à cette instruction return, la fonctionnalité de rechercher_fichier apparaît clairement dès la première lecture. Voir aussi Les recettes 2.19 et 2.20 ; la documentation du module os dans la Library Reference et dans Python en concentré. Chercher dans un chemin les fichiers font les noms correspondent à un certain motif 93 2.19 Chercher dans un chemin les fichiers font les noms correspondent à un certain motif Par Bill McNeill et Andrew Kirkpatrick Problème Vous devez rechercher tous les fichiers dont les noms correspondent à un certain motif le long d’un chemin de recherche (une chaîne de répertoires séparés par un caractère particulier). Solution La solution consiste essentiellement à boucler sur les répertoires du chemin de recherche. Il est préférable d’encapsuler cette boucle dans un générateur : #-*- coding: utf-8 -*import glob, os def tous_les_fichiers(motif, chemin_rech, sep_chemin = os.pathsep): """ Produit tous les fichiers correspondant au motif dans chemin_rech.""" for chemin in chemin_rech.split(sep_chemin): for corresp in glob.glob(os.path.join(chemin, motif)): yield corresp Discussion L’un des avantages des générateurs est que vous pouvez aisément les utiliser pour n’obtenir que le premier élément, tous les éléments ou un nombre quelconque d’éléments. Le code suivant, par exemple, affichera le premier fichier correspondant à '*.pye' dans votre PATH : print tous_les_fichiers('*.pye', os.environ['PATH']).next() Pour afficher tous ces fichiers, ligne par ligne : for corresp in tous_les_fichiers('*.pye', os.environ['PATH']): print corresp Pour les afficher tous, sous la forme d’une liste : print list(tous_les_fichiers('*.pye', os.environ['PATH'])) J’utilise également la fonction tous_les_fichiers dans un script principal, afin d’afficher tous les fichiers de mon PATH correspondant à un certain motif. De cette façon, je peux savoir non seulement celui qui sera exécuté si j’appelle ce nom (le premier), mais également ceux qui sont « masqués » par le premier : if __name__ == '__main__': import sys if len(sys.argv) != 2 or sys.argv[1].startswith('-'): print 'Usage: %s <motif>' % sys.argv[0] sys.exit(1) corresps = list(tous_les_fichiers(sys.argv[1], os.environ['PATH'])) print '%d correspondances :' % len(corresps) for corresp in corresps: print corresp 94 Chapitre 2 — Fichiers Voir aussi La recette 2.18 décrit une approche plus simple pour trouver le premier fichier portant un certain nom le long d’un chemin ; les documentations des modules os et glob dans la Library Reference et dans Python en concentré. 2.20 Rechercher un fichier dans le chemin de recherche de Python Par Mitch Chapman Problème Une grosse application Python contient des fichiers ressources (des fichiers de projet Glade, des patrons SQL et des images, par exemple) ainsi que des paquetages Python. Vous voulez stocker ces fichiers avec les paquetages Python qui les utilisent. Solution Il faut rechercher les fichiers ou les répertoires dans le sys.path de Python : import sys, os class Erreur(Exception): pass def _chercher(chemin, fction_corresp = os.path.isfile): for rep in sys.path: candidat = os.path.join(rep, chemin) if fction_corresp(candidat): return candidat raise Erreur("Le fichier %s est introuvable." % chemin) def chercherFichier(chemin): return _chercher(chemin) def chercherRepertoire(chemin): return _chercher(chemin, fction_corresp = os.path.isdir) Discussion Les plus grosses applications Python sont formées de paquetages Python et de fichiers de ressources associés. Il est commode de stocker ces fichiers associés avec les paquetages Python qui les utilisent : en utilisant cette variante de la recette 2.18, il est facile de rechercher les fichiers ou les répertoires dont les noms sont relatifs au chemin de recherche de Python. Voir aussi La recette 2.18 ; la documentation du module os dans la Library Reference et dans Python en concentré. Modifier dynamiquement le chemin de recherche de Python 95 2.21 Modifier dynamiquement le chemin de recherche de Python Par Robin Parmar Problème Pour être importés, les modules doivent se trouver sur le chemin de recherche de Python, mais vous ne souhaitez pas configurer en permanence un chemin très long car cela ralentit l’exécution — vous voulez modifier ce chemin dynamiquement. Solution On ajoute simplement un « répertoire » au sys.path de Python, en prenant soin d’éviter les doublons : #-*- coding: utf-8 -*def AjoutSysPath(nouveau_chemin): """ AjoutSysPath(nouveau_chemin) : ajoute un répertoire au sys.path de Python. Le répertoire n'est pas ajouté s'il n'existe pas ou s'il est déjà dans le sys.path. Renvoie 1 si OK, -1 si nouveau_chemin n'existe pas, 0 s'il était déjà dans sys.path. """ import sys, os # Évite les chemins inexistants if not os.path.exists(nouveau_chemin): return -1 # Standardise le chemin. Windows ne tient pas compte de la casse, on le # met donc en minuscules si l'on est sous Windows. nouveau_chemin = os.path.abspath(nouveau_chemin) if sys.platform == 'win32': new_path = nouveau_chemin.lower() # Vérifie s'il est déjà dans sys.path for x in sys.path: x = os.path.abspath(x) if sys.platform == 'win32': x = x.lower() if nouveau_chemin in (x, x + os.sep): return 0 sys.path.append(nouveau_chemin) # Si on veut que nouveau_chemin ait priorité sur les répertoires déjà # dans sys.path, il suffit de faire sys.path.insert(0, nouveau_chemin) au # lieu de l'ajouter à la fin. return 1 if __name__ == '__main__': # Teste et affiche la syntaxe import sys print 'Avant :' for x in sys.path: print x if sys.platform == 'win32': print AjoutSysPath('c:\\Temp') print AjoutSysPath('c:\\temp') 96 Chapitre 2 — Fichiers else: print AjoutSysPath('/usr/lib/mes_modules') print 'Après :' for x in sys.path: print x Discussion Les modules doivent se trouver dans des répertoires du chemin de recherche de Python avant d’être importés, mais utiliser un énorme chemin permanent ralentit chaque opération d’importation réalisée par les scripts ou les applications. Cette recette simple ajoute dynamiquement un « répertoire » au chemin, mais uniquement si ce répertoire existe et s’il n’est pas déjà dans sys.path. sys.path est une liste, il est donc facile d’y ajouter des répertoires à la fin avec sys.path.append. Tout import effectué après cet append recherchera automatiquement le module dans le nouveau répertoire ajouté si les précédents ne lui ont pas donné satisfaction. Comme l’indique la solution, vous pouvez également utiliser sys.path.insert(0, ...) pour que le nouveau répertoire soit parcouru avant ceux qui se trouvaient déjà dans sys.path. Si sys.path contient des doublons ou qu’un répertoire inexistant lui est ajouté accidentellement, cela ne pose pas de problème : l’instruction import de Python est suffisamment maligne pour se protéger de telles situations. Cependant, à chaque fois qu’un tel problème survient lors d’une importation (recherches dupliquées infructueuses, erreurs du système d’exploitation devant être gérées correctement, etc.) cela se paye en termes de performances. Pour éviter cette dépense inutile, notre recette n’ajoute jamais un répertoire inexistant ou déjà présent dans sys.path. Les répertoires ajoutés par cette recette ne restent dans sys.path que le temps de l’exécution du programme, comme toute autre modification dynamique que vous pourriez apporter à ce chemin. Voir aussi La documentation des modules sys et os.path dans la Library Reference et dans Python en concentré. 2.22 Calculer le chemin relatif d’un répertoire par rapport à un autre Par Cimarron Taylor et Alan Ezust Problème Vous avez besoin de connaître le chemin relatif d’un répertoire par rapport à un autre — pour créer un lien symbolique ou une référence relative dans une URL, par exemple. Solution L’approche la plus simple consiste à découper les chemins en listes de répertoires, puis à travailler sur ces listes. À l’aide de deux fonctions auxiliaires relativement génériques, nous pouvons écrire le code suivant : Calculer le chemin relatif d’un répertoire par rapport à un autre 97 #-*- coding: utf-8 -*import os, itertools def tous_egaux(elements): ''' Renvoie True si tous les éléments sont égaux, False sinon.''' premier_element = elements[0] for autre_element in elements[1:]: if autre_element != premier_element: return False return True def prefixes_communs(*sequences): ''' Renvoie une liste des éléments communs au début de chaque séquence, puis une liste de listes formées des fins uniques de chaque séquence. ''' # Aucune séquence, c'est fini if not sequences: return [], [] # Boucle en parallèle sur les séquences communs = [] for elements in itertools.izip(*sequences): # Si tous les éléments ne sont pas égaux, on sort de la boucle if not tous_egaux(elements): break # On a un élément commun de plus, on l'ajoute et on continue la boucle communs.append(elements[0]) # Renvoie les préfixes communs et les fins uniques return communs, [ sequence[len(communs):] for sequence in sequences ] def chemin_relatif(p1, p2, sep = os.path.sep, rep_parent = os.path.pardir): ''' Renvoie le chemin relatif de p2 par rapport à p1 Notamment la chaîne vide si p1 == p2 ; p2, si p1 et p2 n'ont pas de préfixe commun. ''' communs, (u1, u2) = prefixes_communs(p1.split(sep), p2.split(sep)) if not communs: return p2 # Laisse le chemin absolu s'ils n'ont rien en commun return sep.join( [rep_parent]*len(u1) + u2 ) def test(p1, p2, sep = os.path.sep): ''' Appelle la fonction chemin_relatif et affiche les résultats.''' print "De", p1, "à", p2, " -> ", chemin_relatif(p1, p2, sep) if __name__ == '__main__': test('/a/b/c/d', '/a/b/c1/d1', '/') test('/a/b/c/d', '/a/b/c/d', '/') test('c:/x/y/z', 'd:/x/y/z', '/') Discussion L’essentiel du travail de cette recette est effectué par la fonction prefixes_communs, simple mais très générale, qui, à partir de N séquences, renvoie leurs préfixes communs et une liste de leurs fins uniques respectives. Pour calculer le chemin relatif entre deux chemins, nous pouvons ignorer leurs préfixes communs car nous n’avons besoin que du nombre approprié de marqueurs de remontée (généralement os.path.pardir — ../ sur les systèmes Unix ; il en faut autant que la longueur de la fin unique du répertoire de départ) suivi de la fin unique du répertoire de destination. La fonction chemin_relatif coupe donc les chemins en listes de répertoires, appelle prefixes_communs, puis effectue la construction que nous venons de décrire. 98 Chapitre 2 — Fichiers prefixe_communs s’articule sur la boucle for elements in itertools.izip(*sequences), en exploitant le fait que izip se termine avec le plus court des itérables qu’elle réunit. Le corps de la boucle a simplement besoin de terminer la boucle dès qu’il rencontre un tuple d’éléments (provenant chacun de chaque séquence d’après les spécifications de izip) qui ne sont pas tous égaux et de mémoriser les éléments qui le sont en ajoutant l’un d’entre eux à la fin de la liste communs. À la fin de la boucle, il reste à préparer les listes à renvoyer en supprimant les éléments déjà dans communs au début de chacune des séquences. La fonction tous_egaux peut également être écrite d’une façon totalement différente. Cele donnera un code moins simple et moins évident, mais intéressant : def tous_egaux(elements): return len(dict.fromkeys(elements)) == 1 ou, en Python 2.4 seulement, de manière équivalente mais plus concise : def tous_egaux(elements): return len(set(elements)) == 1 Dire que tous les éléments sont égaux revient à dire que l’ensemble (set) des éléments a une cardinalité (un nombre d’éléments) de un. Dans la variante utilisant dict.fromkeys, nous utilisons un dict pour représenter l’ensemble : elle fonctionne donc avec Python 2.3 comme avec Python 2.4. La variante utilisant set est plus claire, mais elle ne fonctionne qu’avec Python 2.4 (vous pouvez également la faire fonctionner avec Python 2.3 et 2.4 en utilisant le module sets de la bibliothèque standard). Voir aussi La documentation des modules os et itertools dans la Library Reference et dans Python en concentré. 2.23 Lire un caractère non tamponné de façon portable Par Danny Yoo Problème Votre application doit lire des caractères simples et non tamponnés sur l’entrée standard et elle doit fonctionner sur des systèmes Windows comme sur des systèmes Unix. Solution Lorsque l’on a besoin d’une solution multi-plateforme et que l’on dispose de solutions spécifiques, il suffit de les envelopper pour pour donner l’illusion d’une solution unique : try: from msvcrt import getch except ImportError: ''' On n'est pas sous Windows, on essaie donc l'approche Unix.''' Lire un caractère non tamponné de façon portable 99 def getch(): import sys, tty, termios fd = sys.stdin.fileno() ancienne_config = termios.tcgetattr(fd) try: tty.setraw(fd) car = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, ancienne_config) return car Discussion Avec Windows, le module msvcrt de la bibliothèque standard fournit la fonction getch pour lire au clavier un caractère unique, non tamponné, sans l’afficher à l’écran. Cependant, ce module ne fait pas partie de la bibliothèque standard de Python sur les plates-formes Unix et apparentées, comme Linux et Mac OS X. Sur celles-ci, les modules tty et termios (qui, inversement, ne sont pas disponibles sur Windows), permettent d’obtenir la même fonctionnalité. Le point essentiel est que au niveau du code de l’application, nous ne devrions jamais avoir à nous soucier de ces problèmes. Nous devrions écrire notre application de façon portable, indépendamment des plates-formes, en faisant confiance aux fonctions de la bibliothèque pour gommer les différences entre les systèmes. La bibliothèque standard de Python remplit admirablement ce rôle dans la plupart des cas, mais pas toujours ; le problème posé par cette recette est un exemple de situation où cette bibliothèque ne fournit pas directement de solution portable. Si nous ne pouvons pas trouver de solution portable toute prête dans la librairie standard, nous devrions en écrire une et l’intégrer sous forme de paquetage à notre bibliothèque personnelle. La solution de cette recette, outre qu’elle résout le problème initial, montre également une méthode générale pour créer ce genre de paquetage (vous pouvez également tester sys.platform, mais je préfère ma solution). Notre module de bibliothèque devrait utiliser une clause try pour tenter d’importer le module standard dont il a besoin sur une plate-forme donnée et une clause except ImportError qui sera déclenchée lorsqu’il s’exécute sur une plate-forme différente. Dans le corps de cette clause except, notre module peut alors utiliser l’approche qui fonctionnera sur l’autre plate-forme. Dans quelques cas rares, vous pouvez avoir besoin de prendre en compte plus de deux plates-formes mais, le plus souvent, vous n’aurez besoin que d’une approche pour Windows et d’une autre pour tous les autres systèmes. En effet, la plupart des plates-formes non Windows actuelles sont généralement des systèmes Unix ou apparentés. Voir aussi La documentation de msvcrt, tty et termios dans la Library Reference et dans Python en concentré. 100 Chapitre 2 — Fichiers 2.24 Compter les pages d’un document PDF sur Mac OS X Par Dinu Gherman et Dan Wolfe Problème Vous utilisez une version assez récente de Mac OS X (version 10.3 « Panther » ou supérieure) et vous voulez connaître le nombre de pages d’un document PDF. Solution Le format PDF et Python sont directement intégrés à Mac OS X (à partir de sa version 10.3), ce qui simplifie beaucoup la solution : #!/usr/bin python import CoreGraphics def pageCount(cheminPdf): """Renvoie le nombre de page du document PDF cheminPdf.""" pdf = CoreGraphics.CGPDFDocumentCreateWithProvider( CoreGraphics.CGDataProviderCreateWithFilename(cheminPdf) ) return pdf.getNumberOfPages() if __name__ == '__main__': import sys for chemin in sys.argv[1:]: print pageCount(chemin) Discussion Une alternative possible à cette recette pourrait consister à utiliser l’extension PyObjC qui (entre autres choses) permet à Python de réutiliser toute la puissance des frameworks Foundation et AppKit fournis par Mac OS X. Cette approche vous permettrait d’écrire un script Python pouvant également s’exécuter sur des versions plus anciennes de ce système d’exploitation, comme sa version 10.2 « Jaguar ». Cependant, exiger une version 10.3 ou ultérieure de Mac OS X 10.3 garantit que nous pouvons utiliser l’installation de Python intégrée au système, ainsi que des gâteries comme le module d’extension Python CoreGraphics (faisant également partie de Mac OS X « Panther »), qui permet à votre code Python de réutiliser directement Quartz, l’excellent moteur graphique d’Apple. Voir aussi Le site consacré à PyObjC est http://pyobjc.sourceforge.net/ ; pour plus d’informations sur le module CoreGraphics, consultez la page http://www.macdevcenter.com/pub/a/mac/ 2004/03/19/core_graphics.html. Modifier les attributs de fichiers sur Windows 101 2.25 Modifier les attributs de fichiers sur Windows Par John Nielsen Problème Vous devez configurer les attributs d’un fichier sous Windows ; vous souhaitez, par exemple, le mettre en lecture seule, en mode archivé, etc. Solution Grâce au module win32api de PyWin32, qui fournit une fonction SetFileAttributes, la solution est assez simple : #-*- coding: utf-8 -*import win32con, win32api, os # On crée un fichier, juste pour montrer comment le manipuler fichier = 'test' f = open('test', 'w') f.close() # On cache le fichier : win32api.SetFileAttributes(fichier, win32con.FILE_ATTRIBUTE_HIDDEN) # On le met en lecture seule : win32api.SetFileAttributes(fichier, win32con.FILE_ATTRIBUTE_READONLY) # Pour pouvoir supprimer le fichier, il faut le remettre en mode normal : win32api.SetFileAttributes(fichier, win32con.FILE_ATTRIBUTE_NORMAL) # Enfin, on supprime le fichier que l'on a créé. os.remove(fichier) Discussion Une utilisation intéressante de win32api.SetFileAttributes est qu’elle permet d’autoriser la suppression d’un fichier. En effet, os.remove peut échouer sous Windows si les attributs du fichiers ne sont pas en mode normal. Pour contourner ce problème, il suffit donc d’utiliser l’appel Win32 SetFileAttributes pour le convertir en fichier normal, comme on le fait à la fin de la solution de cette recette. Il faut, bien sûr, être prudent lorsque l’on fait cette manœuvre car il y a peut-être une bonne raison pour laquelle le fichier n’est pas « normal ». Le fichier ne doit être supprimé que si vous savez ce que vous faites ! Voir aussi La documentation sur le module win32file, disponible à l’URL http://aspn. activestate.com/ASPN/docs/ActivePython/2.3/pywin32/win32file.html ou http://aspn. activestate.com/ASPN/docs/ActivePython/2.4/pywin32/win32file.html suivant la version de Python que vous utilisez. 102 Chapitre 2 — Fichiers 2.26 Extraire du texte d’un document OpenOffice.org Par Dirk Holtwick Problème Vous devez extraire le contenu textuel (avec ou sans le marquage XML) d’un document OpenOffice.org. Solution Un document OpenOffice.org est simplement un fichier zip rassemblant des fichiers XML respectant un standard bien défini. Pour accéder à nos précieuses données, nous n’avons même pas besoin que OpenOffice.org soit installé : #-*- coding: utf-8 -*import zipfile, re re_suppr_xml = re.compile("<[^>]*?>", re.DOTALL|re.MULTILINE) def convertir_OO(nomfichier, texte_seul = True): """ Convertit un document OpenOffice.org en XML ou en texte. """ fz = zipfile.ZipFile(nomfichier, "r") donnees = fz.read("content.xml") fz.close() if texte_seul: donnees = " ".join(re_suppr_xml.sub(" ", donnees).split()) return donnees if __name__=="__main__": import sys if len(sys.argv)>1: for nomdoc in sys.argv[1:]: print 'Texte de', nomdoc, ':' print convertir_OO(nomdoc) print 'XML de', nomdoc, ':' print convertir_OO(nomdoc, texte_seul = False) else: print 'Donnez des noms de documents OO.o pour les lire aux formats texte et XML.' Discussion Les documents OpenOffice.org sont des fichiers zip qui contiennent toujours un fichier content.xml en plus d’autres contenus. Le travail de cette recette, par conséquent, se ramène essentiellement à extraire ce fichier. Par défaut, la recette supprime les balises XML à l’aide d’une expression régulière, découpe le résultat selon les espaces et le réunit à nouveau avec un espace unique pour économiser la place occupée. Vous pourriez, bien sûr, utiliser un parseur XML pour obtenir les informations de façon bien plus riche et structurée mais, si nous ne nous intéressons qu’au contenu textuel, cette approche rapide et toute prête peut suffire. L’expression régulière re_suppr_xml capture n’importe quelle balise XML (ouvrante ou fermante) comprise entre < et >. À l’intérieur de la fonction convertir_OO, dans les instructions conditionnées par if texte_seul, nous utilisons cette expression régulière pour remplacer chaque balise XML par un espace, puis nous normalisons les Extraire le texte d’un document Word de Microsoft 103 espaces en découpant (c’est-à-dire en appelant la méthode split des chaînes, qui divise un texte sur toute suite d’espaces) et en réunissant (avec " ".join, pour utiliser un seul caractère espace comme jointure). Ce traitement de division et de réunion revient donc à remplacer toute suite d’espaces par un seul. Vous trouverez des méthodes plus élaborées pour extraire tout le texte d’un document XML dans la recette 8.3. Voir aussi La documentation des modules zipfile et re dans la Library Reference ; le site web de OpenOffice.org, http://www.openoffice.org/ ; la recette 8.3. 2.27 Extraire le texte d’un document Word de Microsoft Par Simon Brunning et Pavel Kosina Problème Sous Windows, vous voulez extraire le contenu textuel de tous les documents Word d’une arborescence dans des fichiers textes correspondants. Solution Grâce à l’extension PyWin32, nous pouvons accéder à Word via COM pour effectuer cette conversion : #-*- coding: utf-8 -*import fnmatch, os, sys, win32com.client wordapp = win32com.client.gencache.EnsureDispatch("Word.Application") try: for chemin, reps, fichiers in os.walk(sys.argv[1]): for nomfic in fichiers: if not fnmatch.fnmatch(nomfic, '*.doc'): continue doc = os.path.abspath(os.path.join(chemin, nomfic)) print "Traitement de %s" % doc wordapp.Documents.Open(doc) doc_en_txt = doc[:-3] + 'txt' wordapp.ActiveDocument.SaveAs(doc_en_txt, FileFormat = win32com.client.constants.wdFormatText) wordapp.ActiveDocument.Close() finally: # Garantit que Word est correctement fermé, même si on reçoit une exception wordapp.Quit() Discussion Un aspect pratique de la plupart des applications Windows est que vous pouvez les scripter avec COM ; grâce à l’extension PyWin32 c’est chose facile à partir de Python car cette extension permet d’écrire des scripts Python capables d’effectuer différents types de tâches Windows. Celui de cette recette pilote Microsoft Word pour extraire 104 Chapitre 2 — Fichiers le texte de chaque fichier .doc d’une arborescence de répertoires dans des fichiers .txt correspondants. Grâce à la fonction os.walk, nous pouvons accéder à chaque sousrépertoire d’une arborescence à l’aide d’une simple instruction for, sans avoir besoin de la récursivité. La fonction fnmatch.fnmatch permet de vérifier qu’un nom de fichier correspond à un joker particulier, ici '*.doc'. Lorsque l’on a déterminé le nom d’un document Word, on le traite avec les fonctions de os.path pour le transformer en chemin absolu et on demande à Word de l’ouvrir, de le sauvegarder au format texte et de le refermer. Si vous ne possédez pas Word, vous devrez utiliser une approche totalement différente. Une possibilité consiste à utiliser OpenOffice.org, qui est capable d’ouvrir des documents Word. Une autre est d’utiliser un programme spécialement conçu pour lire les documents Word, comme Antiword, que vous trouverez à http:// www.winfield.demon.nl/. Cependant, nous n’avons pas exploré ces possibilités. Voir aussi Python Programming on Win32 de Mark Hammond et Andy Robinson (O’Reilly), pour la documentation de PyWin32 ; http://msdn.microsoft.com pour la documentation de Microsoft sur le modèle objet de Word ; les sections consacrées aux modules fnmatch et os.path, ainsi qu’à la fonction os.walk dans la Library Reference et dans Python en concentré. 2.28 Verrouiller un fichier avec une API portable Par Jonathan Feinberg et John Nielsen Problème Vous devez verrouiller des fichiers dans un programme s’exécutant à la fois sous Windows et sur des systèmes Unix, mais la bibliothèque standard de Python n’offre que des solutions spécifiques pour cette tâche. Solution Lorsque la bibliothèque standard de Python n’offre pas de solution portable, il est souvent possible d’en implémenter une par soi-même : #-*- coding: utf-8 -*import os # Nécessite win32all pour fonctionner avec Windows (NT, 2K, XP, _ni_ /95 ni /98) if os.name == 'nt': import win32con, win32file, pywintypes LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK LOCK_SH = 0 # Valeur par défaut LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY __overlapped = pywintypes.OVERLAPPED() def lock(fichier, options): hfile = win32file._get_osfhandle(fichier.fileno()) win32file.LockFileEx(hfile, options, 0, 0xffff0000, __overlapped) def unlock(fichier): hfile = win32file._get_osfhandle(fichier.fileno()) Verrouiller un fichier avec une API portable 105 win32file.UnlockFileEx(hfile, 0, 0xffff0000, __overlapped) elif os.name == 'posix': from fcntl import LOCK_EX, LOCK_SH, LOCK_NB def lock(fichier, options): fcntl.flock(fichier.fileno(), options) def unlock(fichier): fcntl.flock(fichier.fileno(), fcntl.LOCK_UN) else: raise RuntimeError("PortaLocker ne fonctionne que sur NT ou les systèmes POSIX.") Discussion Lorsque plusieurs programmes ou plusieurs threads doivent accéder à un fichier partagé, il est prudent de s’assurer que ces accès seront synchronisés afin que deux processus ne puissent pas modifier ce fichier en même temps. Dans certains cas, une absence de synchronisation pourrait même corrompre tout le fichier. Cette recette fournit deux fonctions, lock et unlock, permettant respectivement de demander et de relâcher des verrous sur les fichiers. L’utilisation du module portalocker.py consiste simplement à appeler la fonction lock en lui passant le nom du fichier ainsi qu’un paramètre précisant le type de verrou désiré : Verrou partagé (par défaut) Ce verrou interdit à tous les processus d’écrire dans le fichier, y compris à celui qui l’a verrouillé en premier. Tous les processus, par contre, peuvent lire le fichier verrouillé. Verrou exclusif Ce verrou interdit à tous les autres processus de lire ou d’écrire dans le fichier. Verrou non bloquant Lorsqu’un verrou est non bloquant, la fonction se termine immédiatement si elle n’a pas pu obtenir le verrou demandé. Sinon, elle attend. LOCK_NB peut être combiné avec LOCK_SH ou LOCK_EX en utilisant l’opérateur OU bit à bit de Python, c’est-à-dire la barre verticale (|). Voici un exemple : import portalocker fichier = open("un_fichier", "r+") portalocker.lock(fichier, portalocker.LOCK_EX) Les implémentations des fonctions lock et unlock sont totalement différentes selon les systèmes. Sur les plates-formes Unix (comme Linux et Mac OS X), la recette utilise la fonctionnalité fournie par le module standard fcntl. Sur les systèmes Windows (NT, 2000, XP — cela ne fonctionne pas sur les anciennes plates-formes Win/95 et Win/98 parce qu’elle ne disposent tout simplement pas des éléments nécessaires !), la recette utilise la module win32file qui fait partie du paquetage PyWin32 bien connu, conçu par Mark Hammond. Cependant, le point important est que les fonctions (et les options passées à la fonction lock) ont été conçues pour se comporter de la même façon sur toutes les plates-formes, malgré les différences d’implémentation. La possibilité d’écrire des paquetages de fonctionnalités équivalentes mais implémentées 106 Chapitre 2 — Fichiers différemment permet d’écrire aisément des applications portables, ce qui est l’une des grandes forces de Python. Lorsque vous écrivez un programme portable, il est souhaitable que la fonctionnalité qu’il utilise soit elle-même encapsulée de façon portable. Pour le verrouillage de fichiers, notamment, cette précaution est très utile pour les utilisateurs de Perl, qui sont habitués à un appel système lock transparent quelle que soit la plate-forme qu’ils utilisent. Plus généralement, if os.name== ne doit pas apparaître dans le code d’une application : ce type de test devrait toujours avoir lieu dans la bibliothèque standard ou dans un module indépendant de l’application, comme nous l’avons fait ici. Voir aussi La documentation sur le module fcntl dans la Library Reference ; la documentation sur le module win32file, http://aspn.activestate.com/ASPN/docs/ActivePython/2.3/pywin32/ win32file.html ou http://aspn.activestate.com/ASPN/docs/ActivePython/2.4/pywin32/win32file. html suivant la version de Python que vous utilisez ; le site web de Jonathan Feinberg (http://MrFeinberg.com). 2.29 Ajouter des numéros de versions à des noms de fichiers Par Robin Parmar et Martin Miller Problème Vous voulez faire une copie de sauvegarde d’un fichier avant de le modifier, en utilisant la convention standard consistant à ajouter un numéro de version de trois chiffres à la fin du nom de l’ancien fichier. Solution Il suffit de coder une fonction pour effectuer la sauvegarde de façon appropriée : #-*- coding: utf-8 -*def VersionFile(spec_fichier, type_vers = 'copier'): import os, shutil if os.path.isfile(spec_fichier): # Vérification du paramètre 'type_vers' if type_vers not in ('copier', 'renommer'): raise ValueError, 'Le type_vers %r est inconnu' % (type_vers,) # Détermine la racine du nom du fichier pour se débarrasser de l'extension n, e = os.path.splitext(spec_fichier) # Est-ce que e est un nombre de 3 chiffres précédé d'un point ? if len(e) == 4 and e[1:].isdigit(): num = 1 + int(e[1:]) racine = n else: num = 0 racine = spec_fichier # Trouve la version disponible suivante du fichier Ajouter des numéros de versions à des noms de fichiers 107 for i in xrange(num, 1000): nouveau_fichier = '%s.%03d' % (racine, i) if not os.path.exists(nouveau_fichier): if type_vers == 'copier': shutil.copy(spec_fichier, nouveau_fichier) else: os.rename(spec_fichier, nouveau_fichier) return True raise RuntimeError, "Impossible de %s %r, tous les noms sont pris" % (type_vers, spec_fichier) return False if __name__ == '__main__': import os # Crée un fichier 'test.txt' bidon tfn = 'test.txt' open(tfn, 'w').close() # Crée 3 versions print VersionFile(tfn) # Affiche : True print VersionFile(tfn) # Affiche : True print VersionFile(tfn) # Affiche : True # Supprime tous les test.txt* venant d'être créés for x in ('', '.000', '.001', '.002'): os.unlink(tfn + x) # Montre ce qui se passe si le fichier n'existe pas print VersionFile(tfn) # Affiche : False print VersionFile(tfn) # Affiche : False Discussion Le but de la fonction VersionFile est de garantir qu’un fichier existant sera copié (ou renommé, selon ce qu’indique le second paramètre facultatif) avant que vous ne l’ouvriez pour le modifier. Il est prudent de faire ce type de sauvegarde avant de modifier un fichier (certains regrettent encore cette fonctionnalité du bon vieux système d’exploitation VMS, qui le faisait automatiquement !). La véritable copie ou le renommage sont effectués, respectivement, par shutil.copy et os.rename ; le seul problème à résoudre est le nom à utiliser pour la cible. Une méthode courante pour déterminer les noms des sauvegardes consiste à ajouter des numéros de versions à la fin des noms de fichiers et à incrémenter successivement ces numéros. Cette recette détermine le nouveau nom en commençant par extraire la racine du nom du fichier (au cas où vous appelleriez la fonction sur un nom de fichier contenant déjà un numéro de version), puis lui ajoute successivement les extensions .000, .001, etc. jusqu’à ce qu’un nom construit de cette façon ne corresponde à aucun fichier existant. Alors, et seulement alors, ce nom sera utilisé comme cible de la copie ou du renommage. Vous remarquerez que VersionFile est limité à 1000 versions : si vous atteignez cette limite, vous devrez avoir mis au point un plan d’archivage. Le fichier doit exister avant que sa première version ne soit créée — vous 108 Chapitre 2 — Fichiers ne pouvez pas sauvegarder ce qui n’existe pas encore. Cependant, si le fichier n’existe pas, la fonction VersionFile renverra simplement False (alors qu’elle renvoie True si le fichier existe et a pu recevoir un numéro de version). Voir aussi La documentation des modules os et shutil dans la Library Reference et dans Python en concentré. 2.30 Calculer une somme de contrôle CRC-64 Par Gian Paolo Ciceri Problème Vous souhaitez garantir l’intégrité de certaines données en calculant leur CRC et en respectant les spécifications CRC-64 de la norme ISO-3309. Solution Comme la bibliothèque standard de Python ne propose pas d’implémentation de CRC-64 (mais une de CRC-32 dans la fonction zlib.crc32), nous devons la programmer nous-même. Heureusement, Python peut effectuer les mêmes opérations bit à bit (masquage, décalage, et bit à bit, ou bit à bit, ou exclusif, etc.) que le langage C (en fait, il utilise exactement la même syntaxe) : il est donc facile de traduire une implémentation de référence de CRC-64 en une fonction Python : #-*- coding: utf-8 -*# Prépare deux tableaux auxiliaires (à l'aide d'une fonction pour aller plus # vite) puis supprime la fonction puisque l'on n'en a plus besoin : CRCTableh = [0] * 256 CRCTablel = [0] * 256 def _inittables(CRCTableh, CRCTablel, POLY64REVh, BIT_TOGGLE): for i in xrange(256): partl = i parth = 0L for j in xrange(8): rflag = partl & 1L partl >>= 1L if parth & 1: partl ^= BIT_TOGGLE parth >>= 1L if rflag: parth ^= POLY64REVh CRCTableh[i] = parth CRCTablel[i] = partl # Premiers 32 bits du polynôme générateur de CRC64 (les 32 bits de poids faible # sont supposés être à zéro) et masque d'inversion de bit utilisé par _inittables POLY64REVh = 0xd8000000L BIT_TOGGLE = 1L << 31L # Lance la fonction pour préparer les tableaux _inittables(CRCTableh, CRCTablel, POLY64REVh, BIT_TOGGLE) Calculer une somme de contrôle CRC-64 109 # Supprime tous les noms dont on n'a plus besoin, dont la fonction del _inittables, POLY64REVh, BIT_TOGGLE # Ce module expose deux fonctions : crc64 et crc64digest def crc64(octets, (crch, crcl) = (0,0)): for octet in octets: shr = (crch & 0xFF) << 24 temp1h = crch >> 8L temp1l = (crcl >> 8L) | shr indice = (crcl ^ ord(octet)) & 0xFF crch = temp1h ^ CRCTableh[indice] crcl = temp1l ^ CRCTablel[indice] return crch, crcl def crc64digest(chaine): return "%08X%08X" % (crc64(chaine)) if __name__ == '__main__': # Petit test exécuté lorsque ce module est utilisé comme un script assert crc64("IHATEMATH") == (3822890454, 2600578513) assert crc64digest("IHATEMATH") == "E3DCADD69B01ADD1" print 'crc64 : succès du test' Discussion Les sommes de contrôle cycliques (CRC, pour Cyclic redundancy check) sont fréquemment employées pour vérifier que des données (un fichier, notamment) n’ont pas été endommagées accidentellement. Les CRC permettent de détecter facilement ce type de dommage mais, contrairement à d’autres sommes de contrôle cryptographiques, ils ne sont pas conçus pour résister à une attaque hostile. Ils peuvent cependant être calculés bien plus rapidement que les autres types de sommes de contrôle, ce qui les rend intéressants dans les cas où le seul dommage contre lequel ont souhaite se prémunir est de type accidentel et non délibéré. Mathématiquement parlant, un CRC est calculé comme un polynôme sur les bits des données contrôlées. En pratique, comme le montre la recette, l’essentiel du calcul peut être effectué une fois pour toute et résumé dans des tableaux qui, s’ils sont correctement indicés, donnent la contribution de chaque octet des données d’entrée au résultat. Par conséquent, après l’initialisation (que nous réalisons à l’aide d’une fonction auxiliaire car le calcul en Python est bien plus rapide lorsque l’on utilise les variables locales d’une fonction plutôt que des variables globales), le calcul du CRC proprement dit est assez rapide. Le calcul des tableaux et leur utilisation pour le calcul du CRC nécessite un grand nombre d’opérations bit à bit mais, heureusement, Python est, dans ce domaine, aussi bon que des langages comme C. L’algorithme utilisé pour calculer le CRC-64 standard est décrit dans la norme ISO3309 et cette recette se contente de l’implémenter. Le polynôme générateur utilisé est x64 + x4 + x3 + x + 1 (la section Voir aussi de cette recette vous indiquera où obtenir les informations sur ce calcul). Nous représentons le résultat sur 64 bits comme une paire de int Python, contenant les 32 bits de poids faible et les 32 bits de poids fort du résultat. Pour pouvoir calculer le CRC de façon incrémentale dans les cas où les données n’arrivent que par parties à la fois, nous pouvons appeler la fonction crc64 en lui fournissant éventuellement une « valeur initiale » pour la paire (crch, crcl), qui est censée être obtenue en appelant crc64 sur les parties précédentes des données. Pour calculer le CRC d’un 110 Chapitre 2 — Fichiers seul coup, il suffit de passer les données à la fonction (sous la forme d’une chaîne d’octets) car, dans ce cas, nous initialisons le résultat à (0, 0) par défaut. Voir aussi W.H. Press, S.A. Teukolsky, W.T. Vetterling et B.P. Flannery, Numerical Recipes in C, 2e édition. (Cambridge University Press), p. 896 et ss. Index Symboles #! (shebang), 69 ''.join (jointure avec une chaîne vide), 9 ''.join (jointure sur chaîne vide), 33 +, opérateur, 13 \ (anti-slash double), 4, 59 , (virgule), 15 \\ (anti-slash), 439 / (barre de fraction), 59 % (opérateur de formatage des chaînes), 7, 13, 37 <<, opérateur, 83 4Suite, paquetage, 318 A accès à une base de données MySQL, 273 aux attributs des objets instances, 192 Access, 510 Active Server Pages (ASP), 381 ActivePython, 288 ActiveX Data Objects (ADO), 162 adaptateur, motif de conception, 88 __add__, méthode spéciale, 194 ADO (ActiveX Data Objects), 162 utilisation de Microsoft Jet, 287 affectation multiple, 422-425 affectation/test du résultat d’un expression, 181-183 affichage du contenu des curseurs de bases de données, 283-285 algorithme de Luhn, 143 analyse lexicale texte, 3 ancien style, classes, 196 anti-slash (\), 4, 59 anti-slash double (\\ double), 439 API portables, verrouillage de fichier, 104 append, méthode (objets listes), 15 applications et conception des bases de données relationnelles, 252 arborescences de répertoires parcours, 89 archivage d’une arborescence dans un fichier tar compressé, 81 *args, syntaxe, 163 arithmétique décimale, 135 simulation de virgule flottante binaire, 416 ASCII, caractères dans les chaînes, 4 ASP (Active Server Pages), 381 assainit, fonction, 354 assert, 231 attributs ajout à des classes, 202 cacher ceux fournis par délégation, 209 configuration restriction dans les classes, 202 méthode __setattr__, 200 nommés, accès aux éléments d’un tuple, 212-214 rechercher dans les objets, 228-231 restriction d’ajout dans les classes, 202 authentication navigation HTTPS via des proxies, 395 512 B barre de fraction (/), 59 bases de données, 251, 270-293 affichage des curseurs, 283-285 applications fournissant un support des transactions et un contrôle de la concurrence, 253 persistance et compression, 259 problèmes de programmation, 251 relationnelles, 252 basestring, type, 9 Berkeley DB (Berkeley database), persistance des données, 270-272 binaires, données envoyées sur la sortie standard de Windows, 82 binaires, fichiers écriture séquentielle d’octets, 67 lecture directe d’octets, 75 lecture séquentielle d’octets, 60 BLOB (binary large objects), 253 stockage MySQL, 274 PostgreSQL, 275 SQLite, 277 boîtes aux lettres POP3, messages mal formés, 357, 358 Borg, 192 Borg, classe, 238 remplacement du motif de conception Singleton, 235-239 Borg, non-motif de conception, alternative à, 237 boucles, désactivation pendant le débogage, 296 bsddb, module, 270-272 bsddb3, module, 270-272 __builtin__, module, 188 C C++, syntaxe des entrées/sorties, 83 C, langage de programmation cPickle, module prédéfini pour stocker/restaurer les données, 253 cache mise en cache automatique, 29 caches valeurs des attributs, 472-474 caractères, 7 caractères de fin de ligne, 60 Celsius, températures, conversions, 198 Index center, méthode (objets chaînes), 11 CGI (Common Gateway Interface), 303, 382 dépôt de fichiers, 386 programmes, 303 scripts (voir CGI, scripts) tests, 382-384 CGI, scripts, 382 traitement des URL, 385-386 utilisation de Microsoft Jet via ADO, 287 chaînes alignement, 11 contenant des fichiers zip, 79 extraction d’octets, 28 suppression des espaces, 12 tester les caractéristiques des chaînes, 9 (voir aussi chaînes de texte) chaînes d’octets, 44 chaînes de texte, 4-7 alignement, 11 conversion en minuscules/majuscules, 26 conversions entre Unicode et normal, 45 expansion/compression des tabulations, 32 filtrage par rapport à un ensemble de caractères, 22-25 insensibles à la casse, 51-57 interprétation des variables, 35 inversement par mots ou par caractères, 15 modification de l’indentation, 31 simplification de l’utilisation de la méthode translate, 19 sous-chaînes, 28 substitutions, 38-41 suppression des espaces aux extrémités, 11 traitement caractère par caractère, 7 vérification de la présence d’un ensemble de caractères, 16-19 des fins, 41 du contenu, 25 characters, méthode, 322 chemins modification dynamique, 95 Index recherche de fichiers dans les répertoires, 92-96 choix de méthodes via un dictionnaire, 175 class instruction, 192 __class__, attribut, 217 classes ajout d’attributs, 202 chaînage des recherches dans des dictionnaires, 204 classiques, fonctionnalité historique, 196 création d’instances, 192 définition par héritage, 194 fonctionnalité fournie à un ensemble de classes, 195 instances, mises à jour lors de leur rechargement, 496-500 instances, modification à la volée, 217 instanciation, 193 objets, 192 recherche de toutes les méthodes, 479 restriction de l’ajout d’attributs, 202 sauvegarde/restauration avec cPickle, 260-263 Singleton, 234 sous-classes, 194 (voir aussi métaclasses) clés de dictionnaires éviter les apostrophes lors de la construction, 167 code développement, les classes classiques sont déconseillées, 244 maintenance par utilisation des classes du nouveau style, 197 programmation, 197 et bases de données, 251 réutilisation via l’héritage, 194 code, objets extraction de __init__, 262 modification dans un décorateur, 500 sérialisation, 265-267 codecs, module, affichage de caractères Unicode sur la sortie standard, 47 collections.deque héritage pour l’implémentation d’un tampon circulaire, 223 513 COM pilotage de ADO et Jet, 287 pilotage de Microsoft Word, 103 traitement de XML avec MSHTML, 336 commandes exécution répétitive, 132 planification, 133 commentaires, trace pendant le débogage, 302-304 Common Gateway Interface (voir CGI) compatibilité ascendante classes classiques dans les nouveaux codes, 244 héritage en Python, 196 compression des objets, 259 et persistance, 259 conditionnelles, désactivation pendant le débogage, 296 _const, classe, 202 const.py, module, 202 constantes, définition, 200 construction d’une instance de classe vide, 216 de classes via des métaclasses, 197 dictionnaires, 167-170 listes, 7, 152, 156 content-type, vérification avec HTTP, 389 conversions caractères en code numériques, 8 chaînes de texte, 45 en majuscules/minuscules, 26 de températures, 198 fuseaux horaires, 130 cookies Internet Explorer, 398-399 traitement pendant la récupération des pages web, 392-395 copies objets, 149-151 __copy__, méthode, 216 copy, module, 149 copy.copy, fonction, 216 copy_reg, module, extension des modules pickle/cPickle, 265 CoreGraphics, module, 100 courrier électronique, messages avec Python 2.4, 355-356 courrier électronique enregistrement sur disque, 376-377 514 mal formé, 356 boîtes aux lettres POP3, 357 cPickle, module, 253 classes et instances, 260-263 et compression, 259 sérialisation des données, 256-259 (voir aussi sérialisation) CRC (cyclic redundancy check), 108-108 création instances de classes, 192 cStringIO, module, 62, 79 cursor, objets, 279 cyclic redundancy check (CRC), 108-108 D datagrammes, sockets (UDP), 341-343 surveillance du réseau, 364-366 utilisation pour SNTP, 346 dates/heures, 111-135 additionner des durées, 121 analyse floue des dates, 128 calcul écart entre deux dates, 120 calculs hier/demain, 117 jours fériés, 125-125 nombre de jours de travail, 123 conversion entre fuseaux horaires, 130 datetime, module, 113 lancement répété de commandes, 132 module time, 111-113 module timedelta type timedelta, 117-120 obtenir l’heure à partir d’un serveur SNTP, 345 planification de commandes, 133 timedelta, module type timedelta, 122 trouver la date d’un jour d’une semaine précédente, 119 vérifier l’heure d’été, 129 datetime, module, 113, 117, 121 calcul du nombre de jours de travail, 124 dateutil, module, 121 calcul du nombre de jours de travail, 124 recherche automatique des jours fériés, 125 Index daylight saving time (DST), vérification, 129 DB, modules de l’API, style unique pour le passage des paramètres, 285-287 db_row (Python Database Row Module), 282 débogage, 295-316 désactivation des conditionnelles et des boucles, 296 fonction property, 215 lancement automatique d’un débogueur après une exception non capturée, 308-311 pile des appels, 305-308 ramasse-miettes, 299 test unitaires exécution automatique, 311 tests unitaires appartenance de valeurs à des intervalles, 315-316 exécution, 309 trace des expressions/commentaires, 302-304 traitement des exceptions, 300-302 decimal, module, 114-117, 135-141 décorateurs, 463-509 modification des objets code, 500 __deepcopy__, méthode, 218 def, instruction de définition de méthodes, 193 __delattr__, méthode, 207 délégation, 195 dans les mandataires, 209-212 souplesse, 208 (voir aussi délégation automatique) délégation automatique, 210 alternative à l’héritage, 206-209 enveloppes, 208 descripteurs, 463-509 description, attribut, 279 curseurs, 284 dict (type prédéfini), 167 fromkeys, classmethod, 177 dictionnaires ajouter des entrées, 165 associer des noms de colonnes à des valeurs d’indices, 279 chaînage des recherches, 204 choix de méthodes/fonctions, 175 clés (voir dictionnaires, clés) Index construction, 167-170 extraction de sous-ensembles, 170 inversion, 172 obtention des valeurs, 164 union et intersection, 177 (voir aussi mappings) dictionnaires, clés associer plusieurs valeurs, 173 dispatching générateurs comme co-routines, 415 division, réelle vs. entière, 26 docstrings, 314 doctest, module, 296 DOM (Document Object Model), 318 données Excel, accès à partir de Jython, 292 sauvegarde/restauration avec possibilité de sélection/recherche, 272 sérialisation module marshal, 254-256 modules pickle et cPickle, 256-259 structuration hiérarchique, 227 dtuple, module, 281 duck typing, 10 dump et dumps, fonctions module marshal, 254-256 modules pickle et cPickle, 256-259 Dynamic IP (DNS), 372-376 E E/S (entrées/sorties) fichiers à accès direct, 75 syntaxe C++, 83 EAFP (easier to ask forgiveness than permission), 10 écriture dans un fichier, 67 email, paquetage intégration de fichiers dans les messages MIME, 349 suppression des pièces jointes, 352-354 encodage détection automatique pour XML, 323-325 types, 46 Unicode pour XML/HTML, 49 entrée standard, lecture de caractères non tamponnés, 98 entrées, fichiers, retour au début, 85-88 enumerate, fonction, 155 515 __eq__, méthode, ajout à la classe Borg, 236 erreurs, gestion EAFP, approche, 10 erreurs, traitement, 295 dans l’analyse de XML, 330 via les exceptions, 300-302 espaces, suppression dans les chaînes, 12 estTexte, fonction, 26 État, motif de conception, 231-233 Excel (voir Microsoft Excel) exceptions, gestion à l’intérieur des expressions, 185 exceptions, traitement, 300-302 lancement automatique d’un débogueur après une exception non capturée, 308-311 exec, instruction, 187 expandtabs, méthode, 32 expat, parseur XML, 317 expressions gestion des exceptions, 185 trace pendant le débogage, 302-304 vs. instructions, 487 expressions génératrices, 153 expressions régulières expansion/compression des tabulations dans les chaînes, 33 substitutions de sous-chaînes, 38 extend, méthode, 15 extensions de fichiers, modification dans une arborescence, 90 extract_stack, fonction, 302 F fabriques, fonctions fermetures, 20 métaclasses, 493-496 sous-classes tuples, 211 Fahrenheit, températures, conversions, 198 FeedParser, module, 355, 356 fermetures, 21 Fibonacci, suite, 420 fichiers, 59-110 accès direct entrées/sorties, 75 modification, 76 approche C++ pour les E/S, 83 archivage dans un fichier tar compressé, 81 compter les lignes, 70-73 516 dépôt avec CGI, 386 écriture, 67 entrée, retour au début, 85-88 extensions (voir extensions de fichiers) intégration dans des messages MIME, 348-351 lecture, 63-66 d’une ligne spécifique, 69 mode binaire et mode texte, 60 modification des attributs avec Windows, 101 modification dynamique du chemin de recherche, 95 noms (voir noms de fichiers) objets, 88 OpenOffice.org, extraction de texte, 102 parcours d’arborescences, 89 PDF (voir PDF, fichiers) portabilité du code, 61-63 production de fichiers OPML, 400-403 pseudo-fichiers, 298 recherche dans les répertoires, 92-96 recherche/remplacement de texte, 68 recherches dans le chemin de recherche, 94 reprise d’un téléchargement par HTTP, 391 sauvegardes numéros de versions dans les noms de fichiers, 106 traitement des mots, 73 verrouillage avec une API portable, 104 types de verrous, 105 Word, extraire le texte, 103 zip dans une chaîne, 79 lecture, 78 filtrage liste de sites FTP, 344 filtre_rdf, fonction, 333 fins de lignes, caractères, 65 FOAF (Friend-Of-A-Friend), 400 fonction remplacements_multiples, 39 fonctions choix avec un dictionnaire, 175 polymorphisme, 229 portabilité, 63 Index prédéfinies, appel de méthodes spéciales dans un ordre spécifique, 194 formatage des chaînes, opérateur %, 7, 13 formatter.AbstractFormatter, classe, 56 Friend-Of-A-Friend (FOAF), 400 FTP, sites, filtrage, 344 G Gadfly, 253 gc, module, 299 générateurs, 413-461 générique, programmation, 194 gestion des erreurs dans l’encodage Unicode, 49-51 gestion des exceptions, 185 get, méthode, 35 et listes, 154 extraction de sous-ensembles de dictionnaires, 171 valeurs des dictionnaires, 164, 164 __getattr__, méthode, 207, 210 valeur des attributs, 200 __getitem__, méthode, 162 _getS, méthod, 215 __getstate__, méthode, 217 getURLQualifiee, fonction, 386 getvalue, méthode, 301 gzip, module et compression, 259 H handlerTexte, classe, 322 __hash__, méthode, ajout à la classe Borg, 236 Haskell, langage de programmation, 153 héritage et copie d’objets, 217 et réutilisation du code, 194 et Singletons, 234 et souplesse de la délégation, 208 inconvénients, 206 multiple, 247-251 ou délégation automatique, 206-209 pour le polymorphisme, 194 (voir aussi héritage multiple) HTML conversion en texte avec Unix, 55 encodage Unicode, 49 envoi de courrier électronique, 346-348 (voir aussi XML) Index htmlentitydefs, module, 49 HTTP surveillance des réseaux, 364-366 vérification du type de contenu, 389 HTTPS, navigation et authentification sur un proxy, 395 I IDLE (Integrated Development Environment) shell graphique pour explorer la POO en Python, 194 immutabilité, 201 __init__, méthode, 52 constructeurs des instances de classes, 193 court-circuit, 217 initialisateurs des instances de classe, 193 redéfinition, 217 , 262 appel des super-classes qui la définit, 244-247 extraction de l’objet code, 243 initialisation automatique des variables d’instance, 242-244 initialisation des variables d’instance à partir de méthodes __init__, 242-243 instances objets, 192 sauvegarde/restauration avec cPickle, 260-263 tester des changements d’état, 224-227 instantane, méthode, 226 Internet Relay Chat (IRC), 376-376 intersection, méthode, 178 intervalles, appartenance de valeurs dans les tests unitaires, 315-316 introspection codage et, 247 IRC (Internet Relay Chat), connexion, 376-377 isinstance, méthode, 9 islower, méthode, 27 isSSL, fonction, 386 istitle, méthode, 27 isupper, methode, 27 itemgetter, fonction, 214 iter, fonction, 159 __iter__, méthode, 63 517 iterable, mappings, 206 itérateurs, 413-461 iteritems, méthode, 173 itertools, module, 17, 17 construction de dictionnaires, 168 inversion de dictionnaires, 172, 172 itertools.ifilter, 17 izip, 168, 172 J JDBC (Java Database Connectivity), accès à partir d’une servlet Jython, 289-292 join, méthode, 6, 14 jointure avec une chaîne vide (’’.join), 9, 33 jouets, programmes, 251 journaux stockage des informations, 221-224 Jython extraction de données Excel, 292 servlets connexion à une base de données JDBC, 289-292 exécution, 396 K kelvin, températures, conversions, 198 KeyError, exception, 205 **kwds, syntaxe, 163 L langages de programmation, état et comportement, 192 LBYL (Look Before You Leap), vérifier les attributs d’un objet, 228 LDAP, accès aux serveurs, 378 ldap, extension, 378 ldap, module, 378 lecture à partir d’un fichier zip, 78 caractères non tamponnés sur l’entrée standard, 98 de données/texte dans un fichier, 63-66 ligne spécifique d’un fichier texte, 69 liaison des attributs des objets instances, 192 liées, méthodes, 42 linecache, module, 69 518 Linux mesure de l’utilisation mémoire, 297 listes choisir aléatoirement des éléments, 184 construction, 7, 152, 156 de lignes, suppression/modification de l’ordre des colonnes, 160 objets, 15 renvoi des éléments, 154 listes en compréhension, 28 listes en intension accès aux sous-chaînes, 28 construction d’un dictionnaire, 172 suppression/modification de l’ordre des colonnes dans une liste de lignes, 160 traduction de Haskell en Python, 153 ljournaux, centralisés, 295 ljust, méthode (objets chaînes), 11 locale, module, 139 localisation alphabets de l’Europe occidentale, 4 traitement des caractères non ASCII, 43-45 locals, fonction, 243 lock, fonction, 105 logging, module, 342 Look Before You Leap (LBYL), vérifier les attributs d’un objet, 228 lower, méthode, 26 __lshift__, 83 lstrip, méthode, 12 M Mac OS X caractères de fin de ligne, 65 compter les pages d’un fichier PDF, 100 makefile, méthode, source de texte, 4 maketrans, fonction, 19 filtrage des chaînes, 23 mandataires, 210 mandataires, délégation des méthodes spéciales, 209-212 MapChainee, classe, 205 mappings, 204 partiels transformés en complets, 205 marshal, module, 253, 266 cas d’utilisation, 272 limitations, 266 sérialisation des données, 254-256 Index mémoire économie et implémentation de tuples sous forme d’éléments nommés, 214 et tampons circulaires, 222 mesure de son utilisation avec Linux, 297 recherche des fuites, 299 transformations utilisant des fichiers, 3 mémoïsation, 29 implémentation de la méthode __deepcopy__, 218 métaclasses, 197, 463-509 méthodes appels d’autres méthodes sur la même instance, 195 cacher celles fournies par délégation, 209 choix avec un dictionnaire, 175 considérées comme des attributs, 207 de classes, recherches, 479 définition du comportement des objets instances, 193 délégation de l’opération à la même méthode dans la super-classe, 195 des objets chaînes, 6 des sous-classes, redéfinition des méthodes de la super-classe, 195 liées, 42 non liées, 42 spéciales de classes, 194 méthodes liées contenues par les objets, sérialisation, 263-265 maintenir des références sans empêcher le travail du ramasse-miettes, 218 références faibles, 220 méthodes spéciales appelées dans un ordre spécifique par les opérations/fonctions prédéfinies, 194 délégation dans les mandataires, 209-212 Microsoft Access (voir Access) Microsoft Excel analyse de XML, 328 extraction des données avec Jython, 292 Microsoft Jet, 510 utilisation via ADO, 287 Microsoft ODBC, standard, 252 Microsoft SQL Server, 253 Index Microsoft Word (voir Word) MIME (Multipurpose Internet Mail Extensions), 348 MIME, messages intégration de fichiers, 348-351 multi-volet, décomposition, 351-352 mimetools, module, 346 MimeWriter, module, 346 mise en cache automatique, 29 mixin, classe appels coopératifs aux super-classes, 247 fonctionnalité fournie à un ensemble de classes, 195 tester les changements d’état des instances, 224 MixinVerifModif, class, 226 mode binaire et mode texte des fichiers, 60 modification des fichiers à accès direct, 76 modules avantages par rapport aux objets de la POO, 192 définitions de classes, contenant une instruction d’affectation, 197 et chemin de recherche de Python, 95 vérifier qu’un nom est défini, 187 monétaires, opérations, 135-145 calculatrice en Python, 140-143 formatage des décimaux, 137-140 surveillance des taux de changes, 144 utilisation de l’arithmétique décimale, 135 vérification des sommes de contrôles des cartes bancaires, 143 moneyfmt, fonction, 137 motifs de conception, 231-231 adaptateur, 88 État, 231-233 Monostate, 238 objet nul, 239-242 orientés objet, 192 patron de méthode, 195 Singleton, 192, 233-239 Stratégie, 232 MSHTML, 336 msvcrt, module, 82, 99 Multipurpose Internet Mail Extensions (MIME), 348 MySQL, 253 accès aux bases de données, 273 stockage de BLOB, 274 519 MySQLdb, module, 273 stockage de BLOB dans MySQL, 274 N Network News Transfer Protocol (NNTP), 340, 340 __new__, méthode, 214 __new__ staticmethod, méthode, 233 NNTP (Network News Transfer Protocol), 340, 340 nobuffer, méthode, 87 nommés, attributs, accès aux éléments d’un tuple, 212-214 noms de fichiers, versions, 106 non liées, méthodes, 42 NoNewAttrs, classe, 202 nouveau style, classes, 196 Null, classe, 239 voir aussi Objet nul, motifs de conception, 239 O Objet nul, motif de conception, 239-242 objets compression, 259 contenant des méthodes liées d’autres objets, sérialisation, 263-265 copie, 149-151 copie de surface, 218 copie en profondeur, 218 copie rapide, 216 cycles de références, 299 de première classe, 192 description de leur création, 192 docstrings, 314 en Python, 192 état, 231-233 fichiers (voir fichiers, objets) itérables, 159 modifications avec le module shelve, 267-269 sérialisation des objets code, 265-267 tester les caractéristiques des chaînes, 9 vérifier les attributs, 228-231 octets ou caractères, 1 calcul d’un CRC-64 sur un flux, 108-110 extraction des chaînes, 28 écriture séquentielle dans un fichier binaire, 67 520 lecture directe dans un fichier binaire, 75 lecture séquentielle dans des fichiers binaires, 60 ODBC (Open Database Connectivity), 252 open, 59 lecture dans un fichier, 64 Open Database Connectivity (voir ODBC) Openldap, API C, 378 OpenOffice.org, fichiers, extraction de texte, 102 opérateur de formatage des chaînes (%), 7, 13, 37 opérations appel de méthodes spéciales dans un ordre spécifique, 194 modification de l’état, vérification des attributs nécessaires, 228 operator, module, 142, 214 OPML (Outline Processor Markup Language), 400 fichiers, 400-403 Oracle, 253 ordinateurs, surveillance, 360 os, module, 55, 59 parcours d’arborescences, 89 outils de conception industrielle et bases de données relationnelles, 252 Outline Processor Markup Language (OPML), 400 P paramètres, style identique pour tous les modules de l’API DB, 285-287 parser, module, 128 parseurs XML avec MSHTML, 336 patron de méthode, motif de conception, 195 PDF, fichiers, compter les pages sur Mac OS X, 100 performances ajouter des entrées à un dictionnaire, 166 inverse_dict_rapide vs. inverse_dict, 173 modification du chemin de recherche, 95 plusieurs morceaux de chaînes dans les séquences, 14 transformations utilisant des fichiers, 3 Index unions/intersections de dictionnaires, 177 persistance, 251-269 et compression, 259 pickle, module, 253 cas d’utilisation, 272 sérialisation des données, 256-259 (voir aussi sérialisation) pièces jointes, suppression, 352-354 pile des appels obtention d’information pendant le débogage, 305-308 polymorphisme avantage de la POO, 194 et fonctions Python, 229 par signature, 194 POO (programmation orientée objet), 175, 191-251 avantage du polymorphisme, 194 implémentation en Python, 191 pop, méthode extraction de sous-ensembles de dictionnaires, 171 obtention des valeurs d’un dictionnaire, 165 POP3, consultation interactive, 357-359 poplib, module, 357 portabilité du code, 61-63 portalocker.py, module, 105 ports réseaux, suivi/redirection, 367-369 PostgreSQL, stockage de BLOB, 275 précision bornée, 114 prédicats, 18 expansion des éléments d’une liste, 158 printf, fonction, 183 programmation, 251 programmation orientée objet (voir POO) property, fonction, 214 propriétés, éviter les accesseurs passe-partout, 214 proxy, fonction, 211 proxy, tunnel SSL, 369-372 pseudo-fichiers, lecture/analyse du contenu, 298 psycopg, module, 275 Py-DBAPI (Python DB Application Programming Interface), 253 pysqlite, module, 285 Python bénéfices de la simplicité, 191 Index comme une calculatrice, 140-143 conversion d’un document XML en arborescence d’objets, 325-327 distributions, ActivePython, 288 fonction printf de C, 183 fonctionnalités POO, 191 interface pour accéder aux bases de données, 253 interface pour accéder aux bases de données relationnelles alternatives, 253 outils, 463-509 raccourcis de programmation, 147-189 support de plusieurs paradigmes, 192 (voir aussi Python 2.3, Python 2.4) Python 2.3 caractère de remplissage dans l’alignement des chaînes, 11 interprétation des variables dans les chaînes, 35 utilisation du module decimal, 141 (voir aussi Python) Python 2.4 caractère de remplissage dans l’alignement des chaînes, 11 et courrier électronique, 355-356 expression génératrices, 171 interprétation des variables dans les chaînes, 35-38 module doctest, utilisé avec unittest, 312 parseur de courrier électronique, 354 (voir aussi Python) Python Database Row Module (db_row), 282 Python DB Application Programming Interface, 253 PyWin32, paquetage, 288 PyXML, paquetage, 318 Q Quixote, 381 R raccourcis en Python, 147-189 ramasse-miettes cycles, 299 débogage, 299 maintenir les références à des méthodes liées sans l’empêcher, 218 521 random, module, 184 Rankine, températures, conversions, 198 rassemblement d’éléments nommés, 179-181 re, module remplacement de plusieurs motifs dans les chaînes, 39 traitement des chaînes, 6 read, méthode, 61, 343 lecture dans un fichier, 64-66 lecture des objets sérialisés sur disque, 260 readLines, méthode, 61, 64 recherche date d’un jour d’une semaine précédente, 119 de cookie Internet Explorer, 398-399 de fichiers dans les répertoires, 92-96 de toutes les méthodes des classes, 479 de toutes les méthodes des classes, 479 des sous-séquences dans les séquences, 158 recherche/remplacement de texte dans les fichiers, 68 récupération incrémentale des données d’une base de données, 441 redéfinition des méthodes, 194 ref, classe, 220 referenceError, exception, 220 références faibles, 218 vers des méthodes liées, 220 références, cycles, 299 RegarderAvantDeSauter, classe, 245 relatifs, chemins, calcul, 96 relationnelles, bases de données, 252 approches hybrides, 253 implémentations avec interface ODBC, 253 implémentations des éditeurs principaux, 252 inadéquation à certaines applications, 252 sauvegarde/restauration des données avec possibilité de sélection/recherche, 272 systèmes logiciels trois tiers, 252 répertoires arborescences (voir arborescences de répertoires) 522 modification des extensions de fichiers, 90 calcul du chemin relatif, 96 recherche de fichiers, 92-96 replace, méthode, 53, 68 __repr__, méthode, 214 réseau, programmation, 339-379 detection des ordinateurs inactifs, 359-364 DNS dynamique, 372-376 envoi de messages par datagrammes, 341-343 surveillance avec HTTP, 364-366 resource, module, 298 rjust, méthode (objets chaînes), 11 rrule.count, méthode, 121 rstrip, méthode, 12 S s.count, méthode, 6 s.isdigit, méthode, 6 s.toupper, méthode, 6 sauvegardes numéros de versions dans les noms de fichiers, 106 SAX API (parseur XML), 317 SAX API, documents bien formés, 319 SAX, parseur, fusion d’événements textes contigus, 333-336 scalaires, 158 sched, module, planification de commandes, 133 Secure Socket Layer (SSL), 369-372 Secure Socket Layer/Transport Layer Security (SSL/TLS), 372 seek, méthode, 61 fichiers à accès direct, 77 self.qquechose, syntaxe, 193 séquences, 14 accès facilité, 281-282 imbriquées, aplatir, 158-160 parcours des éléments, 155 séquences génétiques, archivage dans une base de données relationnelle, 253 sérialisation objets code, 265-267 objets contenant des méthodes liées, 263-265 sérialisation des données module marshal, 254-256 Index modules pickle et cPickle, 256-259 sérialisation des objets codes avec marshal, 266 serveurs, LDAP, 378 servlets, codage en Jython, 396 set, méthode, 18 Set, type, 178 __setattr__, méthode, 202, 207 initialisation des attributs, 200 setdefault, méthode ajouter des entrées à un dictionnaire, 165 setdefault, méthode, ajout d’entrées dans un dictionnaire, 165 sets, module, 98, 178 finding sets/unions of dictionaries, 177 __setstate__, méthode, 217 shebang (#!), 69 shelve, module, modification d’objets, 267-269 signatures, méthodes avec la même, 194 Simple Mail Transfer Protocol (SMTP), 340 Simple Network Time Protocol (SNTP), 345 Singleton, motif de conception, 192, 233 alternative à, 237 remplacement par la classe Borg, 235-239 Singletons, 234 __slots__, méthode restriction de l’ajout d’attributs, 203 SMTP (Simple Mail Transfer Protocol), 340 SNTP (Simple Network Time Protocol), 345 socket, module, 340 somme de contrôle cyclique (voir CRC) sortie standard affichage de l’Unicode, 47 utilisation de printf, 183 sous-chaînes, accès, 28-31 sous-classes, 194 méthodes, 195 spéciales, méthodes définitions de classes, 194 split, méthode, 33 lecture dans un fichier, 64 traitement des mots dans les fichiers, 74 splitlines, méthode, 32 lecture dans un fichier, 64 SQL, 252 Index implémentations, 253 représentations XML stockées dans des bases de données, 253 SQLite, 253 stockage de BLOB, 277 sqlite.encode, insertion de BLOB dans SQLite, 277 SSL (Secure Socket Layer), tunnel à travers un proxy, 369-372 SSL/TLS (Secure Socket Layer/Transport Layer Security), 372 Stratégie, motif de conception, 232 strftime, function, 112 string, module, 6 filtrage des chaînes, 23 StringIO, module, 62, 80 strip, méthode, 12 strptime, function, 112 sub, méthode, pour les substitutions de chaînes, 38 substitute, méthode, 38 sum, fonction, 122 super, méthodes délégation à la super-classe, 195 super-classes appel de la méthode __init__, 244-247 coopération, 247-249 délégation, 195 méthodes, 195 SuperMixin, class, 249 __slots__, fonction, 214 superTuple, fonction, 214 surveillance d’ordinateurs, 360 Sybase, 253 T tableaux transposition de tableaux à deux dimensions, 162 tabulations dans les chaînes, expansion/compression, 32 tampons circulaires, 221-224 tar, fichiers archives compressés, 81 tarfile, module, 81 taux de changes, surveillance, 144 tell, méthode, 61 retour au début d’un fichier, 88 temperatures, conversions, 198 temps (voir dates/heures) termios, module, 99 523 tests, 295-316 CGI, 382-384 des caractéristiques des chaînes dans les objets, 9 traitement des exceptions, 300-302 unitaires appartenance de valeurs à des intervalles, 315-316 exécution, 309 exécution automatique, 311 texte, 1-57 analyse lexicale, 3 chaînes, 1-7 conversion de document HTML avec Unix, 55 extraction de fichiers OpenOffice.org, 102 des fichiers Word, 103 lecture dans un fichier, 63-66 recherche/remplacement dans les fichiers, 68 sources, 3 traitement, 1-7 texte brut, 4 texte normal conversion en Unicode, 45 There’s More Than One Way To Do It (TMTOWIDI), 147 time, module, 111-113 timedelta, module (datetime), 117-120, 122 timedelta, module, calcul de dates, 117 timeit, module, 73 TMTOWTDI (There’s More Than One Way To Do It), 147 traceback, module, 302 traceback.print_exc, fonction, 301 traitement des mots dans un fichier, 73 des textes internationaux avec Unicode, 43-45 du texte, 1-57 du texte non ASCII, 43-45 traitement du texte chaînes, caractère par caractère, 7 conversion de caractères en Unicode, 8 opérations de base, 2 translate, méthode, 19, 19, 19-26 filtrage des chaînes, 23 simplification de l’utilisation, 19 524 try/except, instruction, dans les expressions, 186 tty, module, 99 TupleDescriptor, classe, 281 tuples, implémentation avec des éléments nommés, 212-214 type de données numérique et décimal, 114 type prédéfini, héritage, 197 TypeError, exception levée par inspect.getargspec, 246 types, vérification, 10 U UDP (user datagram protocol), 341 Unicode, 4 affichage sur la sortie standard, 47 conversion caractères en codes numériques, 8 conversions en texte normal, 45 encodage gestion des erreurs, 49-51 pour XML/HTML, 49 traitement des textes internationaux, 43-45 uniform, fonction, 184, 184 unitaires, tests, 296 appartenance de valeurs à des intervalles, 315-316 exécution, 309 exécution automatique, 311 utilisation de doctest avec unittest et Python 2.4, 312 unittest, module utilisation avec doctest et Python 2.4, 312 Unix caractères de fin de ligne, 65 conversion de documents HTML en texte, 55 unObjet.qquechose, syntaxe, 193 upper, méthode, 26 URL récupération de documents sur le Web, 343 traitement dans les scripts CGI, 385-386 urllib, module, 340 urllib2, module, 340 urlopen, fonction, 343 user datagram protocol (UDP), 341 Index V variables de module, 200 interprétation dans les chaînes, 35 virgule (,), 15 virgule flottante, 114 arithmétique, 135 virus, 354 W weakref, module, 218, 220, 299 Web obtention d’un document à partir de son URL, 343 web, pages et traitement des cookies, 392-395 web, programmation, 381-412 web, serveurs, 381 WebWare, 381 win32api, module, 101 Windows caractères de fin de ligne, 65 modification des attributs de fichiers, 101 sortie standard, envoi de données binaires, 82 utilisation de MSHTML pour traiter XML, 336 write, méthode, 3 écriture dans les fichiers, 60 writelines, méthode, écriture dans un fichier, 67 writestr, méthode, lecture d’un fichier zip, 79 X XML accès aux données structurées sous forme lisible, 253 encodage Unicode, 49 utilisation de MSHTML, 336 XML, balises, 320 XML, documents comptage des balises, 320 conversion en arborescence d’objets Python, 325-327 extraction du texte, 321 validation, 330 XML, traitement, 317-337 analyse du XML de Microsoft Excel, 328 Index détection automatique de l’encodage, 323-325 filtrage des éléments/attributs d’un espace de noms, 331-333 gestion des erreurs, 330 suppression des nœuds ne contenant que des espaces, 327 vérifier qu’un document est bien formé, 319 XML, validation, 320 xml.sax.saxutils module, 334 XMLFilterBase, class, 334 525 XMLGenerator, classe, 333 xproperty, fonction, 215 Z Z-Object Database (ZODB), 253 zip, 168 zip, fichiers dans une chaîne, 79 lecture, 78 zipfile, module, 78 ZODB (Z-Object Database), 253 Zope, 381