.. _tut-faq: *** FAQ *** Développement et tests ====================== Comment corriger une erreur "IndentationError: unexpected indent" ? ------------------------------------------------------------------- Contrairement à Java pour lequel l'indentation n'a pas d'autre utilité que la lisibilité du code, Python fait d'une pierre deux coups et utilise d'abord l'indentation pour la structuration du langage ce qui rend le code lisible *de facto*. La contrepartie est qu'une erreur dans l'indentation provoque l'arrêt du programme. Une telle erreur est provoquée lorsque les lignes de code appartenant à un même bloc logique ne sont pas indentées avec les mêmes caractères. Dans le code ci dessous, les deux lignes du bloc :keyword:`while` ne sont pas alignées... Un exemple dans l'interpréteur Python:: >>> i = 0 >>> while i < 5: ... print(i) ... i = i+1 File "", line 3 i = i+1 ^ IndentationError: unexpected indent >>> Le même code exécuté à partir d'un fichier provoque évidemment la même erreur:: > python indent.py File "indent.py", line 4 i = i+1 ^ IndentationError: unexpected indent **Il faut s'assurer que toutes les lignes appartenant à un même bloc logique soit indentées de façon identique** Comment corriger une erreur "TabError: inconsistent use of tabs and spaces in indentation" ? -------------------------------------------------------------------------------------------- Cette erreur est plus subtile car la plupart du temps invisible avec les paramètres standard des éditeurs de texte. Considérons l'exemple ci dessous: .. image:: ../images/18-inconsistentTab0.png :scale: 100 % :align: center Ce code a l'air visuellement correct, cependant à l'exécution une erreur se produit:: > python indent.py File "indent.py", line 4 i = i+1 ^ TabError: inconsistent use of tabs and spaces in indentation Il faut modifier les paramètres de l'éditeur de façon à visualiser les espaces/tabulations. Dans Notepad++, ça se passe dans le menu :command:`View > Show Symbol > Show White Space and TAB`. Il apparait alors clairement que même si visuellement les deux lignes du bloc :keyword:`while` sont alignées, les symboles utilisés pour cet alignement ne sont pas identiques: .. image:: ../images/18-inconsistentTab1.png :scale: 100 % :align: center Le plus efficace est de paramétrer l'éditeur de texte pour qu'il remplace chaque caractère de tabulation par des espaces. Dans Notepad++, ça se passe dans le menu :command:`Settings > Preferences... > Tab Settings > Replace by space`. **Il faut s'assurer que les caractères utilisés pour l'indentation sont tous identiques** Les chaînes de caractère ======================== Comment remplacer les caractères accentués d'une chaîne de caractères par leurs équivalents non accentués ? ----------------------------------------------------------------------------------------------------------- Lors de certaines manipulations de chaînes de caractères, il peut être nécessaire de remplacer les caractères accentués ``à, é, è, ê, ...`` par leur équivalent non accentués ``a, e, e, e,...``. L'utilisation de la méthode de chaîne :meth:`~str.translate` apporte une réponse élégante à ce problème. Il suffit de lui passer une table de conversion créée avec la méthode statique :meth:`str.maketrans` qui prend ici en argument deux chaînes de caractères:: >>> before = 'àéèêîôù' >>> after = 'aeeeiou' >>> translation_table = str.maketrans(before, after) >>> "la maîtresse d'école est à l'hôpital".translate(translation_table) "la maitresse d'ecole est a l'hopital" Comment supprimer tous les signes de ponctuation d'une chaîne de caractères ? ----------------------------------------------------------------------------- Lors de certaines manipulations de chaînes de caractères, il peut être nécessaire de supprimer les signes de ponctuation : ``, ; : . etc``. On peut utiliser la méthode de chaîne :meth:`~str.translate` à laquelle on passe une table de conversion créée avec la méthode statique :meth:`str.maketrans` qui prend ici en argument un dictionnaire:: >>> translation_table = str.maketrans({',':None, ';':None, '.':None}) >>> "Demain, dès l'aube, à l'heure où blanchit la campagne, je partirai.".translate(translation_table) "Demain dès l'aube à l'heure où blanchit la campagne je partirai" La liste des signes de ponctuation est stockée dans :const:`string.punctuation`:: >>> import string >>> print(string.punctuation) !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ On peut donc construire facilement un dictionnaire de translation pour l'ensemble des signes de ponctuation:: >>> import string >>> d = {key: None for key in string.punctuation} >>> translation_table = str.maketrans(d) >>> s = "Demain, dès l'aube, à l'heure où blanchit la campagne, je partirai." >>> s.translate(translation_table) 'Demain dès laube à lheure où blanchit la campagne je partirai' Les listes ========== Comment récupérer l'indice des éléments lorsqu'on itère sur une liste ? ----------------------------------------------------------------------- Contrairement à des langages plus bas niveau, Python itère sur les éléments de la liste, pas sur les indices. C'est le plus souvent suffisant mais lorsque l'accès à l'indice s'avère nécessaire, la fonction :func:`enumerate` est utilisée:: >>> l = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144] >>> for indice, elt in enumerate(l): ... print(indice, elt) ... 0 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 10 89 11 144 .. _dictreader: Les fichiers ============ Comment lire de façon structurée un fichier csv ? ------------------------------------------------- L'utilisation de la méthode statique :func:`csv.reader` :ref:`permet de lire le fichier en stockant chaque ligne dans une liste de chaînes de caractère `. Considérons le fichier :file:`Data/RolandGarros2016.csv` contenant les résultats des derniers tours du tournoi ATP Roland Garros 2016:: >>> import csv >>> with open('RolandGarros2016.csv', 'r') as f: ... r = csv.reader(f, delimiter=';') ... l = list(r) ... >>> print(l[0]) ['Round', 'Winner', 'Loser', 'WRank', 'LRank', 'Wsets', 'Lsets'] >>> print(l[-1]) ['The Final', 'Djokovic N.', 'Murray A.', '1', '2', '3', '1'] Structurer l'information demande une étape supplémentaire. On peut alors préférer utiliser la classe :class:`~csv.DictReader` qui lit chaque ligne du fichier et la stocke dans un dictionnaire en une seule opération. Les clés du dictionnaire sont fournies par l'en tête du fichier. >>> with open('RolandGarros2016.csv', 'r') as f: ... r = csv.DictReader(f, delimiter=';') ... l = list(r) ... >>> print(l[-1]) {'Winner': 'Djokovic N.', 'LRank': '2', 'Round': 'The Final', 'Loser': 'Murray A.', 'WRank': '1', 'Lsets': '1', 'Wsets': '3'} Les dictionnaires ================= Comment créer un dictionnaire dont les valeurs sont des listes ? ---------------------------------------------------------------- Si on veut construire un dictionnaire en utilisant une liste comme valeur, se pose le problème du 1er accès. Si la clé n'existe pas, il faut créer une liste vide, si la clé existe déjà il faut ajouter une valeur à la liste existante. Considérons l'exemple suivant qui s'intéresse aux `40 villes les plus peuplées du monde `_. L'objectif est de construire un dictionnaire qui regroupe ces villes par pays : .. code-block:: python :linenos: :emphasize-lines: 10-11 # source : https://www.populationdata.net/palmares/villes/ data = [ ("Delta de la Rivière des Perles","Hong Kong"), ("Tokyo","Japon"), # données supprimées pour l'affichage... ("Shenzhen","Chine")] d1 = dict() for value, key in data: if key not in d1.keys(): d1[key] = list() d1[key].append(value) key = 'Chine' print(key, d1[key]) Les lignes surlignées gèrent le cas particulier du premier accès, `lorsque la clé n'existe pas encore `_. Ce code produit bien un dictionnaire dont les valeurs sont des listes :: Chine ['Shanghai', 'Beijing', 'Chongqing', 'Chengdu', 'Guangzhou', 'Tianjin', 'Shenzhen'] Le module :mod:`collections` apporte une réponse plus compacte avec la classe :class:`~collections.defaultdict` qui comporte une *default_factory* que l'on passe en paramètre au moment de l'instanciation. Lorsqu'on fait un appel à une clé qui n'existe pas, plutôt que de lever une exception, la *default_factory* est alors invoquée:: from collections import defaultdict d2 = defaultdict(list) for value, key in data: d2[key].append(value) La *default_factory* est une fonction qui retourne un objet. Cette fonction est passée en argument d'une autre fonction. Ceci est un (tout) petit aperçu de la programmation fonctionnelle. L'exemple ci dessus utilise la classe :class:`list` dans la *default_factory*. On peut également utiliser :class:`str`, :class:`int`, ... comme le montre l'exemple ci dessous:: >>> dds = defaultdict(str) >>> dds defaultdict(, {}) >>> dds['une_cle'] '' >>> dds['une_cle'] = 'hello' >>> dds defaultdict(, {'une_cle': 'hello'}) >>> ddi = defaultdict(int) >>> ddi['une_cle'] 0 >>> ddi['une_cle'] += 1 >>> ddi defaultdict(, {'une_cle': 1}) L'utilisation de :class:`~collections.defaultdict` doit être envisagée dès lors qu'il s'agit de manipuler des clés dont on ignore si elles existent ou non. Au moment du traitement, si la clé n'existe pas, elle est créée. Si elle existe, elle est modifiée. D'un point de vue programmation objet, :class:`~collections.defaultdict` est une sous classe de :class:`dict` et hérite donc de son comportement (le concept est détaillé :ref:`dans le chapitre sur les classes `). Concrètement, toutes les opérations sur les dictionnaires sont applicables indifféremment aux :class:`dict` et aux :class:`~collections.defaultdict`.