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

Plongée dans Python


précédentsommairesuivant

V. Chapitre 4 - Strings

« I'm telling you this 'cause you're one of my friends. My alphabet starts where your alphabet ends! » - Dr. Seuss, On Beyond Zebra!

V-A. Quelques petites choses ennuyeuses que vous devez comprendre avant de plonger

Peu de personnes y prêtent attention, mais le texte est incroyablement compliqué. Commencez avec l'alphabet. Les habitants de l’île de Bougainville ont le plus petit alphabet du monde ; leur alphabet, le Rotokas, est composé de seulement 12 lettres : A, E, G, I, K, O, P, R, S, T, U et V. À l'autre extrémité du spectre, les langues comme le chinois, le japonais et le coréen ont des milliers de caractères. L'anglais, bien sûr, a 26 lettres – 52, si vous comptez les majuscules et les minuscules séparément - plus une poignée de symboles de ponctuation : !@#$%&.

Quand vous parlez de « texte », vous pensez probablement aux « caractères et symboles sur l'écran de mon ordinateur ». Mais les ordinateurs ne travaillent pas avec des caractères et des symboles ; ils fonctionnent avec des bits et des octets. Chaque morceau de texte que vous avez vu sur un écran d'ordinateur est en fait stocké grâce à un encodage de caractères particulier. Dit grossièrement, l‘encodage de caractères fournit une correspondance entre ce que vous voyez à l'écran et ce que votre ordinateur stocke effectivement en mémoire et sur le disque. Il y a de nombreux systèmes d’encodage des caractères, certains optimisés pour une langue en particulier, comme le russe ou le chinois ou l'anglais, et d'autres qui peuvent être utilisés pour de multiples langages.

En réalité, c'est plus compliqué que cela. De nombreux caractères sont communs à plusieurs encodages, mais chaque encodage peut utiliser une séquence d'octets différente pour réellement stocker ces caractères en mémoire ou sur le disque. Ainsi, vous pouvez penser à l’encodage de caractères comme à une sorte de clef de déchiffrement. Dès que quelqu'un vous fournit une séquence d’octets - un fichier, une page web, n'importe quoi - et annonce que c'est du « texte », vous avez besoin de savoir quel encodage de caractères est utilisé pour que vous soyez en mesure de décoder ces octets en caractères. Si une mauvaise clef est fournie, ou aucune clef, vous vous retrouvez avec la tâche non enviable de craquer le code par vous-même. Il y a de fortes chances que vous n'y parveniez pas, et que le résultat soit incompréhensible.

Vous avez certainement déjà vu des pages web, avec d'étranges caractères ressemblant à des points d'interrogation là où des apostrophes devraient être. Cela signifie le plus souvent que l'auteur de la page n’a pas déclaré l'encodage de caractères correctement, que votre navigateur Internet a essayé de le deviner et que le résultat était un mixe de caractères attendus et non attendus. En anglais, c'est seulement gênant ; dans d'autres langues, le résultat peut être complètement illisible.

Il y a des encodages de caractères pour chaque langue majeure dans le monde. Comme chaque langue est différente et qu’historiquement, la mémoire et l'espace disque étaient coûteux, chaque encodage de caractères est optimisé pour une langue en particulier. J'entends par là que chaque encodage utilise les mêmes nombres (de 0 à 255) pour représenter tous les caractères de cette langue. Par exemple, vous êtes probablement familier avec l'encodage ASCII qui stocke les caractères de la langue anglaise sous la forme de nombres allant de 0 à 127. (65 étant le « A » majuscule, 97 étant le « a » minuscule, etc.). L'anglais a un alphabet très simple, il peut donc être entièrement exprimé avec moins de 128 nombres. Pour ceux d'entre vous qui savent compter en base 2, cela représente 7 des 8 bits d'un octet.

Les langues de l'Europe de l'Ouest comme le français, l'espagnol, et l'allemand ont plus de lettres que l'anglais. Ou, plus précisément, elles ont des lettres combinées avec différents signes diacritiques, comme le caractère ñ en espagnol. L’encodage de caractères le plus courant pour ces langues est le CP-1252, aussi appelé « Windows-1252 » car il est largement utilisé sur Microsoft Windows. L'encodage CP-1252 partage des caractères avec l'ASCII dans la plage 0-127, mais s'étend à la plage 128-255 pour les caractères comme le « n » avec un tilde au-dessus (241), le « u » avec deux points au-dessus (252), etc. Cependant, c’est toujours un encodage sur un octet ; le plus grand nombre possible, 255, tient toujours dans un octet.

Viennent ensuite les langues comme le chinois, le japonais, et le coréen, qui ont tellement de caractères qu'elles nécessitent un ensemble de caractères encodés sur plusieurs octets. Chaque « caractère » est représenté par un nombre codé sur deux octets, allant de 0 à 65535. Mais les différents encodages multioctets partagent toujours le même problème que les encodages à un seul octet, à savoir qu'ils utilisent chacun les mêmes nombres pour représenter différentes choses. La gamme de nombres est simplement plus large, car il y a beaucoup plus de caractères à représenter.

C'était acceptable dans un monde non connecté, où le « texte » était quelque chose que vous aviez vous-même tapé et parfois imprimé. Le « texte brut » (1) n'existait pas. Le code source était en ASCII, et tout le monde utilisait un logiciel de traitement de texte qui définissait son propre format (non-texte) et qui conservait l'information sur l’encodage de caractères ainsi que les styles employés, etc. Les personnes lisaient ces documents avec le même logiciel de traitement de texte que celui de l'auteur, ainsi tout fonctionnait plus ou moins correctement.

Pensez maintenant à la montée des réseaux mondiaux tels que la messagerie électronique et le Web. Une grande quantité de « textes bruts » s'échangeant autour de la planète, sont écrits sur un ordinateur, transmis par un deuxième ordinateur et reçus et affichés sur un troisième ordinateur. Les ordinateurs ne traitent que des chiffres, mais ces chiffres peuvent signifier différentes choses. Oh non ! Que faire ? Et bien, les systèmes devaient être conçus pour transporter l’information d'encodage avec chaque élément de «texte brut». Souvenez-vous, c'est la clef de déchiffrement qui fait correspondre les nombres lisibles par l'ordinateur en caractères lisibles par l'homme. Une clef de déchiffrement manquante signifie du texte tronqué, du charabia ou pire.

Maintenant, essayez d’imaginer vouloir stocker plusieurs morceaux de texte au même endroit, comme dans la même table de base de données contenant tous les courriels que vous avez reçus. Vous devez toujours stocker l'encodage de caractères à côté de chaque morceau de texte pour pouvoir l'afficher correctement. Vous pensez que c'est difficile ? Essayez de faire une recherche dans votre base de données de courriels, ce qui signifie que vous convertissez plusieurs encodages à la volée. Cela ne vous semble-t-il pas amusant ?

Pensez maintenant aux documents multilingues, où des caractères de plusieurs langues sont côte à côte dans le même document (indice : les programmes qui essayaient de faire cela utilisaient généralement des codes d'échappement pour changer de « mode ». Pouf, vous êtes en mode russe koi8-r, donc 241 signifie Я ; pouf, vous êtes maintenant en mode Mac Greek, donc 241 signifie ώ). Et bien sûr, vous voudrez aussi faire des recherches dans ces documents.

Maintenant, pleurez, parce que tout ce que vous pensiez savoir au sujet des chaînes de caractères est faux, et que le « texte brut» n'existe pas.

V-B. Unicode

Entrée Unicode

L'Unicode est un système conçu afin de représenter chaque caractère de chaque langue. Unicode représente chaque lettre, chaque caractère ou idéogramme, comme un nombre codé sur 4 octets. Chaque nombre représente un caractère unique utilisé dans au moins un mot de toutes les langues du monde (tous les nombres ne sont pas utilisés, mais plus de 65535 d'entre eux le sont, donc 2 octets ne suffiraient pas). Les caractères employés dans plusieurs langues ont généralement le même nombre, à moins qu'il y ait une bonne raison étymologique à ce que ce ne soit pas le cas. Quoi qu'il en soit, il y a exactement 1 nombre par caractère et exactement 1 caractère par nombre. Chaque nombre a une signification unique ; il n'y a pas de « modes » à suivre. u+0041 signifie toujours 'A', même dans une langue qui ne possède pas de 'A'.

À priori, cela semble être une bonne idée. Un encodage pour les diriger tous. Plusieurs langues dans un même document. Plus de « commutation de mode » pour basculer entre les encodages en cours de flux. Immédiatement, une question évidente devrait vous sauter aux yeux. Quatre octets ? Cela semble terriblement inutile, en particulier pour les langues telles que l'anglais ou l'espagnol, qui ont besoin de moins d'un octet (256 nombres) pour exprimer chaque caractère possible. En fait, c'est même du gaspillage pour les langues basées sur des idéogrammes (comme le chinois), dont les besoins n'excèdent jamais plus de deux octets par caractère.

Il existe un encodage Unicode qui utilise quatre octets par caractère. Il s'appelle UTF-32, car 32 bits = 4 octets. UTF-32 est un encodage simple ; il prend chaque caractère Unicode (un nombre de 4 octets) et représente ce caractère avec ce même nombre. Cela présente certains avantages, le plus important étant que vous pouvez trouver le Nième caractère d'une chaîne en un temps constant, car le Nième caractère commence à l'octet 4xN. Il présente également plusieurs inconvénients, le plus évident étant qu'il prend quatre octets pour chaque caractère.

Bien qu'il y ait beaucoup de caractères Unicode, il s'avère que la plupart des gens n'utiliseront jamais plus que les 65535 premiers. Il existe donc un autre codage Unicode appelé UTF-16, car 16 bits = 2 octets. UTF-16 encode chaque caractère de 0 à 65535 en 2 octets, puis fait appel à des astuces si vous avez réellement besoin de représenter les caractères Unicode rarement utilisés du « plan astral(2) », au-delà de 65535. L'avantage le plus évident : UTF-16 est deux fois plus efficace en termes de matière d'espace utilisé que UTF-32, car chaque caractère nécessite seulement deux octets au lieu des quatre requis par UTF-32 (à l'exception de ceux qui n'en ont pas besoin). Et vous pouvez toujours facilement retrouver le Nième caractère d'une chaîne en un temps constant, si vous présumez que la chaîne n'inclut aucun caractère du plan astral, ce qui est une bonne hypothèse tant que ce moment ne se présente pas.

Mais il existe également des inconvénients non évidents à la fois pour UTF-32 et UTF-16. Les octets sont stockés de différentes manières par les différents systèmes informatiques. Cela signifie que le caractère U+4E2D peut être stocké en UTF-16 comme soit 4E 2D, soit 2D 4E, selon que le système est big-endian(3) ou little-endian(4). (Pour UTF-32, il y a encore plus de possibilités pour ordonner les octets). Tant que vos documents ne quittent jamais votre ordinateur, vous êtes tranquille – les différents programmes d'un même ordinateur utiliseront tous le même ordre d'octets. Cependant, à la minute où vous souhaitez transférer des documents d'un système à un autre, peut-être sur Internet, vous aurez besoin d'un moyen pour indiquer dans quel ordre vos octets sont stockés. Autrement, le système recevant ces données n'a aucun moyen de savoir si la séquence de deux octets 4E 2D signifie U+4E2D ou U+2D4E.

Pour résoudre ce problème, les encodages Unicode multioctets définissent une « marque d'ordre d'octets » qui est un caractère spécial non imprimable que vous pouvez inclure au début de votre document pour indiquer l'ordre de vos octets. Pour UTF-16, la marque d'ordre d'octets est U+FEFF. Si vous recevez un document encodé en UTF-16 commençant par l'octet FF FE, vous savez que l'ordre est dans un sens ; s'il commence par FE FF, vous savez que l'ordre est inversé.

Cependant, UTF-16 n'est pas tout à fait idéal, surtout si vous utilisez beaucoup de caractères ASCII. Si vous y réfléchissez, même une page web en chinois contiendra de nombreux caractères ASCII : tous les éléments et attributs entourant les caractères chinois imprimables. Il est bien de pouvoir trouver le Nième caractère en un temps constant, mais il y toujours ce problème récurrent des caractères du plan astral, ce qui signifie que vous ne pouvez pas garantir que chaque caractère a exactement deux octets, vous ne pouvez donc pas réellement déterminer le Nième caractère en un temps constant, sauf si vous maintenez un index séparé. Et bon sang, il y a énormément de texte en ASCII dans le monde…

D'autres personnes ont réfléchi à ces questions et ont proposé une solution :

UTF-8

UTF-8 est un système d’encodage à longueur variable pour Unicode. Autrement dit, différents caractères utilisent un nombre d'octets différent. Pour les caractères ASCII (de A à Z, etc.), UTF-8 utilise un seul octet par caractère. En fait, il utilise exactement les mêmes octets ; en UTF-8, les 128 premiers caractères (de 0 à 127) sont impossibles à distinguer de l'ASCII. Les caractères « latin étendu » comme ñ et ö prennent deux octets. (Les octets ne sont pas simplement des points de code Unicode tels qu’ils le seraient en UTF-16 ; cela implique quelques sérieux bricolages). Les caractères chinois comme prennent trois octets. Les caractères rarement utilisés du « plan astral » prennent quatre octets.

Inconvénients : comme chaque caractère peut prendre un nombre d'octets différent, trouver le Nième caractère est une opération O(N), c'est-à-dire que plus la chaîne de caractères est longue, plus la recherche d'un caractère spécifique prend du temps. En outre, encoder des caractères en octets et inversement implique un peu de bricolage.

Avantages : cet encodage est super efficace pour les caractères ASCII courants. Moins mauvais que UTF-16 pour les caractères latins étendus. Mieux que UTF-32 pour les caractères chinois. Aussi (et vous devrez me faire confiance à ce sujet, parce que je ne vais pas vous le démontrer par calcul), en raison de la nature du bricolage sur les octets, il n'y a pas de problème d'ordre des octets. Un document encodé en UTF-8 utilise exactement le même flux d'octets sur n'importe quel ordinateur.

V-C. Plongeons

En Python 3, toutes les chaînes sont des séquences de caractères Unicode. Il n'existe pas de chaîne Python encodée en UTF-8, ni de chaîne Python encodée en CP-1252. « Cette chaîne est-elle codée en UTF-8 ? » est une question invalide en Python 3. UTF-8 est une manière d’encoder des caractères en une séquence d'octets. Si vous souhaitez transformer une chaîne en séquence d'octets dans un encodage de caractères particulier, Python 3 peut vous aider. Si vous souhaitez convertir une séquence d'octets en chaîne, Python 3 peut également vous aider. Les octets ne sont pas des caractères ; les octets sont des octets. Les caractères sont une abstraction. Une chaîne est une séquence de ces abstractions.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
>>> s = '深入 Python'     1
>>> len(s)               2
9
>>> s[0]                 3
'深'
>>> s + ' 3'             4
'深入 Python 3'
  1. Pour créer une chaîne, encadrez-la de guillemets. Les chaînes de caractères Python peuvent être définies soit entre des guillemets simples (') soit entre des guillemets doubles ("). La fonction interne len() renvoie la longueur d'une chaîne de caractères, c'est-à-dire le nombre de caractères. C'est la même fonction que celle que vous utilisez afin de déterminer la longueur d'une liste, d'un tuple, d'un set ou d'un dictionnaire. Une chaîne de caractères est comme un tuple de caractères. Tout comme la récupération d'un élément d'une liste, vous pouvez récupérer un caractère d'une chaîne en utilisant son index.Comme pour les listes, vous pouvez concaténer des chaînes de caractères en utilisant l'opérateur +.

V-D. Formater les chaînes de caractères

Regardons à nouveau le script humansize.py :

humansize.py
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],         1
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convertit la taille d'un fichier dans une forme lisible par humain.  2

    Arguments:
    size -- La taille du fichier en octets
    a_kilobyte_is_1024_bytes -- Si vrai (True) (par défaut), utilise un multiple
                                de 1024. Si faux (False), utilise un multiple de
                                1000.

    Returns: string
    '''                                                                     3
    if size < 0:
        raise ValueError('La taille doit être un nombre positif')           4

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)                       5

    raise ValueError('Nombre trop grand')
  1. ‘KB’, ‘MB’, ‘GB’… sont des chaînes de caractères ;
  2. Les docstrings de la fonction sont des chaînes de caractères. Cette docstring(5) s'étend sur plusieurs lignes, donc elle utilise trois guillemets à la suite pour commencer et terminer la chaîne de caractères ;
  3. Ces trois guillemets à la suite clôturent la docstring ;
  4. Ceci est une autre chaîne de caractères qui est passée à l'erreur d’exception afin d'avoir un message d'erreur humainement lisible ;
  5. C'est un… waouh, qu'est-ce que c'est que ça ?

Python 3 prend en charge le formatage des valeurs en chaînes. Bien que cela puisse inclure des expressions très compliquées, l'utilisation la plus élémentaire consiste à insérer une valeur dans une chaîne avec un seul espace réservé.

 
Sélectionnez
1.
2.
3.
4.
>>> username = 'mark'
>>> password = 'PapayaWhip'                             1
>>> "{0}'s password is {1}".format(username, password)  2
"mark's password is PapayaWhip"
  1. Non, mon mot de passe n'est pas réellement PapayaWhip(6) ;
  2. Beaucoup de choses se passent ici. Premièrement, ceci est un appel à une méthode sur une chaîne de caractères littérale. Les chaînes de caractères sont des objets, et les objets ont des méthodes. Deuxièmement, l'expression entière est évaluée comme une chaîne de caractères. Troisièmement, {0} et {1} sont des champs de remplacement qui sont remplacés par les arguments passés à la méthode format().

V-D-1. Les champs de noms composés

L'exemple précédent montre le cas le plus simple où les champs de remplacement sont simplement des entiers. Les champs de remplacement d'entiers sont traités comme des index dans la liste d'arguments de la méthode format(). Cela signifie que {0} est remplacé par le premier argument (username dans ce cas), que {1} est remplacé par le deuxième argument (password), etc. Vous pouvez avoir autant d’index de position que d'arguments et autant d'arguments que vous le souhaitez. Mais les champs de remplacement sont beaucoup plus puissants que cela.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]      1
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes)  2
'1000KB = 1MB'
  1. Plutôt que d'appeler l'une des fonctions du module humanize, récupérons uniquement l'une de ses structures de données : la liste des suffixes « SI » (puissance de mille).
  2. Cela semble compliqué, mais cela ne l'est pas. {0} ferait référence au premier argument de si_suffixes passé à la méthode format(). Mais si_suffixes est une liste. Donc, {0[0]} fait référence au premier item de la liste qui est le premier argument passé à la méthode format(): ‘KB’. De même, {0[1]} fait référence au second élément de la même liste : ‘MB’. Tout ce qui se trouve en dehors des accolades - incluant 1000, le signe égal ainsi que les espaces - est intact. Le résultat final est la chaîne de caractères ‘1000KB = 1MB’.

    Cet exemple montre que les spécificateurs de format peuvent accéder aux éléments et propriétés des structures de données en utilisant (presque) la syntaxe Python. Ceci est appelé champs de noms composés. Les champs de noms composés suivants fonctionnent « tels quels » :

  3. passer une liste et accéder à un élément de la liste par son index (comme dans l'exemple précédent) ;

  4. passer un dictionnaire et accéder à une valeur du dictionnaire par une clef ;

  5. passer un module et accéder à ses variables et fonctions par leurs noms ;

  6. passer une instance de classe et accéder à ses propriétés et méthodes par leurs noms ;

  7. toute combinaison des éléments ci-dessus.

Juste pour vous en mettre plein la vue, voici un exemple qui combine tous les points qui précèdent :

 
Sélectionnez
1.
2.
3.
4.
>>> import humansize
>>> import sys
>>> '1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)
'1MB = 1000KB'

Voici comment cela fonctionne :

  • le module sys contient des informations à propos de l'instance Python en cours. Comme vous venez juste de l'importer, vous pouvez passer le module sys lui-même en argument de la méthode format(). Ainsi le champ de remplacement {0} se réfère au module sys ;
  • sys.modules est un dictionnaire de tous les modules qui ont été importés par cette instance Python. Les clefs sont les noms des modules en tant que chaîne de caractères ; les valeurs sont les objets des modules eux-mêmes. Donc le champ de remplacement {0.modules} fait référence au dictionnaire des modules importés ;
  • sys.modules['humansize'] est le module humansize que vous venez tout juste d'importer. Le champ de remplacement {0.modules[humansize]} fait référence au module humansize. Notez ici la petite différence de syntaxe. En Python, les clefs du dictionnaire sys.modules sont des chaînes de caractères. Pour s'y référer, vous devez mettre des guillemets autour du nom du module (ex : 'humansize'). Mais dans un champ de remplacement, vous sautez les guillemets autour du nom de la clef du dictionnaire (ex : humansize). Pour citer la PEP 3101: Advanced String Formatting : « Les règles pour analyser une clef d'un item sont vraiment simples. Si elle débute avec un chiffre, alors elle est traitée comme un nombre, sinon elle est utilisée comme une chaîne de caractères. » ;
  • sys.modules['humansize'].SUFFIXES est le dictionnaire défini en début du module humansize. Le champ de remplacement {0.modules[humansize].SUFFIXES}} fait référence au dictionnaire ;
  • sys.modules['humansize'].SUFFIXES[1000] est la liste des suffixes SI: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']. Donc le champ de remplacement {0.modules[humansize].SUFFIXES[1000]} fait référence à la liste ;
  • sys.modules['humansize'].SUFFIXES[1000][0] est le premier élément de la liste des suffixes SI : ‘KB’. Ainsi, le champ de remplacement complet {0.modules[humansize].SUFFIXES[1000][0]} est remplacé par la chaîne de deux caractères KB.

V-D-2. Les spécificateurs de format

Mais attendez ! il y a plus ! regardons à nouveau cette étrange ligne de code du script humansize.py :

 
Sélectionnez
1.
2.
if size < multiple:
    return '{0:.1f} {1}'.format(size, suffix)

{1} est remplacé par le deuxième argument transmis à la méthode format(), qui est suffix. Mais qu’est-ce que {0:.1f} ? Ce sont deux choses : {0}, que vous reconnaissez, et :.1f, que vous ne connaissez pas. La seconde moitié (les deux points y compris) définit le spécificateur de format, qui précise le formatage de la variable remplacée.

 
Sélectionnez
1.
> Les spécificateurs de format vous permettent de transformer le texte de remplacement de différentes manières, à l'image de la fonction `printf()` en C. Vous pouvez ajouter des zéros ou des espaces, aligner des chaînes de caractères, contrôler la précision décimale et même convertir des nombres en hexadécimal.

Dans un champ de remplacement, les deux points (:) marquent le début du spécificateur de format. Le spécificateur de format ".1" signifie « arrondir au dixième près » (c'est-à-dire n'afficher qu'un chiffre après la décimale). Le spécificateur de format "f" signifie « nombre à virgule fixe(7) » (par opposition à la notation exponentielle ou toute autre représentation décimale). Ainsi, si la valeur de size était 698.24 et que celle de suffix était 'GB', alors, la chaîne formatée serait '698,2 GB', car '698,24' est arrondi à une décimale, puis le suffixe est ajouté après le nombre.

 
Sélectionnez
1.
2.
>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'

Pour tous les détails croustillants sur les spécificateurs de format, consultez le chapitre « minilangage de spécification de format » de la documentation officielle de Python.

V-E. D'autres méthodes communes des chaînes de caractères

Outre le formatage, les chaînes de caractères peuvent faire beaucoup d'autres choses utiles.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
>>> s = '''Finished files are the re-  1
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
>>> s.splitlines()                     2
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']
>>> print(s.lower())                   3
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
>>> s.lower().count('f')               4
6
  1. Dans le shell interactif Python, vous pouvez entrer des chaînes de caractères sur plusieurs lignes. Lorsque vous commencez une chaîne de caractères sur plusieurs lignes avec trois guillemets, pressez la touche ENTRÉE et le shell interactif vous invitera à poursuivre la chaîne. Tapez les trois guillemets fermants pour marquer la fin de la chaîne et au prochain ENTRÉE, la commande sera exécutée (dans le cas présent l'assignation de la chaîne de caractères à la variable s).
  2. La méthode splitlines() prend une chaîne de caractères multilignes et renvoie une liste de chaînes, une pour chaque ligne de l'originale. Notez que le retour chariot n'est pas inclus.
  3. La méthode lower() convertit la totalité de la chaîne de caractères en minuscules (de la même façon, la méthode upper() convertit la chaîne en majuscules).
  4. La méthode count() compte le nombre d'occurrences d'une sous-chaîne de caractères. Oui, il y a réellement six « f » dans cette phrase !

Voici un autre cas classique. Disons que vous avez une liste de paires de clefs-valeurs de la forme key1=value1&key2=value2 et que vous souhaitez les séparer pour en faire un dictionnaire de la forme {key1: value1, key2: value2}.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
>>> query = 'user=pilgrim&database=master&password=PapayaWhip'
>>> a_list = query.split('&')                                        1
>>> a_list
['user=pilgrim', 'database=master', 'password=PapayaWhip']
>>> a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v]  2
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
>>> a_dict = dict(a_list_of_lists)                                   3
>>> a_dict
{'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
  1. La méthode de chaîne split() requiert un argument, un séparateur. Cette méthode divise une chaîne de caractères en une liste de chaînes en se basant sur le séparateur. Ici, le séparateur est le caractère esperluette(8), mais cela pourrait être n'importe quel autre caractère.
  2. Maintenant, nous avons une liste de chaînes de caractères, chacune avec une clef suivie par un signe égal, puis une valeur. Nous pouvons utiliser une liste en compréhension pour parcourir toute la liste et diviser chaque chaîne en deux chaînes en utilisant comme séparateur le premier signe égal. Le second argument, optionnel, de la méthode split() est le nombre de fois que vous souhaitez diviser. 1 signifie « diviser une seule fois », ainsi la méthode split() renverra une liste de deux éléments. (En théorie, une valeur pourrait aussi contenir un signe égal ; si vous faites 'key=value=foo'.split('='), vous obtiendrez une liste de trois éléments ['key', 'value', 'foo']).
  3. Enfin, Python est en mesure de transformer cette liste de listes en un dictionnaire simplement en la passant en paramètre de la fonction dict().

L'exemple précédent ressemble beaucoup à l'analyse des paramètres d'une URL, mais en réalité, analyser une URL est bien plus compliqué. Si vous utilisez des paramètres de requête d'URL, il est préférable d'utiliser la fonction urllib.parse.parse_qs(), qui gère certains cas d'utilisation non évidents.

V-E-1. Découper des chaînes de caractères

Une fois que vous avez défini une chaîne, vous pouvez récupérer n'importe quelle partie en tant que nouvelle chaîne. On appelle cela le découpage en tranches de chaînes. Le découpage de chaînes de caractères fonctionne exactement comme le découpage de listesChapitre 2 – Les types de données natifs, ce qui a du sens, car les chaînes sont juste des séquences de caractères.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
>>> a_string = 'My alphabet starts where your alphabet ends.'
>>> a_string[3:11]           1
'alphabet'
>>> a_string[3:-3]           2
'alphabet starts where your alphabet en'
>>> a_string[0:2]            3
'My'
>>> a_string[:18]            4
'My alphabet starts'
>>> a_string[18:]            5
' where your alphabet ends.'
  1. Vous pouvez récupérer une partie de la chaîne, appelée une « tranche », en fournissant deux index. La valeur renvoyée est une nouvelle chaîne de caractères contenant tous les caractères de la chaîne, dans l'ordre, à partir du premier index.
  2. Comme pour le découpage des listes, vous pouvez utiliser des index négatifs pour découper les chaînes.
  3. Les chaînes de caractères commencent par l’index 0, ainsi a_string[0:2] renvoie les deux premiers éléments de la chaîne, commençant par a_string[0] jusqu'à a_string[2]mais sans l’inclure.
  4. Si l’index de gauche est 0, vous pouvez ne pas l'indiquer, car 0 est implicite. Ainsi, a_string[:18] est équivalent à a_string[0:18].
  5. De la même façon, si l’index de droite est de la longueur de la chaîne de caractères, vous pouvez ne pas l'indiquer. Ainsi, a_string[18:] est identique à a_string[18:44], car la chaîne de caractères est de 44 caractères. Il y a une symétrie plaisante ici. Dans cette chaîne de 44 caractères, a_string[:18] renvoie les 18 premiers caractères et a_string[18:] renvoie tous les caractères excepté les 18 premiers. En fait, a_string[:n] renverra toujours les n premiers caractères, et a_string[n:] renverra le reste, quelque soit la longueur de la chaîne.

V-F. Chaînes de caractères vs. octets

Les octets sont des octets ; les caractères sont une abstraction. Une séquence immuable de caractères Unicode est appelée une chaîne de caractères. Une séquence immuable de nombres entre 0 et 255 est appelée un objet bytes(9).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
>>> by = b'abcd\x65'  1
>>> by
b'abcde'
>>> type(by)          2
<class 'bytes'>
>>> len(by)           3
5
>>> by += b'\xff'     4
>>> by
b'abcde\xff'
>>> len(by)           5
6
>>> by[0]             6
97
>>> by[0] = 102       7
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
  1. Pour définir un objet bytes, utilisez la syntaxe b''10N.D.T. : byte literal en anglais . Chaque octet à l'intérieur de la syntaxe octet littéral peut être un caractère ASCII ou un nombre encodé en hexadécimal de \x00 à \xff (0-255).
  2. Le type d'un objet bytes est bytes.
  3. Comme pour les listes et les chaînes de caractères, vous pouvez déterminer la longueur de l'objet bytes en utilisant la fonction len().
  4. Comme pour les listes et les chaînes de caractères, vous pouvez utiliser l'opérateur pour concaténer des objets bytes. Le résultat est un nouvel objet bytes.
  5. Concaténer un objet bytes de 5 octets et un objet bytes d’un octet donne un objet bytes de 6 octets.
  6. Comme pour les listes et les chaînes de caractères, vous pouvez récupérer un octet d'un objet bytes par son index. Les éléments d'une chaîne de caractères sont des chaînes de caractères ; les éléments d'un objet bytes sont des entiers. Plus précisément, les entiers compris entre 0 et 255.
  7. Un objet bytes est immuable ; vous ne pouvez pas attribuer des octets individuels. Si vous avez besoin de changer des octets individuels, vous pouvez soit utiliser le découpage des chaînes de caractèresDécouper des chaînes de caractères et les opérateurs de concaténation (qui fonctionnent de la même façon que pour les chaînes de caractères), soit convertir les objets bytes en un objet bytearray.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
>>> by = b'abcd\x65'
>>> barr = bytearray(by)  1
>>> barr
bytearray(b'abcde')
>>> len(barr)             2
5
>>> barr[0] = 102         3
>>> barr
bytearray(b'fbcde')
  1. Afin de convertir un objet bytes en un objet bytearray muable, utilisez la fonction intégrée bytearray().
  2. Toutes les méthodes et opérations que vous pouvez faire sur les bytes peuvent également être faites sur les objets bytearray.
  3. La seule différence est qu'avec l’objet bytearray, vous pouvez modifier les octets indépendamment en utilisant son index. La valeur assignée doit être comprise entre 0 et 255.

La seule chose que vous ne pouvez jamais faire est de mélanger les octets et les chaînes de caractères.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
>>> by = b'd'
>>> s = 'abcde'
>>> by + s                       1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
>>> s.count(by)                  2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> s.count(by.decode('ascii'))  3
  1. Vous ne pouvez pas concaténer des octets et des chaînes de caractères. Ce sont deux types de données différents.
  2. Vous ne pouvez pas compter les occurrences des octets dans une chaîne de caractères, car il n'y a pas d'octets dans une chaîne. Une chaîne de caractères est une séquence de caractères. Peut-être voulez-vous « compter les occurrences de la chaîne que vous obtiendriez après le décodage de cette séquence d'octets dans un encodage de caractères particulier »? Eh bien, vous allez devoir le dire explicitement. Python 3 ne va pas implicitement convertir des octets en chaînes de caractères ou des chaînes en octets.
  3. Par une étrange coïncidence, cette ligne de code dit « compter les occurrences de la chaîne que vous obtiendriez après le décodage de cette séquence d'octets dans un encodage de caractères particulier ».

Voici le lien entre les chaînes de caractères et les octets : les objets bytes ont une méthode decode() qui prend un encodage de caractères et renvoie une chaîne de caractères, et les chaînes de caractères ont une méthode encode() qui prend un encodage de caractères et renvoie un objet bytes. Dans l'exemple précédent, le décodage était relativement simple - convertir une séquence d'octets encodée en ASCII en une chaîne de caractères. Mais ce même procédé fonctionne avec n'importe quel encodage qui supporte les chaînes de caractères - même les anciens encodages (non Unicode).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
>>> a_string = '深入 Python'         1
>>> len(a_string)
9
>>> by = a_string.encode('utf-8')    2
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
>>> by = a_string.encode('gb18030')  3
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
>>> by = a_string.encode('big5')     4
>>> by
b'\xb2`\xa4J Python'
>>> len(by)
11
>>> roundtrip = by.decode('big5')    5
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
  1. Ceci est une chaîne de caractères. Elle a neuf caractères.
  2. Ceci est un objet bytes. Il est de 13 octets. C'est une séquence d'octets que vous obtenez lorsque vous prenez la chaîne a_string et que vous l'encodez en UTF-8.
  3. Ceci est un objet bytes. Il est de 11 octets. C'est une séquence d'octets que vous obtenez lorsque vous prenez a_string et que vous l'encodez en GB18030.
  4. Ceci est un objet bytes. Il est de 11 octets. C'est une séquence complètement différente d'octets que vous obtenez lorsque vous prenez a_string et que vous l'encodez en Big5.
  5. C'est une chaîne de caractères. Elle a neuf caractères. C'est une séquence de caractères que vous obtenez lorsque vous prenez la chaîne by et que vous la décodez en utilisant l’algorithme d'encodage Big5. Elle est identique à la chaîne originale.

V-G. Postscriptum : encodage de caractères du code source de Python

Python 3 suppose que votre code source - c'est-à-dire chaque fichier .py - est encodé en UTF-8.

En Python 2, l'encodage par défaut pour les fichiers .py était l’ASCII. En Python 3, l'encodage par défaut est l'UTF-8.

Si vous souhaitez utiliser un encodage différent pour votre code Python, vous pouvez déclarer cet encodage sur la première ligne de chaque fichier. Cette déclaration spécifie qu’un fichier .py est encodé en windows-1252 :

 
Sélectionnez
1.
# -*- coding: windows-1252 -*-

Techniquement, forcer l'encodage de caractères peut également se faire sur la seconde ligne, si la première est une commande shebang de type Unix.

 
Sélectionnez
1.
2.
#!/usr/bin/python3
# -*- coding: windows-1252 -*-

Pour plus d'informations, consultez la PEP 263 : Defining Python Source Code Encodings

V-H. Lectures complémentaires

À propos d'Unicode en Python :

À propos d'Unicode en général :

À propos de l'encodage de caractères dans d'autres formats :

À propos des chaînes et du formatage des chaînes de caractères :


précédentsommairesuivant
N.D.T.Note de la traduction : plain texte en anglais dans le document original
N.D.T.Note de la traduction : dans le standard Unicode, un plan est un groupe continu de 65536 nombres servant à encoder
Gros-boutiste : appelé aussi «gros boutien», signifie que l’octet le plus fort est enregistré ou transmis en premier et l’octet de poids le plus faible, en suivant.
Petit-boutiste : appelé aussi «petit boutien», signifie que l’octet le plus faible est enregistré ou transmis en premier et l’octet de poids le plus fort, en suivant.
N.D.T. : une docstring est littéralement une chaîne de caractères littérale de documentation.
N.D.T. : littéralement fouet de papaye, nom d'une couleur dont le code hexadécimal est #FFEFD5
(N.D.T : float en anglais)
Le caractère esperluette est le symbole &
N.D.T. : bytes signifie octets

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Mark Pilgrim. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.