Démystifier les « raw strings » en Python
A force de donner des cours autour de Python, on finit par identifier les confusions les plus courantes. Parmi les utilisateurs de niveau intermédiaire à avancé, un des points les plus flous est souvent le concept de « raw string ». Pourtant, il n’y à rien de compliqué, mais on trouve un peu partout sur le Net des explications nébuleuses. C’est l’occasion de faire le point sur ce sujet en vérité très simple.
« Raw string », suggère qu’il existe un type de « chaîne brute ».
Coupons court à toute conception erronée. Il n’y a rien de tel.
Je répète.
Les « raw strings » n’existent pas.
Ce que sont les chaînes de caractères
Pour Python, dans sa petite tête de reptile automate, une chaîne est une suite de bits. Ces bits sont organisés pour former des valeurs, et chaque valeur correspond à un caractère. Cette correspondance, c’est ce qu’on appelle l’encodage. L’encodage est juste un gros tableau disant « cette valeur » vaut « ce caractère », une sorte de dictionnaire de traduction pour l’ordinateur.
Il existe beaucoup d’encodages différents, Windows utilise par exemple le CP-1552 tandis qu’Ubuntu utilise UTF-8. Si vous souhaitez faire de l’informatique de manière sérieuse, vous devez maîtriser le concept d’encodage et être capable de passer de l’un à l’autre. Comme ce n’est pas l’objectif de cet article, je vous renvoi à l’excellent article de Joel sur le sujet.
La bonne nouvelle c’est que vous pouvez comprendre la suite sans lire l’article en lien. Mais ça ne vous dispense pas de le lire plus tard.
Ce qu’il faut retenir c’est qu’une chaîne, en mémoire, est une suite de bits. Avec un encodage, il n’y a qu’une manière, et une seule, de représenter une donnée. Un saut de ligne ne se représente qu’avec une seule combinaison de bits possible. Un antislash et un ‘n’ ne se représentent qu’avec une seule suite de bits possible. Et donc « \n » et saut de ligne n’ont rien en commun en mémoire.
La donnée et sa représentation
Il y a une subtile différence entre la donnée (la valeur d’un concept) et sa représentation (comment on décrit cette valeur). Objectivement, une donnée n’existe pas, il n’y a que des représentations, mais ça ne m’aide pas beaucoup à vous expliquer alors prenons un raccourci :
- La donnée, c’est ce que stocke l’ordinateur en mémoire.
- La représentation de la donnée, c’est ce qui s’affiche.
On peut représenter la même chose de plusieurs manière. Par exemple :
- Le chiffre « 2 » peut être représenté par « 2 », « deux », ou « II ». La donnée, « le concept de 2 choses », est différent de sa représentation, c’est à dire ce qu’on écrit.
- Un dictionnaire s’écrit dans le code « dict() », ou « {} » mais vous vous imaginez bien que ce n’est pas stocké ainsi en RAM. « {} » est une représentation.
- La carte n’est pas le territoire.1
Votre code est une représentation de ce que doit faire la machine. Ce n’est pas ce que fait la machine.
Votre ordinateur vous ment
Une représentation est toujours imparfaite, et votre ordinateur vous donne des représentations pour vous donner une idée compréhensible facilement ce qui se passe. Il simplifie volontairement les choses pour vous permettre de travailler. Vous faites de même avec lui.
Quand vous écrivez :
dico = {'name': 'guido', 'language': 'python'}
Vous dites à Python « créer un dictionnaire et lui associer le nom ‘dico’ ». C’est une convention, une manière d’écrire pour lui commander une action. Ce n’est pas l’action elle même. Un dictionnaire n’est pas stocké sous forme de « {’name’: ‘guido’, ‘language’: ‘python’} » en RAM. Cette ligne de code est la représentation d’une action, une description de ce qu’il faut faire, mais ça reste du texte.
Si vous faites un « print » sur ‘dico’, l’interpréteur va vous afficher :
{'name': 'guido', 'language': 'python'}
L’interpréteur vous affiche une représentation d’un dictionnaire lisible pour vous, humain. Ce n’est pas ce qui est stocké en mémoire, mais ça vous donne une bonne idée de ce qui s’y trouve.
Ce que fait le drapeau « r », ou pourquoi on parle de « raw string »
Il n’est pas facile de taper certains caractères, tels que les sauts de ligne. Python inclut donc un mécanisme pour les écrire facilement. Ce mécanisme est automatique, et ‘r’ permet juste de le désactiver.
Vous ne créez pas de chaîne. Au mieux vous donnez à Python une instruction pour qu’il le fasse pour vous. Cette instruction est une réprésentation. Quand vous faites :
s = 'Super chaîne avec un saut de ligne\n'A aucun moment « Super chaîne avec un saut de ligne\n » n’est la chaîne en mémoire, c’est une représentation, une instruction donnée à Python pour créer une chaîne, tout comme “{}” n’est pas le dictionnaire en mémoire.
Vous dites à Python « créer un objet ’string’ qui contient les codes des lettres ‘Super chaîne avec un saut de ligne’ et le code d’un saut de ligne dans l’encodage courant.
Python ne garde pas les lettres en mémoire, mais des bits. Python ne garde pas « \n » en mémoire, mais le code pour « LF » ou « CR ».
« \n » ici est une représentation, une manière de dire à Python de créer une saut de ligne.
Si vous utilisez le drapeau (flag) « r » :
s = r'Super chaîne avec un saut de ligne\n'Ici « Super chaîne avec un saut de ligne\n » n’est pas non plus la chaîne stockée en mémoire. Tout votre code est une représentation, jamais une donnée.
Vous dites à Python « créer un objet ’string’ qui contient les codes des lettres ‘Super chaîne avec un saut de ligne’ et le code de ‘\’ et le code de ‘n’.
Vous lui demander de ne pas interpréter ‘\n’ comme la représentation d’un saut de ligne, mais comme les caractères séparément. ‘r’ dit à Python de ne pas activer la fonction qui vous permet de taper facilement des sauts de lignes et autres caractères spéciaux. Vous lui demander de ne pas interpréter l’instruction, mais de la traduire directement. Vous lui demandez de faire une traduction « brute » de la « chaîne », d’où le « raw string ».
L’interpréteur, le plus gros menteur
Il n’est pas facile d’afficher un saut de ligne dans l’interpréteur. Il a donc tendance à vous donner du « \n » pour vous indiquez où ils se trouvent :
>>> s = "Test\n" # « \n » représente un saut de ligne, "line break" est stocké en mémoire
Si on affiche, Python vous ressort « \n » !
>>> print(s) 'Test\n'
Python vous indique qu’il y a un saut de ligne à la fin de « Test ». Il n’a pas stocké en mémoire « \n », il essaye juste de vous aider visuellement.
Pareil pour les caractères spéciaux :
>>> s = raw_string("Saisie : ") # saisissons un anti-slash Saisie : \ >>> print(s) '\\'
Comme Python utilise les « \n » pour vous indiquer où sont les caractères spéciaux, il utilise aussi antislash pour vous indiquez où ils ne sont pas !
A aucun moment Python stocke « \\ », il ne garde en mémoire que les bits pour un seul « \ » mais le représente ‘\\’ pour éviter les ambigüités.
Moralité
Quand votre programme reçoit une chaîne en entrée, elle est déjà initialisée : qu’elle vienne du Web, de la ligne de commande ou d’un formulaire de fenêtre, c’est déjà une suite de bits qui correspond à un encodage. La chaîne ne change pas (c’est un immutable), n’est pas interprétée par Python, et un « \n » reste un « \n ».
Si dans le code, vous entrez des caractères spéciaux, ce sont des instructions qui disent à Python commenter créer la chaîne en mémoire. « r » dit à Python de ne pas interpréter ces notations spéciales.
Enfin, pour éviter la confusion, Python affiche les caractères spéciaux en utilisant la même représentation que vous utilisez dans le code. De manière amusante, c’est la partie qui amène justement le plus de confusion.
1 - Ne manquez jamais de citer l’Art de la guerre en formation, c’est du rep farming facile.