Présentation, opérations, conversions

\(\newcommand{\ds}{\displaystyle}\) \(\newcommand{\Frac}{\ds\frac}\) \(\renewcommand{\r}{\mathbb{ R}}\) \(\newcommand{\C}{\mathbb{ C}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\z}{\mathbb{ Z}}\) \(\newcommand{\Q}{\mathbb{ Q}}\) \(\newcommand{\N}{\mathbb{ N}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\ol}{\overline}\) \(\newcommand{\abs}[1]{\left| \,{#1} \right|}\) \(\newcommand{\pv}{\;;\;}\) \(\newcommand{\ens}[1]{\left\{ {#1} \right\}}\) \(\newcommand{\mens}[1]{\setminus\left\{ {#1} \right\}}\) \(\newcommand{\Par}[1]{\left({#1}\right)}\) \(\newcommand{\pe}[1]{\left\lfloor {#1} \right\rfloor}\) \(\newcommand{\trans}[1]{\,^t\!{#1}}\)

Présentation, opérations, conversions

Les chaînes : premier contact

La notion de chaîne

Les chaînes de caractères permettent de stocker du texte. En première approximation, une chaîne est une succession de caractères imprimables. Cela peut être, par exemple, une phrase ou un groupe de mots, comme Le nombre 42 est magique.

On dit souvent chaîne au lieu de chaîne de caractères.

De la même façon qu’un programme peut travailler avec des entiers, il peut travailler avec des chaînes.

Sensibilité à la casse

La casse d’un caractère alphabétique est le fait que le caractère soit en majuscule ou en minuscule.

Les chaînes bonjour et bonJour sont considérées en Python comme distinctes. Si deux chaînes ont, à des positions identiques, des lettres de casses différentes, les chaînes sont considérées comme distinctes.

Chaîne littérale

Le moyen le plus immédiat d’utiliser des chaînes dans un code Python est par le biais d’une chaîne littérale :

1
2
3
4
5
s = "orange"
print(len(s))

t = 'rose'
print(len(t))
6
7
6
4

À la ligne 1, on lit "orange" : c’est une chaîne, dite littérale. A la ligne 2, on demande l’affichage du nombre de caractères de la chaîne. On lit à la ligne 6 la longueur de la chaîne.

Une chaîne est littérale lorsque

  • tous ses caractères sont placés dans le code-source,
  • la chaîne est encadrée par des caractères particuliers, appelés quotes, ici des guillemets anglo-saxons (mais il existe d’autres délimiteurs possibles).

Afficher une chaîne

Pour afficher une chaîne avec une « commande » Python, on utilisera obligatoirement la fonction print :

1
2
3
fruit = "orange"
print(fruit)
print("bonjour")
4
5
orange
bonjour
  • Lignes 2 et 4: ici, on affiche une chaîne par l’intermédiaire d’une variable.
  • Lignes 3 et 5 : affichage direct d’une chaîne littérale.
  • On notera que les délimiteurs de chaîne ne sont pas affichés.

Dans certains environnement (mode interactif, cellule Jupyter Notebook), une chaîne peut être affichée sans recours à la fonction print.

Erreur courante de débutant

Pour afficher une chaîne littérale, on fera attention de ne pas oublier les quotes autour de la chaîne :

print(bonjour)
NameError: name 'bonjour' is not defined

La notion de caractère

L’unité fondamentale d’un texte est le caractère. Un mot, une phrase est composée de caractères. On peut disposer de certains caractères en les plaçant entre guillemets, par exemple la lettre "Y" qui est en fait une chaîne d’un seul caractère (alors qu’on en écrit trois dans le code si on compte les deux guillemets).

Parmi les caractères, on trouve les caractères alphabétiques majuscules ou minuscules mais aussi tous les caractères de ponctuation, les chiffres, par exemple le chiffre "8".

On dispose aussi de caractères invisibles comme l’espace " " (taper sur la barre d’espace) ou encore le saut de ligne. Le blanc souligné _ est aussi un caractère très utilisé. Il existe aussi des caractères non imprimables et qui peuvent se retrouver dans une chaîne.

Python supporte l’encodage UTF-8 et donne donc accès à tous les caractères imaginables.

En pratique, dans un cours de débutant sur les chaînes de caractères, on essaye de ne pas avoir à utiliser des caractères tels que le guillemet ou l’apostrophe car ils ont rôle spécial pour construire des chaînes dites littérales.

La notion de chaîne littérale

Une chaîne littérale est un chaîne de caractères saisie telle quelle dans le code-source entre une paire de délimiteurs. Ainsi, "orange" est une chaîne littérale, le délimiteur étant ici un guillemet :

s = "orange"
print(s)
orange

Les délimiteurs ne font pas partie du contenu d’une chaîne littérale, comme on le voit à l’affichage.

Insistons : une chaîne littérale n’a de sens que dans un code-source puisque littéral veut dire qui s’interprète littéralement autrement dit, « lettre à lettre », et c’est bien comme cela qu’elle apparaît dans le code-source Python.

Le délimiteur de chaîne littérale le plus familier est le guillemet anglais ". Mais un autre délimiteur usuel est l’apostrophe :

1
2
3
4
s = "rose"
t = 'rose'
print(s)
print(t)
5
6
rose
rose
  • Ligne 1 : chaîne littérale entourée de guillemets
  • Ligne 2 : chaîne littérale entourée d’apostrophes
  • Lignes 5-6 : les chaînes s et t sont égales.

Pour ce qui est du contenu d’une chaîne, par défaut, les caractères d’une chaîne Python (littérale ou pas d’ailleurs) appartiennent au jeu Unicode et donc peuvent comporter des accents ou être des caractères spéciaux. Par exemple, la chaîne littérale s suivante est valide :

s = "Les pièces de 5€ n'existent pas"
print(s)
Les pièces de 5€ n'existent pas

Opérations sur des chaînes

On peut faire de nombreuses opérations sur les chaînes. Voici juste quelques exemples et qui seront détaillés ailleurs.

Nombre de caractères

Déterminer avec la fonction standard len le nombre de caractères d’une chaîne, ce qu’on appelle sa longueur :

1
print(len("anticonstitutionnellement"))
2
25
  • Ligne 1 : la fonction standard len, fournie par Python, effectue le calcul de la longueur.

Concaténer deux chaînes

L’opérateur + permet de mettre bout à bout deux chaînes :

1
print("bon" + "Jour")
2
bonJour
  • Ligne 1 : la concaténation (c’est le terme consacré) est effectuée avec l’opérateur +.
  • Ligne 2 : noter que les deux chaînes bon et Jour sont placées bout à bout, sans espace entre les chaînes.

Détail : l’expression "bon" + "Jour" est la concaténation de deux chaînes littérales et n’est pas une chaîne littérale.

Chaîne vide

Une chaîne peut être vide autrement dit ne contenir aucun caractère. Une chaîne vide peut être représentée par la chaîne littérale "" (deux guillemets collés l’un à l’autre et qui n’entourent rien). La chaîne vide est de longueur nulle :

vide = ""

print(len(vide))
0

La chaîne vide peut aussi être notée '' c’est-à-dire deux apostrophes côte-à-côte, sans espace entre les deux apostrophes :

vide = ''

# une chaîne vide a une longueur nulle
print(len(vide))
0

La chaîne vide a de nombreux usages (comme le chiffre zéro en a !). Par exemple, une chaîne vide peut être utilisée pour supprimer des caractères à l’intérieur d’une chaîne en remplaçant les caractères par une chaîne vide.

Le caractère espace

De même qu’il existe un caractère A désignant la lettre A, il existe un caractère espace, celui obtenu en tapant sur la barre d’espaces. Plus précisément, le contenu de la chaîne " " est un espace :

1
2
3
print("Bug" + "2038")
print("Bug" + " " + "2038")
print("Bug 2038")
4
5
6
Bug2038
Bug 2038
Bug 2038
  • Ligne 1 : Les chaînes "Bug" et "2038" sont concaténées sans insertion d’espace.
  • Lignes 2 et 3 : les chaînes affichées sont identiques : l’espace peut-être considéré comme une chaîne littérale (ligne 2) ou immergé dans la chaîne (ligne 3).

Invisible n’est pas vide

La chaîne " " n’est pas vide, elle contient exactement un caractère :

espace = " "
print(len(espace))
1

Le caractère espace sert aussi à créer une indentation en plaçant des « blancs » en début de lignes. Pour dessiner en mode texte, la figure ci-dessous :

         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************
         #
         #

il faut placer des caractères espace en début de ligne.

Le caractère saut de ligne

Dans beaucoup de langages de programmation, y compris Python, le codage du caractère saut de ligne est noté \n. L’usage de ce groupe de deux caractères permet effectivement de signifier un saut de ligne dans une chaîne (littérale) :

s = "Lundi\nMardi\nMercredi"
print(s)
Lundi
Mardi
Mercredi

On notera que le caractère saut de ligne \n n’a pas de raison d’être d’être suivi d’un espace littéral qui provoquerait un effet peut-être non désiré :

1
2
s = "Lundi\n Mardi\nMercredi"
print(s)
3
4
5
Lundi
 Mardi
Mercredi
  • Ligne 1 : noter l’espace dans le code source, dans la chaîne littérale, juste avant le mot Mardi.
  • Ligne 4 : l’espace est compté dans la chaîne et visible à l’affichage.

Bien que le caractère littéral représentant un saut de ligne s’écrive avec deux caractères, ces deux caractères sont insécables et ils comptent pour un caractère dans la longueur de la chaîne vue comme objet Python. Ainsi :

s="rose\nbleu"
print(s)
print(len(s))
rose
bleu
9

Plusieurs sauts de ligne

Pour aérer un texte, on peut souhaiter y placer plusieurs sauts de ligne l’un après l’autre. Si on place côte-à-côte deux caractères \n, on effectue deux passages à la ligne, ce qui va créer une ligne blanche :

texte = "rose\n\nkiwi"
print(texte)
rose

kiwi

Une ligne blanche n’est jamais vide et est constituée d’au moins un caractère, le saut de ligne et éventuellement des espaces en début de ligne.

Concaténation et répétition de chaînes

On peut concaténer des chaînes, c’est-à-dire les mettre bout à bout. La concaténation de chaînes est effectuée avec l’opérateur + :

1
2
3
4
5
6
a = "Rose"
b = "Rouge"
c = "Rome"
s = a + b + c
print(s)
print(a, b, c)
7
8
RoseRougeRome
Rose Rouge Rome
  • Ligne 4 : s est la concaténation des chaînes a, b et c.
  • Lignes 5 et 8 : la concaténation des chaînes crée une nouvelle chaîne et préserve les chaînes initiales.

On peut aussi concaténer n fois une chaîne avec elle-même avec l’opérateur *

1
2
3
4
5
a = "Rose"
r = 3 * a
print(r)
print(a * 3)
print(2 * a *2)
6
7
8
RoseRoseRose
RoseRoseRose
RoseRoseRoseRose
  • Ligne 3 ou 4 : a est répétée 3 fois
  • Lignes 5 : on peut multiplier des deux côtés.

On peut aussi panacher addition et multiplication :

a = "Rose"
b = "Rouge"
c = "Rome"
s = 2 * (c + a + ' ') + 2 * (c + b + ' ')
print(s)
RomeRose RomeRose RomeRouge RomeRouge

Concaténation et produit par zéro

L’opération 0 * s renvoie la chaîne vide et 1 * s. On peut en déduire une astuce pour mettre un nom au pluriel en ajoutant, selon l’effectif, un "s" terminal :

for val in (0, 1, 7):
    print(val, "bille" + (val > 1)*"s")
0 bille
1 bille
7 billes

Voici 3 autres façons de réaliser la même opération

for val in (0, 1, 7):
    print(val, "bille" + ["s", ""][val < 2])

print("---------------")
for val in (0, 1, 7):
    print(val, "bille" + ("" if val < 2 else 's'))

print("---------------")
for val in (0, 1, 7):
    print(val,  f"bille{'s'[val<2:]}")

et qui affiche :

0 bille
1 bille
7 billes
---------------
0 bille
1 bille
7 billes
---------------
0 bille
1 bille
7 billes

Chaînes égales

L’opérateur == permet de comparer deux chaînes pour savoir si elles ont ou pas exactement les mêmes caractères et dans le même ordre :

1
2
3
4
print("ROSE" == "R0SE")
print("RoseRose" == "Rose" + "Rose")
print("Rose" == "rose")
print("ROSE" == 'ROSE')
5
6
7
8
False
True
False
True
  • Ligne 1 : Malgré les apparences, les chaînes sont distinctes : à l’indice 1, l’une utilise la lettre O majuscule et l’autre un zéro.
  • Ligne 2 : "Rose" + "Rose" est la concaténation de deux chaînes.
  • Ligne 3 : Pour que deux caractères soient égaux, il faut déjà qu’ils aient la même casse, ce qui n’est pas le cas de la lettres initiale.
  • Ligne 4 : Des délimiteurs apostrophes ou guillemets n’ont pas d’influence sur l’égalité de chaînes.

Attention que l’égalité de deux chaînes est une opération ayant une complexité cachée. En effet, pour savoir si deux chaînes sont égales ou pas, l’interpréteur Python compare terme à terme les éléments des deux chaînes jusqu’à ce que deux caractères soints distincts ou bien qu’une des chaînes ne possède plus d’élément à examiner.

Donc la comparaison de deux chaînes de longueur chacune 1000, peut nécessiter jusqu’à 1000 comparaisons. Ainsi, l’opérateur == entre deux chaînes peut être coûteux (en réalité, c’est moins simple que ça, cf. implicit Interning).

Accès aux caractères d’une chaîne

On peut accéder en lecture individuellement aux caractères d’une chaîne à l’aide d’un indice entier :

p = "Jupiter"

# le premier caractère de la chaîne p
print(p[0])

# le troisième caractère de la chaîne p
print(p[2])

# la longueur de la chaîne
n = len(p)
print(n)

# Dernier caractère de la chaîne p
print(p[n - 1])
J
p
7
r

Les caractères d’une chaînes sont numérotés de la gauche vers la droite. Ces numéros sont appelés des indices ; la numérotation commence à 0 (et non pas à 1) et se termine à n-1n désigne le nombre total de caractères.

On accède à chaque caractère de la liste avec l’opérateur [] d’indexation.

Le fait de pouvoir accéder à tous les caractères d’une chaîne par des entiers consécutifs se traduit en disant que les chaînes de caractères sont du type séquence.

Il est possible d’utiliser des indices négatifs, ce qui est parfois à l’origine de bugs pour ceux qui ne connaissent pas leur existence.

Chaîne vs liste des caractères

On recontre parfois dans du code d’apprentis codeurs des « chaînes » définies par la liste de leurs caractères, cf. 1re ligne ci-dessous :

1
2
3
s = ['C', 'H', 'O', 'C', 'O', 'L', 'A', 'T']
print(len(s))
print(s[0])
4
5
8
C

Il se trouve que (lignes 4 et 5) la longueur de la chaîne est donnée correctement ainsi que le premier caractère. Une tournure ici équivalente serait d’utiliser une chaîne littérale :

s = "CHOCOLAT"
print(len(s))
print(s[0])

En pratique, on rencontre souvent la liste des lettres de l’alphabet :

alpha = ['A', 'B', 'C', 'D', etc]

Bien que la tournure ne soit pas forcément incorrecte (en particulier si on a besoin de modifier une chaîne), la plupart du temps ce n’est pas justifié. La saisie de la chaîne est beaucoup plus laborieuse que sous forme de chaîne littérale, la chaîne est moins facilement lisible, on n’a pas une chaîne mais une liste, donc on ne dispose pas des méthodes de chaînes, l’appartenance n’est pas définie de la même façon, par exemple :

s = ['C', 'H', 'O', 'C', 'O', 'L', 'A', 'T']
print("" in s)

s = "CHOCOLAT"
print("" in s)
False
True

Dépassement d’indice dans une chaîne

Comme pour les listes, un indice de chaîne trop grand entraîne la levée d’une exception :

1
2
3
4
z = "orange"
c = z[10]

print(c)
5
6
    c = z[10]
IndexError: string index out of range
  • Ligne 6 : une erreur d’indice est signalée
  • Ligne 2 : la variable c désignerait le caractère à l’indice 10 alors que l’indice maximal dans c est 5 puisque la chaîne z est de longueur 6.
  • Ligne 2 : L’opération z[10] est en fait interdite, on tente d’accéder en lecture à un caractère qui n’existe pas.
  • Lignes 5-6 : Le message d’erreur explique que l’indice 10 est en dehors de la plage d’indices possibles.

Tenter d’accéder en simple lecture à un caractère d’une chaîne avec un indice ne correspondant pas à un élément de la chaîne conduit à une erreur, de type indexError et qualifiée de débordement d’indice. C’est le même problème que pour les listes.

Boucle for : parcours de chaînes

Une chaîne est une séquence et peut être parcourue, sans indice, par une boucle for. Voici un exemple de parcours d’une chaîne avec une boucle for :

for c in "ALIBABA":
    print(c*2)
AA
LL
II
BB
AA
BB
AA

C’est le même principe que pour les listes, il est possible de faire un parcours sans recourir à un indice.

Il est aussi possible de parcourir la chaîne par indices :

s="ALIBABA"

for i in range(len(s)):
    print(s[i]*2)
AA
LL
II
BB
AA
BB
AA

Cela peut être justifié dans certaines situations (par exemple si on doit comparer un caractère au suivant ou au précédent) où l’usage recommande d’utiliser enumerate, voir cette explication.

Comparaison alphabétique de chaînes

Les différents opérateurs de comparaison (par exemple < ou >=) permettent d’effectuer certaines comparaisons alphabétiques de chaînes :

print("prune" < "rose")
True

Si les chaînes ont la même casse (soit les deux en minuscule, soit les deux en majuscule) et sont dépourvues de caractères spéciaux ou d’accents, ces comparaisons correspondent à ce qui est attendu. En effet, l’opérateur de comparaison appliqué à des chaînes compare les codes unicodes des chaînes. Ces codes ne correspondent que partiellement à l’ordre alphabétique usuel. Pour des chaînes de casses différentes ou utilisant des caractères spéciaux, les comparaisons sont parfois contraires à l’ordre usuel :

print("prune" < "Rose")
print("été"< "printemps")
False
False

Pour comparer deux chaînes en tenant compte des accents, voir le paragraphe intitulé Comparaison alphabétique de chaînes unicodes.

La fonction repr

La notion exposée dans ce paragraphe n’est pas forcément adaptée à quelqu’un ayant peu d’expérience en Python.

La fonction repr est une fonction standard. Voici un exemple commenté d’utilisation de cette fonction :

1
2
3
4
5
X = "rose"
code = repr(X)
print(code)
print(X)
print(len(code))
6
7
8
'rose'
rose
6
  • Ligne 1 : la fonction repr peut prendre en argument n’importe quel objet Python (un entier, une liste, une fonction, etc), ici une chaîne (qui est littérale ici mais ça n’a aucune importance) ;
  • Ligne 2 : la variable code est ici une chaîne de caractères. Le contenu de cette chaîne est exactement un code Python d’un objet qui vaudrait X.
  • Ligne 6 : on observera la présence d’apostrophes au début et en fin de chaîne, cela va être expliqué ci-dessous.
  • Ligne 7 : par contraste, on observera l’absence de séparateur (apostrophe ou guillemet) dans l’affichage de X
  • Ligne 8 : code n’est pas la chaîne rose ; cette dernière est de longueur 4 alors que code est une chaîne de longueur 6 : code est en fait une chaîne littérale, donc le premier caractère de code n’est pas le r de rose mais une apostrophe (délimiteur de chaîne littérale).

Ainsi, la fonction repr appliquée à un objet Python X (par exemple un entier, une chaîne, une liste, etc) renvoie une chaîne dont le contenu, s’il était placé dans du code Python, serait évalué en l’objet X. Le nom repr vient du fait que la fonction renvoie une représentation de l’objet sous forme de chaîne.

Pour dire les choses simplement, repr transforme des objets Python en du code Python.

Ne pas s’inquiéter si cette notion n’est pas totalement claire, elle peut nécessiter d’avoir pas mal travaillé avec du code Python pour bien être comprise.

La fonction repr ressemble beaucoup à la fonction print dans la mesure où, dans la plupart des cas, l’affichage de ce que renvoie repr est strictement identique à l’affichage produit par la fonction print :

print(repr(42))
print(42)
42
42

Il y a une nuance essentielle pourtant : la fonction print appliquée à un objet se contente de fournir une représentation uniquement visuelle d’un objet (la fonction print affiche à l’écran et ne retourne rien) tandis que la fonction repr renvoie une chaîne représentant l’objet.

La présentation ci-dessus est simplifiée. Il se peut très bien que repr ne renvoie pas une chaîne de code Python exécutable. Par exemple,

print(repr(print))
<built-in function print>

et les cas de ce type sont nombreux. La documentation officielle de la fonction : repr. La définition donnée n’est pas claire et la documentation n’indique pas quand la chaîne retournée est directement exécutable ou pas.

La classe str

Toutes les chaînes de caractères sont des instances de la classe str qui représente le type string en Python. La classe str peut être utilisée comme « constructeur » ie un appel de la forme str(v)v est un objet Python retourne (construit) une chaîne qui permet d’afficher v. Par exemple :

1
2
3
4
s = str(6 * 7)

print(s)
print(s[0])
5
6
42
4
  • Ligne 1 : le constructeur str est appelé sur l’entier 42
  • Ligne 4 : s est l’interprétation par str de l’entier 42. Il s’agit de la chaîne "42".
  • Lignes 3 et 5 : s[0] n’est donc que le premier chiffre de 42 vu comme comme un caractère, à savoir le chiffre "4".

C’est anecdotique mais un appel str() renvoie une chaîne vide.

La fonction print est fortement liée à la fonction str : en effet, la fonction print appliquée à un objet X affiche la chaîne renvoyée par str(X).

Conversion d’un nombre en chaîne avec le type str

Dans certaines situations, un nombre x, par exemple entier ou flottant, a besoin d’être vu moins comme un nombre que comme la suite des chiffres décimaux qui composent x, chaque chiffre (décimal) étant alors vu comme un caractère. Le type natif str permet de convertir un nombre en chaîne.

Soient des questions telles que :

  • trouver le nombre de chiffres d’un nombre, par exemple 458 a 3 chiffres
  • calculer la somme des chiffres d’un nombre, par 458 a pour somme des chiffres 17
  • récupérer le premier chiffre d’un nombre, par exemple 4523012 a pour premier chiffre 4
  • changer un point par une virgule, par exemple 3.14 -> 3,14

Pour ce genre de programme, il est préférable de voir les nombres comme chaînes de caractères.

Fonction str de conversion

Voici un exemple d’utilisation :

1
2
3
print(str(42))
print(str(3.14))
print(str(75 + 30))
4
5
6
42
3.14
105
  • Ligne 1 str prend ici un entier en argument et renvoie sa conversion en caractères (en chiffres décimaux). En particulier, str(42) est la chaîne de caractères 42
  • Ligne 2 : le type natif str convertit également des flottants.
  • Lignes 3 et 6 : noter que l’argument 75 + 30 est d’abord évalué en l’entier 105 avant d’être converti en la chaîne 105.

Le type natif str s’utilise comme une fonction : on parlera donc de fonction str même s’il s’agit plutôt d’un type.

Signalons qu’effectuer la conversion d’un nombre vers sa représentation décimale nécessite un travail qui n’est pas complètement trivial même si l’idée est simple (faire des divisions entières successives par 10). Le code de l’implémentation en C se trouve ici, cf. la fonction long_to_decimal_string_internal.

La Référence du langage indique seulement depuis sa version 3.8 que la représentation se fait en base 10, si possible.

Utilisations de la fonction str

Voici quelques exemples d’utilisation de la fonction str.

Nombre de chiffres

Pour calculer le nombre de chiffres d’un nombre entier, on peut utiliser la fonction str :

1
2
3
4
x = 2038 * 2 ** 10
ch = str(x)
print(len(ch))
print(x)
5
6
7
2086912
  • Ligne 2 : on convertit l’entier en chaîne et la fonction len compte le nombre de caractères de la chaîne, ce qui donne le nombre de chiffres (en base 10, toujours).
  • Ligne 6 : en calculant explicitement x, on vérifie visuellement que x a bien 7 chiffres.

Changer le point décimal en virgule

Pour transformer par exemple le nombre 3.14 en sa représentation à virgule 3,14 on transforme d’abord le flottant 3.14 en chaîne puis on remplacera le point par une virgule dans la nouvelle chaîne :

x = 3.14
z = str(x)
zz = z.replace(".", ",")
print(zz)
3,14
  • Noter que zz est une chaîne de caractères

Limitation de la conversion en chaîne

Il faut prendre garde qu’une fois un nombre x converti en chaîne s, il n’est plus possible d’effectuer directement des opérations numériques sur s car les chiffres sont en fait des caractères et non des entiers.

Par exemple, supposons que l’on cherche à afficher les chiffres pairs d’un entier ; si x = 53421 alors les chiffres pairs sont 4 et 2. Alors, on ne pourra pas tester la parité du chiffre avec l’opération % 2 :

x = 53421
s = str(x)
for c in s:
    if c % 2 == 0:
        print(c)
    if c % 2 == 0:
TypeError: not all arguments converted during string formatting

Pour y parvenir, il faudrait reconvertir les chiffres en entiers en utilisant int. On aurait le même type de problème s’il fallait calculer la somme des chiffres.

Conversion d’une chaîne en entier

Il ne faut pas confondre un nombre entier et la chaîne qui le représente. Par exemple, les objets Python "2042" et 2042 s’affichent de la même façon :

a = "2042"
print(a)
b = 2042
print(b)
2042
2042

mais ils sont bien distincts : le premier objet est de type chaîne de caractères et le second est de type entier.

C’est quoi le problème ?

Voici un exemple de situation où l’on a besoin de convertir une chaîne en nombre : soit un programme qui, à partir d’une date donnée sous forme de chaîne de caractères et sous un certain format, par exemple 31/12/2042, doit retourner la date du lendemain, ici 01/01/2043 (sous forme de chaîne, toujours). Pour résoudre le problème, il faut extraire de la date les éléments suivants :

  • le jour (ici 31),
  • le mois (ici 12),
  • l’année (ici 2042)

qui sont des sous-chaînes et effectuer des opérations sur ces objets vus comme des entiers, par exemple une addition, ici \(2042+1=2043\), d’où la nécessité de convertir la chaîne en nombre entier, car la somme des chaînes "2042" +"1" ne donne pas le résultat attendu :

print("2042" + "1")
20421

La fonction de conversion int

Pour convertir une chaîne représentant un entier écrit en base 10 en l’entier correspondant, on utilise le type intégré int :

1
2
a = "2042"
print(int(a) + 1)
3
2043
  • Ligne 2 : int(a) vaut l’entier représenté en base 10 par la chaîne « 2042 » c’est-à-dire le nombre 2042. Il est donc licite d’ajouter 1 à int("2042").

int désigne le type int (c’est-à-dire le type entier) mais peut aussi être utilisé comme une fonction. On parlera donc de fonction int même s’il s’agit plutôt d’un type.

Remarquer que l’opération suivante :

"2042" + 1

n’a pas de sens en Python :

a = "2042"
print(a + 1)
    print(a + 1)
TypeError: Can't convert 'int' object to str implicitly

Précautions

On prendra garde qu’une fois une chaîne s convertie en nombre entier x, les opérations de chaînes sur x ne sont plus permises (elles n’ont pas de sens). Par exemple, si un nombre x est écrit sous forme de chaîne s alors s[0] représente le premier chiffre de x, un chiffre étant ici vu comme un caractère. En revanche, l’opération x[0] n’a pas de sens :

s = "3000"
x = int(s)
print(s[0])
print(x[0])
    print(x[0])
TypeError: 'int' object is not subscriptable

Le code de l’implémentation en C de la conversion semble se trouver ici, cf. la fonction PyLong_FromString. La fonction est particulièrement complexe (mais elle convertit aussi depuis des bases autres que 10).

Chaînes autorisées

Pour qu’un appel str(s) convertisse la chaîne s en entier encore faut-il que la chaîne fournie représente un entier :

x = int("3.14")
print(x)
    x = int("3.14")
ValueError: invalid literal for int() with base 10: '3.14'

La documentation indique que la chaîne doit être un entier littéral, ce qui d’ailleurs, n’est pas tout à fait vrai :

x = int("0042")
print(x)
42

alors que 0042 n’est pas un littéral entier valide :

x = 0042
print(x)
    x = 0042
           ^
SyntaxError: invalid token

Conversion d’une chaîne en flottant

On peut avoir besoin de convertir une chaîne représentant un nombre décimal « à virgule » et dont on a besoin de connaître la valeur en tant que nombre, par exemple pour faire une opération. Typiquement, on a du texte comme ci-dessous

Dollar:0.83€
Livre:1.16€
Rouble:0.011€

donnant, entre autres, le cours du dollar en euro et qu’on cherche à savoir combien d’euros coûtent 42 dollars. Pour cela, on va capturer, par un moyen ou par un autre et que je ne détaille pas ici, la chaîne "0.83". Une fois ceci fait, voici comment on en récupère la valeur numérique :

1
2
3
4
taux="0.83"
valeur = float(taux)
n=42
print(n, "dollars valent", n*valeur, "euros")
5
42 dollars valent 34.86 euros
  • Ligne 1 : taux est une chaîne
  • Ligne 2 : valeur est la valeur flottante du nombre que la chaîne taux représente.
  • Lignes 4 et 5: dès lors que valeur est un nombre, on peut faire avec ce nombre des opérations numériques, par exemple un produit ce qui permet d’effectuer le calcul du change de 42 €.

Valeur d’un entier donné en base autre que 10

On a vu que int peut convertir une chaîne de chiffres décimaux en l’entier correspondant :

1
2
x = 10 * int("42") + 1
print(x)
3
421

En réalité, il est possible de convertir un entier représenté en n’importe quelle base allant de 2 à 36, il suffit de le préciser dans l’appel à int :

1
2
3
4
5
x = int("101010", base=2)
print(x)

x = int("AC", base=13)
print(x)
6
7
42
142

Différence entre les fonctions str et repr

Les deux fonctions standard str et repr s’appliquent à n’importe quel objet Python et renvoient toutes les deux une chaîne qui est censée représenter l’objet en question. Un appel de str sur un objet x renvoie une chaîne de caractères qui est censée interpréter x de façon informelle comme dit la documentation officielle. D’un autre côté, repr(x) renvoie une représentation qui est, en principe, une expression Python valide et qui s’évalue en l’objet x.

Les objets Fraction montrent bien la différence entre les deux. Python dispose d’un module standard appelé fractions qui permet d’utiliser des fractions de nombres entiers. Une fraction est définie par une classe Fraction, par exemple la fraction \(\ds\frac{22}{7}\) est définie de la manière suivante :

from fractions import Fraction

f = Fraction(22, 7)

Pour afficher cette fraction, il serait naturel de lire 22/7 et c’est justement ainsi que cet affichage est codé en interne dans le module fractions :

from fractions import Fraction

f = Fraction(22, 7)
print(f)
22/7

Par construction, print(f) affiche exactement la chaîne str(f) et donc ici, cette chaîne se traduit par la chaîne littérale "22/7".

Maintenant, que vaut repr(f) ? Cela ne peut pas être la même chose car l’expression 22/7 n’est pas égale (en Python) à la fraction \(\ds\frac{22}{7}\) puisque 22/7 est un nombre flottant que Python ne connaît en plus qu’approximativement :

from fractions import Fraction

f = Fraction(22, 7)
print(f == 22/7)
False

Regardons ce que vaut repr(f) :

from fractions import Fraction

f = Fraction(22, 7)
print(repr(f))
Fraction(22, 7)

On voit que repr(f) == ss := 'Fraction(22, 7)'. Et en effet, la valeur de f est le résultat de l’évaluation de l’expression entre les apostrophes, à savoir Fraction(22, 7).

Un autre exemple qui peut aider à faire comprendre la nuance est de considérer comment sont affichés les tableaux sous Numpy (qui est une bibliothèque Python massivement utilisée en apprentissage automatique). Observer le code suivant :

import numpy as np

z=np.zeros(5)

print(z)
print(repr(z))
[0. 0. 0. 0. 0.]
array([0., 0., 0., 0., 0.])
  • Lignes 5 et 7 : un tableau Numpy est affiché en donnant la liste de ses éléments, sans virgule séparatrice.
  • Lignes 6 et 8 : repr(z) affiche du code Python exécutable et qui renvoie un objet ayant même valeur que z :
import numpy as np
from numpy import array

z=np.zeros(5)
zz = array([0., 0., 0., 0., 0.])

print(np.array_equal(z, zz))
True

Modifier la terminaison d’affichage par défaut

Par défaut, toute instruction d’affichage avec la fonction print se termine par un saut de ligne. La fonction print possède une « option » modifiant ce comportement par défaut en faisant en sorte que l’affichage soit terminé par une certaine suite de caractères décidée par le programmeur. Cette option consiste à passer un argument à la fonction print, nommé end et que le programmeur affecte au type de terminaison choisie.

Par exemple, supposons que l’on souhaite qu’un affichage soit terminé par deux points d’interrogation :

1
print("lundi", "mardi", end = "??")
2
lundi mardi??
  • Ligne 1 : print a reçu trois arguments. Le dernier est un argument dit nommé : ici, c’est comme si on affectait une variable interne à la fonction print, la variable end. Le fait d’affecter end dans l’appel de print modifie la terminaison d’affichage : les chaînes lundi et mardi sont affichées, séparées par un espace (comme d’habitude) et, immédiatement après, le contenu de la chaîne end est affiché. On notera l’absence d’espace entre mardi et ??.
  • Les deux points d’interrogation ont été placés à la fin de l’affichage grâce à l’option end fournie à print. Toutefois, (rare) l’affichage dans certaines consoles pourra se comporter légèrement différemment.

Attention au comportement suivant :

1
2
3
4
print("lundi", "mardi", end = "??")

print("mercredi")
print("jeudi")
5
6
lundi mardi??mercredi
jeudi
  • Ligne 1 : comme end n’a plus sa valeur par défaut, l’affichage n’est pas terminé par un saut de ligne
  • Ligne 3 : l’affichage est effectué immédiatement après l’affichage provoqué par la ligne 1, en particulier sans rajout de saut de ligne au précédent affichage. En revanche, à la fin de l’affichage mercredi, print rajoute un saut de ligne (c’est le comportement par défaut de print).
  • Ligne 4 : le mot est placé seul sur la ligne à cause du saut de ligne par défaut placé par le print à la ligne 3.

Modifier le séparateur par défaut de print

Par défaut, lorsque print reçoit plusieurs arguments, les affichages correspondants sont séparés par un espace et un seul. On peut modifier ce comportement par défaut en utilisant un argument nommé sep et lui affecter le séparateur que l’on souhaite, par exemple "..." si on veut séparer chaque affichage par trois points successifs :

1
print("lundi", "mardi", "mercredi", sep = "...")
2
lundi...mardi...mercredi
  • Ligne 1 : l’argument nommé sep doit apparaître après la liste des chaînes à afficher.

L’affichage par défaut correspond à l’argument nommé sep = " " autrement dit print("lundi", "mardi") et print("lundi", "mardi", sep = " ") sont équivalents :

print("lundi", "mardi")
print("lundi", "mardi", sep = " ")
lundi mardi
lundi mardi

Il est possible de cumuler la modification du séparateurs sep et de la terminaison de ligne end.

print("lundi", "mercredi", sep = "...", end = "???????")
m = "mardi"
print("lundi", m, "mercredi", sep = "...")
lundi...mercredi???????lundi...mardi...mercredi

Afficher plusieurs objets sans séparation

On souhaite afficher des objets, sans qu’il n’y ait d’espace entre eux à l’affichage, par exemple :

LundiMardiMercredi

Il y a deux façons d’y parvenir avec la fonction print. Soit on modifie le séparateur par défaut, sep, de la fonction print en le plaçant à une chaîne vide, soit on fait de même mais avec le séparateur end de terminaison de ligne :

print("Lundi", "Mardi", "Mercredi", sep="")

print("Lundi", end="")
print("Mardi", end="")
print("Mercredi")
LundiMardiMercredi
LundiMardiMercredi

Affichage parfait sur une même ligne et boucle for

Ce paragraphe s’intéresse à une question très marginale. À examiner en seconde lecture.

Soit à réaliser avec une boucle for l’affichage exact suivant :

0 1 2 3 4 5 6 7 8 9

Un espace doit figurer uniquement entre deux chiffres successifs mais pas en fin de ligne.

Le code suivant semble réaliser l’affichage demandé :

for i in range(10):
    print(i, end = ' ')
0 1 2 3 4 5 6 7 8 9

Même si ce n’est pas visible, l’affichage n’est pas parfaitement conforme puisque lorsque i vaut 9 dans la boucle for, 9 est affiché suivi d’un espace alors qu’un espace est censé séparer un entier de son suivant dans la ligne (9 est le dernier entier de la ligne). On peut d’ailleurs le voir si on ajoute un affichage arbitraire :

for i in range(10):
    print(i, end = ' ')
print("XXXXX")
0 1 2 3 4 5 6 7 8 9 XXXXX

on voit qu’avant le premier X affiché, il y a un espace.

Pour obtenir un affichage parfaitement conforme, il faudrait interrompre la boucle juste avant la dernière étape puis afficher le dernier élément, non suivi d’un espace :

for i in range(9):
    print(i, end = ' ')
print(9)
print("XXXXXX")
0 1 2 3 4 5 6 7 8 9
XXXXXX

Une autre solution est possible, voire préférable serait d’utiliser la méthode join :

print(" ".join(str(i) for i in range(10)))
print("XXXXXX")
0 1 2 3 4 5 6 7 8 9
XXXXXX

Afficher sur la sortie d’erreurs standard

Quand on affiche un message, il apparaît, par défaut, sur ce qu’on appelle la sortie standard, désignée en Python par stdout. En pratique, la sortie standard est une zone de texte spécifique : une console par exemple, l’output d’une cellule Jupyter, etc. Mais parfois, on ne veut pas, que certains messages apparaissent (par exemple des diagnostics, des erreurs d’exécution) et dans ce cas, il existe une possibilité de rediriger les messages de ce type vers une autre « zone » qu’on appelle l”erreur standard désignée sous le terme de stderr.

Pour accéder à l’erreur standard, on utilise le module sys :

1
2
3
4
import sys

print(42)
print(2038, file=sys.stderr)
5
6
42
2038

On constate que les deux messages sont visibles dans la console alors que le 2e était pourtant dirigé vers l’erreur standard. C’est que, par défaut, les affichages sur la sortie standard et l’erreur standard, sont confondus.

L’usage de stderr est intéressant en cas de redirection, en permettant de distinguer les messages d’erreurs de la sortie « normale » :

main.py

1
2
3
4
import sys

print(42)
print(2038, file=sys.stderr)
5
6
$ python3.4 main.py > mon_fichier.txt
2038

et dans ce cas, le contenu de mon_fichier.txt est :

1
42

Afficher sous Jupyter Notebook ou dans une console Python

Comment est réalisé l’affichage de calculs dans une feuille Jupyter Notebook ou une console Python ? Dans une feuille Jupyter Notebook, soit la cellule de code suivante, suivie de l’affichage produit dans une cellule d”output par l’exécution de la cellule :

1
2
3
1 + 1
print(2 + 2)
3 + 3
4
5
4
6
  • Ligne 1 : la valeur de l’expression n’est pas affichée dans la cellule d”output, faute d’appel à la fonction print
  • Lignes 2 et 4 : puisque la fonction print est utilisée, un affichage est visible.
  • Ligne 3 et 5 : malgré l’absence de print, il y a eu un affichage : en effet la dernière instruction de la cellule est une expression (3 + 3) et dans ce cas, sa valeur est affichée.

En revanche, la cellule suivante

1 + 1
3 + 3
x = 42

ne produira aucun affichage. En effet, la dernière instruction n’est pas une expression.

Il reste le cas particulier où l’expression en dernière ligne vaut None : dans ce cas, rien n’est affiché :

1
2
3
L = [40, 41]
print("append : ", L.append(42))
L.append(43)
4
append :  None
  • L’affichage visible provient de la ligne 2 et de l’appel à la fonction print : l’appel L.append(42)) vaut None et c’est ce qui est affiché
  • Comme indiqué précedemment, l’expression L.append(43) vaut toujours None. Pourtant, cette expression, bien que dernière dans la cellule, n’affiche pas None. C’est normal, c’est l’effet recherché par le fonctionnement d’une cellule Jupyter Notebook.

Par ailleurs, l’affichage que peut produire l’expression, disons expr, en dernière ligne de la cellule, affiche en fait repr(expr) et non pas l’affichage habituel. On le voit bien avec une chaîne de caractères :

1
2
3
4
5
6
c = "Orange"

print(c)
print(repr(c))

"Orange"
7
8
9
Orange
'Orange'
'Orange'

La dernière ligne (ligne 9) provenant de l’expression ligne 6 montre le mot entouré d’apostrophes : c’est exactement l’affichage de repr(c), cf. lignes 4 et 8.

Le comportement de l’affichage lorsque Python est utilisé en mode interactif (une « console avec un prompt Python ») est très semblable :

1
2
3
4
5
>>> print("Orange")
Orange
>>> "Orange"
'Orange'
>>>

Décompactage des arguments de la fonction print

Le décompactage des arguments appliqué à la fonction print permet d’obtenir assez simplement des effets d’affichage (le décompactage est le résultat de l’usage de la syntaxe * devant un itérable, voir ci-dessous l’exemple).

Par exemple, on dispose d’une liste L d’entiers et on veut afficher ces entiers juste séparés par un espace :

L = [65, 31, 9, 32, 81, 82, 46, 12]
print(*L)
65 31 9 32 81 82 46 12

En effet, par définition même de la syntaxe de décompactage, l’appel print(*L) est équivalent à :

print(65, 31, 9, 32, 81, 82, 46, 12)

et on sait que, dans ce cas (le cas par défaut), les arguments de la fonctions print sont séparés par un espace.

En utilisant les argument nommés de la fonction print, l’affichage peut être enrichi :

L = [65, 31, 9, 32, 81, 82, 46, 12]
print(*L, sep=', ')
65, 31, 9, 32, 81, 82, 46, 12

La décompactage des arguments s’applique à tout itérable. Par exemple, aux chaînes de caractères, ce qui permet d’afficher facilement les lettres d’un mot :

print(*"ORANGE", sep='.')
O.R.A.N.G.E

Il s’applique aussi aux expressions génératrices, par exemple :

print(*(k for k in range(20) if k%3==0 and k%5!=0))
3 6 9 12 18

Vider le buffer

La sortie de print est bufférisée, c’est-à-dire que le contenu de l’affichage est placé dans une mémoire-tampon et lorsque la mémoire-tampon est pleine ou lorsque les données sont construites, le buffer est vidé et les données sont envoyées dans la zone d’affichage, habituellement la sortie standard (stdout).

La présence de certains caractères vide le buffer, par exemple le caractère saut de ligne ('\n') à pour effet de vider le tampon ce qui provoque l’affichage.

Si une chaîne de caractères doit être affiché et qu’elle ne contient pas de saut de ligne, il peut y avoir un délai d’attente entre l’instruction print et l’affichage réel, comme le montre le code ci-dessous :

1
2
3
4
5
import time

for c in "Python"*3:
    print(c, end='...')
    time.sleep(0.5)
6
P...y...t...h...o...n...P...y...t...h...o...n...P...y...t...h...o...n...

Chaque appel à sleep temporise l’exécution pendant 1/2 seconde. Dans ce code, l’affichage (ligne 6) n’apparaît qu’au bout de 9 secondes, et il apparaît d’un seul coup. En effet, l’appel à la fonction print ne contient pas de saut de ligne (puisque end vaut autre chose, cf. ligne 4), donc le buffer est vidé lorsque le programme se termine.

Pour provoquer le vidage du buffer, on peut utiliser l’option flush=True de la fonction print :

1
2
3
4
5
import time

for c in "Python"*3:
    print(c, end='...', flush=True)
    time.sleep(0.5)
6
P...y...t...h...o...n...P...y...t...h...o...n...P...y...t...h...o...n...

Cette fois l’affichage apparaît progressivement, chaque caractère attend 1/2 seconde avant d’être affiché.

Chaîne analogue à un fichier

On aimerait parfois qu’un code qui capture des données dans un fichier texte soit encore applicable à un code où les données seraient dans une chaîne au lieu d’être dans un fichier.

Par exemple, soit le fichier planetes.txt

Mars
Jupiter
Uranus
Neptune

et le code suivant qui affiche le contenu du fichier planetes.txt :

1
2
3
fichier = open("planetes.txt")
contenu = fichier.read()
print(contenu)

Le contenu du fichier planetes.txt est représenté par la chaîne "Mars\nJupiter\nUranus\nNeptune". On souhaiterait que la partie suivante du code précédent :

1
2
contenu = fichier.read()
print(contenu)

soit encore applicable si on dispose d’une chaîne au lieu du fichier planetes.txt. La solution est donnée par la fonction io.StringIO du module standard io :

1
2
3
4
5
6
7
import io

s = "Mars\nJupiter\nUranus\nNeptune"

fichier = io.StringIO(s)
contenu = fichier.read()
print(contenu)
 8
 9
10
11
Mars
Jupiter
Uranus
Neptune
  • Ligne 5 : la fonction io.StringIO renvoie un objet de type io.StringIO et qui admet les méthodes de fichier.
  • Ligne 6 : on peut lire fichier (qui n’est pas un objet de type file) comme un objet de type file, en particulier avec la méthode read.

Le cas de l’entrée standard

De la même façon, on peut émuler l”entrée standard par une chaîne. Par exemple, voici un code qui effectue la somme de deux entiers donnés sur l’entrée standard :

somme_readlines.py

1
2
3
4
5
6
import sys
stdin = sys.stdin
L = stdin.readlines()
x = L[0]
y = L[1]
print(int(x)+int(y))
7
8
9
42
10
52

Pour transmettre les nombres:

  • soit on appuie sur la touche Entrée après chaque nombre et, à la fin, on transmet via le clavier le caractère de fin de fichier EOF (Ctrl + D sous Linux)
  • (le plus commode) soit on place les deux nombres dans un fichier nombres.txt (un nombre par ligne et sans saut de ligne final) et le code Python (somme_readlines.py) capture les deux entiers par redirection en lançant en ligne de commande :
$ python3 somme.py < nombres.txt

Voici maintenant un code qui conserve l’essentiel de somme_readlines.py mais en lisant les entrées dans une chaîne :

1
2
3
4
5
6
7
8
9
import io

s = "42\n10\n"
stdin = io.StringIO(s)

L = stdin.readlines()
x = L[0]
y = L[1]
print(int(x)+int(y))
10
52
  • Lignes 6-9 : la partie lecture et calcul du code de somme_readlines.py est préservée.

Afficher « dans » une chaîne

La sortie standard (là où on affiche communément) est un fichier comme un autre. On y a accède via sys.stdout et ce fichier possède une méthode write permettant d’afficher (c’est plus ou moins équivalent à print). Voici un exemple :

import sys
cible = sys.stdout

message1 = "Mars\nJupiter\n"
cible.write(message1)

message2 = "Uranus\nNeptune\n"
cible.write(message2)

qui produit, en principe dans une console ou une zone de sortie textuelle, l’affichage suivant

1
2
3
4
Mars
Jupiter
Uranus
Neptune

On peut parfois souhaiter que tous les affichages qu’un code effectue soient envoyés dans une chaîne au lieu de la sortie standard sans pour autant modifier profondément le code. C’est possible en utilisant le module standard stringIO. Voici un exemple équivalent au précédent :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import io
cible = io.StringIO()

message1 = "Mars\nJupiter\n"
cible.write(message1)

message2 = "Uranus\nNeptune\n"
cible.write(message2)

print(cible.getvalue())
11
12
13
14
Mars
Jupiter
Uranus
Neptune
  • Ligne 2 : la chaîne dans laquelle les messages ultérieurs vont être « affichés ».
  • Lignes 5 et 8 : envoie des messages dans la chaîne avec la méthode write.
  • Ligne 10 : pour récupérer le contenu de la chaîne de type StringIO, on appelle sa méthode getvalue ce qui renvoie une chaîne habituelle et que l’on peut afficher.
  • Ligne 11-14 : on récupère bien le contenu envoyé dans la chaîne et comme avec le code précédent.

Utilisation de la fonction print

Il est également possible d’envoyer l’affichage dans une chaîne StringIO au moyen de la fonction print. En effet, la fonction print dispose d’un argument nommé file qui par défaut pointe vers la sortie standard. On peut passer une autre valeur, par exemple une chaîne StringIO, exactement comme à l’exemple précédent. On peut en plus utiliser les autres arguments nommés sep ou end pour formater l’affichage. Voici un exemple :

1
2
3
4
import io
cible = io.StringIO()

print("Mars", "Jupiter", "Uranus", "Neptune", sep='\n', end='', file=cible)

Ce code n’affiche rien car l’affichage a été détournée vers la chaîne cible. Dans cible, les objets à afficher sont séparés par des sauts de ligne, cf. sep='\n'. Noter que pour éviter un saut de ligne non désiré, la fin de ligne par défaut (qui est un saut de ligne) a été écrasée par end=''.

Il est bien sûr possible d’afficher le contenu de cible en récupérant son contenu via la méthode getvalue :

import io
cible = io.StringIO()

print("Mars", "Jupiter", "Uranus", "Neptune", sep='\n', end='', file=cible)

print(cible.getvalue())
Mars
Jupiter
Uranus
Neptune