Le formatage des strings en long et en large 19


Un bon article bien long. Je sens que ça vous avait manqué :) Musique ?

Un problème qui se retrouve souvent, c’est le besoin d’afficher un message qui contient des valeurs de variables. Or, si en Python on privilégie généralement “il y a une seule manière de faire quelque chose”, cela ne s’applique malheureusement pas au formatage de chaînes qui a accumulé bien des outils au fil des années.

TL;DR

Si c’est juste pour afficher 2, 3 bricoles dans le terminal, utilisez print() directement:

>>> print("J'ai", 3, "ans")
J'ai 3 ans
>>> print(3, 2, 1, sep='-')
3-2-1

Si vous avez besoin d’un formatage plus complexe ou que le texte n’est pas que pour afficher dans le terminal…

Python 3.6+, utilisez les f-strings:

>>> produit = "nipple clamps"
>>> prix = 13
>>> print(f"Les {produit} coûtent {prix:.2f} euros")
Les nipple clamps coûtent 13.00 euros

Sinon utilisez format():

>>> produit = "nipple clamps"
>>> prix = 13
>>> print("Les {} coûtent {:.2f} euros".format(produit, prix))
Les nipple clamps coûtent 13.00 euros

Si vous êtes dans le shell, que vous voulez aller vite, ou que vous manipulez des bytes, vous pouvez utiliser “%”, mais si ça ne vous arrive jamais, personne ne vous en voudra:

>>> produit = "nipple clamps"
>>> prix = 13
>>> print("Les %s coûtent %.2f euros" % (produit, prix))
Les nipple clamps coûtent 13.00 euros

N’utilisez jamais string.Template.

Si vous avez un gros morceau de texte ou besoin de logique avancée, utilisez un moteur de template comme jinja2 ou mako. Pour l’i18n et la l10n, choisissez une lib comme babel.

Avec print()

Par exemple, si j’ai :

produit = "nipple clamps"
prix = 13

Et je veux afficher :

"Les nipple clamps coûtent 13 euros"

La manière la plus simple de faire cela est d’utiliser print():

>>> print("Les", produit, "coûtent", prix, "euros")
Les nipple clamps coûtent 13 euros

Mais déjà un problème se pose : cette fonction insère des espaces entre chaque argument qu’elle affiche. Cela est ennuyeux si par exemple je veux utiliser le signe et le coller pour obtenir :

Les nipple clamps coûtent 13

print() possède un paramètre spécial pour cela : sep. Il contient le séparateur, c’est à dire le caractère qui va être utilisé pour séparer les différents arguments affichés. Par défaut, sep est égal à un espace.

Si je change ma phrase et que j’ai besoin d’espaces à certains endroits et pas à d’autres, il me faut définir un séparateur – ici une chaîne vide – et jouer un peu avec le texte :

>>> print("Les ", produit, " coûtent ", prix, "€", sep="")
Les nipple clamps coûtent 13

C’est mieux. Mais, ça commence à devenir moins lisible.

Maintenant que se passe-t-il si je veux utiliser une valeur numérique mais que j’ai besoin de la formater ?

Par exemple :

produit = "nipple clamps"
prix = 13
exo_taxe = 0.011

Et je veux tronquer le prix au centime de telle sorte que j’obtienne :

Les nipple clamps coûtent 13.01

Arf, ça va demander un peu plus de travail.

>>> total = round(prix + exo_taxe, 2)
>>> print("Les ", produit, " coûtent ", total, "€", sep="")

Bon, mais admettons que je veuille sauvegarder ce texte dans une variable ? Par exemple pour le passer à une fonction qui vérifie l’orthographe ou met la phrase en jaune fluo…

Dans ce cas ça devient burlesque, il faut intercepter stdout et récupérer le résultat :

>>> faux_terminal = io.StringIO()
>>> print("Les ", produit, " coûtent ", total, "€", sep="", file=faux_terminal)
>>> faux_terminal.seek(0)
>>> msg = faux_terminal.read()
>>> print(msg)
Les nipple clamps coûtent 13.01

Vous l’avez compris, print() est fantastique pour les cas simples, mais devient rapidement peu pratique pour les cas complexes : son rôle est d’être bon à afficher, pas à formater.

Avec +

A ce stade, un débutant va généralement taper “concaténation string python” sur son moteur de recherche et tomber sur l’opérateur +. Il essaye alors ça :

>>> "Les " + produit + " coûtent " + total + "€"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-126b156e23bd> in <module>()
----> 1 "Les " + produit + " coûtent " + total + "€"
 
TypeError: Can't convert 'float' object to str implicitly

Et il apprend par la même occasion que Python est fortement typé. On ne peut pas additionner des choux et des carottes disait ma prof de CE1, et donc on ne peut pas additionner "coûtent" (type str) et total (type float).

Il faut donc convertir total :

>>> "Les " + produit + " coûtent " + str(total) + "€"
'Les nipple clamps coûtent 13.01€'

C’est mieux que notre version avec print(), d’autant qu’on peut sauvegarder facilement tout ça dans une variable :

>>> total = round(prix + exo_taxe, 2)
>>> msg = "Les " + produit + " coûtent " + str(total) + "€"
>>> print(msg)
Les nipple clamps coûtent 13.01

Mais ça reste chiant à taper, et encore plus à modifier. Si je veux insérer quelque chose là dedans, il me faut faire très attention en déplaçant mes + et mes " sans compter calculer ma gestion des espaces.

La raison est simple : il est difficile de voir la phrase que j’essaye d’afficher sans bien étudier mon expression.

Par ailleurs, je suis toujours obligé de faire mon arrondi.

Pour cette raison, je recommande de ne pas utiliser + pour formater son texte, car il existe de bien meilleurs outils en Python.

Avec %

Là, on arrive à quelque chose de plus sympa !

L’opérateur % appliqué aux chaînes de caractères permet de définir un texte à trous, et ensuite de dire quoi mettre dans les trous. C’est une logique de template.

Elle est courte et pratique : c’est la méthode que j’utilise le plus actuellement dans un shell ou sur les chaînes courtes.

Par exemple, si je veux créer la chaîne:

Les nipple clamps coûtent 13€

Alors mon texte à trou va ressembler à :

Les [insérer ici le nom du produit] coûtent [insérer ici le prix du produit]€

Avec l’opérateur %, le texte à trous s’écrit :

Les %s coûtent %s€

%s marque les trous.

Pour remplir, on met les variables à droite, dans l’ordre des trous à remplir :

>>> total = round(prix + exo_taxe, 2)
>>> "Les %s coûtent %s€" % (produit, total)
'Les nipple clamps coûtent 13.01€'

Pas besoin de convertir total en str, et la phrase qu’on souhaite obtenir est facile à deviner en lisant l’expression.

%s veut dire “met moi ici la conversion en str de cet objet”. C’est comme si on appelait str(total).

Il existe d’autres marqueurs :

  • %d est comme si faisait int() sur la valeur.
  • %f est comme si faisait float() sur la valeur.
  • %x est comme si on faisait hex()[2:]
  • etc

Ex:

>>> "%d" % 28.01
    '28'
>>> "%f" % 28
    '28.000000'
>>> "%x" % 28
    '1c'

La liste des marqueurs est disponible sur cette page de la doc.

En plus des marqueurs qui permettent de savoir où insérer la valeur et quel format lui donner, on peut aussi donner des précisions sur l’opération de formatage. On peut ainsi décider combien de chiffres après la virgule on souhaite, ou obliger la valeur à avoir une certaine taille :

>>> "%4d" % 28 # au moins 4 caractères
    '  28'
>>> "%04d" % 28 # au moins 4 chiffres
    '0028'
>>> "%.2f" % 28 # 2 chiffres après la virgule
    '28.00'

Ainsi notre exemple:

>>> total = round(prix + exo_taxe, 2)
>>> "Les %s coûtent %s€" % (produit, total)
    'Les nipple clamps coûtent 13.01€'

peut maintenant être réduit à :

>>> total = prix + exo_taxe
>>> "Les %s coûtent %.2f€" % (produit, total)
    'Les nipple clamps coûtent 13.01€'

Néanmoins un des défauts de % est qu’il n’accepte qu’un tuple, ou une valeur seule. Impossible de passer un itérable arbitraire :

>>> "Les %s coûtent %.2f€" % [produit, total]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-bb9b7acda392> in <module>()
----> 1 "Les %s coûtent %.2f€" % [produit, total]
 
TypeError: not enough arguments for format string

Et si vous voulez formater un tuple, il faut le mettre dans un tuple d’un seul élément, source de plantage :

>>> "Les données sont %s" % data
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-4e21e97b8a3f> in <module>()
----> 1 "Les données sont %s" % data
 
TypeError: not all arguments converted during string formatting
 
>>> "Les données sont %s" % (data, )
    "Les données sont ('nipple clamps', 13.01)"

Tout codeur Python s’est retrouvé un jour devant ce cas et s’est gratté la tête.

Par ailleurs, dès qu’il y a beaucoup de trous à combler dans le texte, ça devient vite difficile de savoir ce qui va où :

 "[%s] %s%s %s(%s) - %s%s%s"  % (
        datetime.datetime.now(),
        res,
        unit,
        type,
        variant,
        testers[0],
        testers[1],
        testers[2]
  )

Pour pallier ce problème, % peut accepter aussi un dictionnaire et avoir des trous nommés :

 "[%(date)s] %(value)s%(unit)s %(type)s(%(variant)s) - %(tester1)s%(tester2)s%(tester3)s"  % {
        "date": datetime.datetime.now(),
        "value": res,
        "unit": "m",
        "type": "3",
        "variant": "beta",
        "tester1": testers[0],
        "tester2": testers[1],
        "tester3": testers[2]
  }

On peut voir néanmoins que le pari n’est pas tout à fait gagné. Et on ne gagne pas tant que ça en lisibilité. Pour cette raison, les formatages complexes sont plus intéressants à faire avec format() que nous verrons plus loin.

Rappelez-vous néanmoins que depuis Python 3, format() ne fonctionne plus sur les bytes. % reste donc la seule option pour formater des paquets réseaux, des headers de jpeg et tout autre format binaire.

Formater les dates

Même si il est toujours recommandé d’utiliser une bonne lib pour manipuler les dates, Python permet déjà de faire pas mal de choses avec la lib standard.

En effet, certaines notions, comme le temps, ont une forme très différente entre celle utilisée pour les manipuler, et celles utilisées pour les représenter.

Pour cette raison, l’objet date de Python propose deux méthodes, strptime et strftime, pour gérer le format des dates.

La procédure pour gérer les dates se fait donc toujours en 3 parties, un peu comme l’encoding d’un texte :

  1. Créer une nouvelle date, soit à la main, soit à partir de données existantes.
  2. Manipuler les dates pour obtenir ce qu’on souhaite (un autre date, un durée, un intervalle, etc.
  3. Formater le résultat pour le présenter à l’utilisateur ou le sauvegarder à nouveau.

Pour récupérer une date existante, on va utiliser strptime (“str” pour string, “p” pour parse) :

>>> from datetime import datetime
>>> date = datetime.strptime("1/4/2017", "%d/%m/%Y")
>>> date.year
    2017
>>> date.day
    1

Le deuxième paramètre contient le motif à extraire de la chaîne de gauche : c’est l’inverse d’un texte à trous ! On dit “dans la chaîne de gauche, j’ai le jour là, le mois là et l’année là, maintenant extrais les”.

Pour formater une date, c’est la même chose, mais dans l’autre sens, avec strftime (“f” pour format) :

>>> date = datetime.now()
>>> date.strftime('%m-%d-%y')
    '04-01-17'

Le mini-langage pour formater les dates est documenté ici, et vous pouvez en apprendre plus sur les dates sur une petite intro dédiée.

Avec format()

% a ses limites. C’est un opérateur pratique pour les petites chaînes et les cas de tous les jours, mais si on a beaucoup de valeurs à formater, cela peut devenir vite un problème. Il possède aussi quelques cas d’utilisation qui causent des erreurs inattendues puisqu’il n’accepte que les tuples.

format() a été créé pour remédier à cela. Dans sa forme la plus simple, il s’utilise presque comme %, mais les marqueurs sont des {} et non des %s :

>>> "Les {} coûtent {}€".format(produit, prix)
    'Les nipple clamps coûtent 13€'

Mais déjà, format() se distingue du lot car il permet de choisir l’ordre d’insertion :

>>> "Les {1} coûtent {0}€".format(prix, produit)
    'Les nipple clamps coûtent 13€'

La méthode accepte également n’importe quel itérable grâce à l’unpacking :

>>> "Les {} coûtent {}€".format(*[produit, prix])
    'Les nipple clamps coûtent 13€'

Formater un tuple seul est aussi très simple :

>>> "Les données sont {}".format(data)
    "Les données sont ('nipple clamps', 13.01)"

Mais là où format() est bien plus pratique, c’est quand on a beaucoup de données et qu’on veut nommer ses trous :

 "[{date}] {value}{unit} {type}({variant}) - {testers[0]}{testers[1]}{testers[2]}".format(
        date=datetime.datetime.now(),
        value=res,
        unit="m",
        type="3",
        variant="beta",
        testers=testers
  )

Le texte à trous est plus clair, et on peut utiliser l’index d’une liste directement dedans.

Un autre avantage non négligeable, est que format() n’utilise pas de nombreux marqueurs différents comme %f, %d, %s

A la place, il n’y a que {}, et format() appelle en fait pour chaque valeur sa méthode … __format__, ou en l’absence de celle-ci appelle str().

>>> a = 42
>>> a.__format__("")
    '42'

Chaque objet peut définir __format__, et accepter ses propres options:

>>> a.__format__(".2f")
    '42.00'
>>> from datetime import datetime
>>> datetime.now().__format__('%d %h')  # pas besoin de strftime !
    '20 Sep'

Et format() utilise tout ce qui est après : dans un trou pour le passer à __format__:

>>> "{foo:.2f} {bar:%d %h}".format(foo=42, bar=datetime.now())

Cela permet des formatages très poussés.

Les f-strings

Les f-strings sont une nouvelle fonctionnalité de Python 3.6, et elles sont merveilleuses, combinant les avantages de .format() et %, sans les inconvénients :

>>> produit = "nipple clamps"
>>> prix = 13
>>> print(f"Les {produit} coûtent {prix:.2f} euros")
Les nipple clamps coûtent 13.00 euros

En gros, c’est la syntaxe de format(), mais sans sa verbosité.

En prime, on peut utiliser des expressions arbitraires dedans:

>>> print(f"Les {produit.upper()} coûtent {prix:.2f} euros")
Les NIPPLE CLAMPS coûtent 13.00 euros

A première vue, ça ressemble à du exec, et donc à un parsing lent, doublé d’une une grosse faille de sécurité.

Et bien non !

C’est en fait du sucre syntaxique, et au parsing du code, Python va transformer l’expression en un truc du genre:

"Les " + "{}".format(produit.upper()) + " coûtent " + "{:.2f}".format(prix) + " euros"

Mais en bytecode. Pas d’injection de code Python possible, et en prime, les f-strings sont aujourd’hui la méthode de formatage la plus performante.

En clair, si vous êtes en 3.6+, vous pouvez oublier toutes les autres.

Méthodes de l’objet str

Parfois, on ne veut pas remplir un texte à trous. Parfois on a déjà le texte et on veut le transformer. Pour cela, l’objet str possède de nombreuses méthodes qui permettent de créer une nouvelle chaîne, qui possède des traits différents :

>>> "    strip() retire les caractères en bouts de chaîne   ".strip() # espace par défaut
    'strip() retire les caractères en bouts de chaîne'
>>> "##strip() retire les caractères en bouts de chaîne##".strip("#")
    'strip() retire les caractères en bouts de chaîne'
>>> "##strip() retire les caractères en bouts de chaîne##".lstrip("#")
    'strip() retire les caractères en bouts de chaîne##'
>>> "##strip() retire les caractères en bouts de chaîne##".rstrip("#")
    '##strip() retire les caractères en bouts de chaîne'
>>> "wololo".replace('o', 'i') # remplacer des lettres
    'wilili'
>>> "WOLOLO".lower() # changer la casse
    'wololo'
>>> "wololo".upper()
    'WOLOLO'
>>> "wololo".title()
    'Wololo'

Notez bien que ces méthodes créent de nouvelles chaînes. L’objet initial n’est pas modifié, puisque les strings sont immutables en Python.

Parmi les plus intéressantes, il y a split() et join(), qui ont une caractéristique particulière : elles ne transforment pas une chaîne en une autre.

split() prend une chaîne, et retourne… une liste !

>>> "split() découpe une chaîne en petits bouts".split() # défaut sur espaces
    ['split()', 'découpe', 'une', 'chaîne', 'en', 'petits', 'bouts']
>>> "split() découpe une chaîne en petits bouts".split("e")
    ['split() découp', ' un', ' chaîn', ' ', 'n p', 'tits bouts']

join() fait l’inverse, et prend un itérable (comme une liste), pour retourner… une chaîne :)

>>> "-".join(['join()', 'recolle', 'une', 'chaîne', 'DEPUIS', 'des', 'petits', 'bouts'])
    'join()-recolle-une-chaîne-DEPUIS-des-petits-bouts'

Avec juste ces méthodes, on peut s’autoriser pas mal de fantaisies avec le texte, et le nombre de méthodes disponibles est assez large :

>>> dir(str)
    [...
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

Donc référez-vous à la doc.

Caractères spéciaux

On vous a menti !

Quand on écrit "" en Python on ne crée pas une chaîne. En fait, on écrit une instruction qui dit à Python comment créer une chaîne.

La différence est subtile, mais importante. "" n’est PAS la chaîne, "" est une instruction, une indication pour Python de comment il doit procéder pour créer une chaîne.

Et on peut donner des instructions plus précises à Python. Par exemple on peut dire, “insère moi ici un saut de ligne”. Cela se fait avec le marqueur "\n".

>>> print('un saut\n de ligne')
un saut
 de ligne

\n n’est PAS un saut de ligne. C’est juste une indication donnée à Python pour lui dire qu’ici, il doit insérer un saut de ligne quand il créera la chaîne en mémoire.

Il existe plusieurs marqueurs de ce genre, les plus importants étant \n (saut de ligne) et \t (tabulation).

Pour rentrer les caratères \t\n, il faut donc dire à Python explicitement qu’on ne veut pas qu’il insère un saut de ligne ou une tabulation, mais plutôt ces caractères.

Cela peut se faire, soit avec le caractère d’échappement \ :

>>> print('pas un saut\\n de ligne')
pas un saut\n de ligne

Soit en désactivant cette fonctionalité avec le préfixe r, pour raw string:

>>> print(r'pas un saut\n de ligne')
pas un saut\n de ligne

Cette fonctionalité est très utilisée pour les noms de fichiers Windows et les expressions rationnelles car ils contiennent souvent \t\n.

Bytes, strings et encoding

Python fait une distinction très forte entre les octets (type bytes) et le texte (type str). La raison est qu’il n’existe pas de texte brut dans la vraie vie, et que tout ce que vous lisez : fichiers, base de données, socket réseau, et même votre code source (!) est un flux d’octets encodés dans un certain ordre pour représenter du texte.

En Python, on a donc le type str pour représenter du texte, une forme d’abstraction de toute forme d’encodage qui permet de manipuler ses données textuelles sans se soucier de comment il est représenté en mémoire.

En revanche, quand on importe du texte (lire, télécharger, parser, etc) ou qu’on exporte du texte (écrire, afficher, uploader, etc), il faut explicitement convertir son texte vers le type bytes, qui lui a un encoding en particulier.

Ce principe mérite un article à lui tout seul, et je vous renvoie donc à la page dédiée du blog.

Templating

Parfois on a beaucoup de texte à gérer. Par exemple, si vous faites un site Web, vous aurez beaucoup de HTML. Dans ce cas, faire tout le formatage dans son fichier Python n’est pas du tout pragmatique.

Pour cet usage particulier, on utilise ce qu’on appelle un moteur de template, c’est à dire une bibliothèque qui va vous permettre de mettre votre texte à trous dans un fichier à part. Les moteurs de templates sophistiqués vous permettent de faire quelques opérations logiques comme des boucles ou des conditions dans votre texte.

La première chose à savoir, c’est de ne PAS utiliser string.Template. Cette classe ne permet d’utiliser aucune logique, et n’a aucun avantage par rapport à .format().

Pour le templating, il vaut mieux se pencher vers une lib tièrce partie. Les deux principaux concurrents sont Jinja2, le moteur de templating le plus populaire en Python, créé par l’auteur de Flask. Et le moteur de Django, fourni par défaut par le framework.

Depuis Django 1.10, le framework supporte aussi jinja2, donc je vais vous donner un exemple avec ce dernier. Sachez qu’il existe bien d’autres moteurs (mako, cheetah, templite, TAL…) mais jinja2 a plus ou moins gagné la guerre.

Un coup de pip :

pip install jinja2

On fait son template dans un fichier à part, par exemple wololo.txt:

Regardez je sais compter :
  {% for number in numbers %}
    - {{number}}
  {% endfor %}

Puis en Python:

import jinja2
 
# On définit où trouver les fichiers de template. Ex. le dossier courant:
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'))
 
# On dit à jinja de charger le template à partir de son chemin relatif
template = jinja_env.get_template('wololo.txt')
 
# On crée un contexte, c'est à dire une collection d'objects qu'on veut rendre
# accessibles dans le template. Généralement, c'est un dictionnaire dont les
# clés sont les noms des variables telles qu'elle apparaîtront dans le template
# et les valeurs ce que contiendront ces variables.
ctx = {"numbers": [1, 2, 3]}
 
# On demande le "rendu" du template, c'est à dire le mélange du template
# et du contexte.
resultat = template.render(ctx)
 
print(resultat)
 
# Ce qui donne :
# - 1
# - 2
# - 3

i18n et l10n

L’i18n, pour ‘internationalisation’ (soit 18 lettres entre le i et le n) est le fait d’organiser votre code de telle sorte que son interface puisse s’adapter à plusieurs cultures. La l10n, pour ‘localisation’ (soit 10 lettres entre le l et le n), est le fait de fournir avec son code les données nécessaires pour une culture en particulier.

Par exemple, marquer toutes vos chaînes de caractères comme étant traductibles et fournir un mécanisme de substitution de la chaîne est de l’i18n. Fournir un fichier de traduction pour l’espagnol pour ces chaînes est de la l10n.

La combinaison des deux est parfois nommée g11n pour “globalization”.

La g11n peut inclure:

  • La gestion de l’UI (traduction, sens de la lecture, formatage des nombres, devise…).
  • La gestion des dates (formatage, différences de types de calendriers, événements locaux, ordres des jours…).
  • La gestion du temps (zones horaires, heure d’été…).
  • La gestion de la géolocation (fournir des informations autour de soi, filtrer par la distance…).
  • La gestion politique et culturelle (symbolisme des couleurs, adaptation du contenu aux moeurs…).
  • La gestion légale (services et contenus selon la loi en vigueur, warnings obligatoires…).

Plus qu’un article, c’est un dossier qu’il faudrait faire sur ces sujets car c’est très, très vaste.

La traduction de texte peut être faite directement avec le module gettext fourni en Python. Certains formatages de nombres et de dates sont aussi faisables avec la stdlib grâce au module locale.

Néanmoins dès que vous voulez faire quelque chose de plus gros avec la g11n, je vous invite à vous tourner vers des libs externes.

Babel est la référence en Python pour le formatage du texte et des nombres, et il existe des extensions pour les moteurs de template les plus populaires. La lib inclut une base de données aussi à jour que possible sur les devises, les noms de pays, les langues…

pendulum est idéal pour la manipulation des dates en général, et des fuseaux horaires en particulier, y compris pour le formatage. Et ça évite de manipuler pytz à la main.

Et pour le reste… bonne chance !

Programmation orientée objet

Souvenez-vous, Python a des méthodes magiques. 3 sont dédiées au formatage.

__repr__ est utilisée quand on appelle repr() sur un objet. Typiquement, c’est ce qui s’affiche dans le shell si on utilise pas print(). C’est aussi ce qui détermine la représentation d’un objet quand on affiche une collection qui le contient.

__str__ est utilisée quand on appelle str() sur un objet. Quand on fait print() dessus par exemple. Si __str__ n’existe pas, __repr__ est appelée.

__format__ est utilisée quand on passe cet objet à format(), ou que cet objet est utilisé dans une f-string.

Ex :

class Foo:
    def __repr__(self):
        return "<Everybody's kung foo fighting>"
    def __str__(self):
        return "C'est l'histoire d'un foo qui rentre dans un bar"
    def __format__(self, age):
        if int(age or 0) > 18:
            return "On s'en bat les couilles avec une tarte tatin. Tiède."
        return "On s'en foo"

Ce qui donne :

>>> Foo()
<Everybody's kung foo fighting>
>>> print(Foo())
C'est l'histoire d'un foo qui rentre dans un bar
>>> print([Foo(), Foo()])
[<Everybody's kung foo fighting>, <Everybody's kung foo fighting>]
>>> "J'ai envie de dire: {}".format(Foo())
"J'ai envie de dire: On s'en foo"
>>> f"J'ai envie de dire: {Foo():19}"
"J'ai envie de dire: On s'en bat les couilles avec une tarte tatin. Tiède."

Astuce de dernière minute

Enfin pour conclure cet article dont la longueur n’a d’égale que celle de la période entre deux publications sur le blog, une petite remarque.

S’il est certes courant de formater une string, il est aussi possible de déformer un string. Ce sont des pièces plus résistantes qu’il n’y parait, et en cas d’empressement, le retrait total n’est pas nécessaire :

String en levrette

Ne pas porter de strings du tout évite aussi tout une classe de bugs

Assurez-vous juste que la partie ficelle soit suffisament éloignée pour éviter les frictions fort désagréables quand on entame un algo avec une grosse boucle.

Sinon, moins intéressant, mais toujours utile, les strings en Python peuvent êtres écrites sur plusieurs lignes de plusieurs manières:

>>> s = ("Ceci est une chaine qui n'a pas " 
...      "de saut de ligne mais qui est "
...      "écrite sur plusieurs lignes")
>>> print(s)
Ceci est une chaine qui n'a pas de saut de ligne mais qui est écrite sur plusieurs lignes

Cela fonctionne car deux chaînes littérales côte à côte en Python sont automatiquement concaténées au démarrage du programme. Cela évite les + \ à chaque fin de ligne, pourvu qu’on ait des parenthèses de chaque côté de la chaîne.

L’alternative des triples quotes est assez connue:

>>> s = """
...    Ceci est une chaine avec des sauts de lignes
...    écrite sur plusieurs lignes.   
... """
>>> print(s)
 
    Ceci est une chaine avec des sauts de lignes
    écrite sur plusieurs lignes.

Pour éviter l’indentation et les espaces inutiles:

>>> from textwrap import dedent
>>> print(dedent(s).strip())
Ceci est une chaine avec des sauts de lignes
écrite sur plusieurs lignes.

Perso j’ai un wrapper pour ça.

19 thoughts on “Le formatage des strings en long et en large

  • cocksucker

    Merci pour ce bel article, très bien illustré ;) ! Ça faisait longtemps… On en veut encore !

    “Assurez-vous juste que la partie ficelle soit suffisament éloignée pour éviter les frictions fort désagréables quand on entame un algo avec une grosse boucle.”

    Ça a égayé ma journée !

  • ashgan

    j’ai noté 2 coquilles:

    format() a été créé pour remédier à cela. Dans sa forma la plus simple

    et

    Les f-strings sont une nouvelle fonctionalité de Python 3.6, […] sans les inconvéniants

    a ranger a cote de l’article sur l’encoding!

  • ster

    Merci l’article, faudra que je passe à la 3.6, f-strings a l’air cool :D

    Et histoire de faire mon chiant moi aussi:

    “utilisé pour séparé les différents arguments” -> “utilisé pour séparer”

    “c’est la syntaxe de format(), mais dans sa verbosité.” -> “mais sans sa verbosité”?

    “écrite sur plusiers lignes.” -> plusieurs

  • Alex

    Oh God, ça m’a manqué!

    Mon 1er (et seul) éclat de rire de la journée.

    Merci

  • yuiio

    Comme d’hab, encore une ressource géniale. On sent la générosité du gars qui bourlingue dans du code python depuis des années. Les forces de cet article sont :

    – La mise en perspective historique au travers des différentes versions de python. On peut trouver toutes ces infos ailleurs sur le net mais c’est éparpillé et sans lien. Seule l’expérience peut tisser le lien entre toutes.

    – Même si l’article est long, le contenu est structuré et progressif. Cet article nourrit ton cerveau (même si tu fais du formattage de string depuis des années). Faut être bon pédagogue pour faire ça.

    – L’article est AUSSI une fucking ressource quand t’as besoin de t’en servir. Y’a du code concret, limpide et pertinent. (Bien vu aussi le tl;dr en guise de sommaire).

    – Mettre dans un tel contenu en guise d’illustration ces deux femmes WTF !? … produit un effet malgré le côté … c’est étonnant, drôle et joyeux à la fois. On sent un vent de liberté, de l’amour, de la curiosité et de la culture.

    Arriver à rendre vivant à ce point une documentation informatique, mérite le respect.

  • Romain

    J’utilise string.Template de temps en temps. Là par exemple, j’ai un template avec pas mal d’accolades que je n’ai pas envie d’échapper pour utiliser format. Je n’ai pas envie non plus d’installer une dépendance comme jinja2 pour si peu.

    Juste pour dire qu’il y en a pour tous les goûts !

  • batisteo

    Merci pour ces articles où il y a toujours quelquechose à apprendre, et qui sert de référence pour plus tard !

    En voyant que ça parle de formatage de date, j’aimerais bien voir un comparatif d’Arrow, pendulum, Delorean et du nouveau Maya. Et il doit y en avoir d’autres…

  • Sam Post author

    Je m’épargne l’article:

    J’ai adoré arrow. Je suis passé à pendulum après avoir étudié la question. C’est le projet le plus sérieux, avec le meilleur rapport sanité / API / puissance / propreté / perf. Dolorean est loin derrière en terme de fonctionalité. Maya est simplement inutile vu ce qui existe déjà. Elle fait moins et arrive après, sur un marché ou le problème est déjà résolu, contrairement à requests. Je sais pas pourquoi kenneth a pondu cette lib.

    Bref, go pendulum.

  • Brice

    Merci pour l’article ! Toujours intéressant.

    Niveau .format(), j’ai lu ça, à retenir si on l’utilise avec du texte non sûr : http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/

    Et je me suis posé la question par rapport à ça (et j’ai eu la flemme de faire le test moi-même) :

    “Les ” + “{}”.format(produit.upper()) + ” coutent ” + “{:.2f}”.format(prix) + ” euros”… les f-strings sont aujourd’hui la méthode formattage la plus performante.

    C’est peut-être bête, mais j’aurais vu une conversion comme celle-là plus rapide, à l’exécution, non?

    “Les ” + produit.upper().format() + ” coutent ” + prix.format(“{:.2f}”) + ” euros”. Y’a plus aucun string à parser du coup…

    Bisous bisous

  • Brice

    Ah oui, j’oubliais :

    Pour cela, l’objet str possède de nombreuses méthodes qui permet de créer une nouvelle chaîne -> qui permettent

    J’avais vu une autre faute d’orthographe, mais je la retrouve plus, désolé!

  • cladmi

    Il est tout faux l’exemple “Pour pallier ce problème, % peut accepter aussi un dictionnaire et avoir des trous nommés :”

    Il faut que ce soit un dictionnaire et non un tuple

    il manque une virgule après le “”unit”: “m”

    Il y a 3 fois la clé testers1

  • cladmi

    Et il y a le même soucis de “unit”: “m” qui n’a pas de virgule dans l’example avec format.

  • john holt

    Merci pour l’article, le blog (je suis un grand fan du contenu et du style décalé), et pour le plaisir de retrouver cette chere meilleure guitariste francaise sur un grand classique de beethove.

    Du coup je me permets de partager la meilleure version au piano que j’aie jamais entendue – Lisitsa a les doigts les plus voluptueux de l’univers, et ca fait frémir les oreilles. On est sur un blog culturel après tout.

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.