Votre guide pour la fonction d'impression Python – Real Python

By | août 12, 2019

Cours Python en ligne

Si vous êtes comme la plupart des utilisateurs de Python, y compris moi, vous avez probablement commencé votre aventure avec Python en apprenant: impression(). Cela vous a aidé à écrire votre propre Bonjour le monde bon mot. Vous pouvez l'utiliser pour afficher des messages formatés à l'écran et peut-être trouver des bugs. Mais si vous pensez que tout ce qu'il y a à savoir sur le fonctionnement de Python impression() fonction, alors vous manquez beaucoup!

Continuez votre lecture pour profiter pleinement de cette petite fonction apparemment ennuyeuse et méconnue. Ce tutoriel vous familiarisera avec l’utilisation de Python impression() efficacement. Cependant, préparez-vous pour une plongée profonde en parcourant les sections. Vous pourriez être surpris combien impression() a à offrir!

À la fin de ce tutoriel, vous saurez comment:

  • Évitez les erreurs courantes avec Python impression()
  • Traiter les nouvelles lignes, les encodages de caractères et la mise en mémoire tampon
  • Écrire du texte dans des fichiers
  • Moquer impression() en tests unitaires
  • Construire des interfaces utilisateur avancées dans le terminal

Si vous êtes un débutant complet, vous tirerez le meilleur parti de la lecture de la première partie de ce didacticiel, qui illustre l’essentiel de l’impression en Python. Sinon, n'hésitez pas à sauter cette partie et à vous déplacer comme bon vous semble.

Imprimer en quelques mots

Voyons quelques exemples concrets d’impression en Python. À la fin de cette section, vous connaîtrez tous les moyens possibles pour appeler impression(). Ou, dans le jargon des programmeurs, vous diriez que vous serez familiarisé avec le signature de fonction.

Appel d'impression

L'exemple le plus simple d'utilisation de Python impression() nécessite seulement quelques frappes:

Vous ne transmettez aucun argument, mais vous devez toujours mettre des parenthèses vides à la fin, qui indiquent à Python d’exécuter la fonction plutôt que de simplement y faire référence par son nom.

Cela produira un caractère de nouvelle ligne invisible, ce qui provoquera l'apparition d'une ligne vierge sur votre écran. Tu peux appeler impression() plusieurs fois comme celui-ci pour ajouter de l'espace vertical. C’est comme si tu frappais Entrer sur votre clavier dans un traitement de texte.

UNE caractère de nouvelle ligne est un caractère de contrôle spécial utilisé pour indiquer la fin d'une ligne (EOL). Il n’a généralement pas de représentation visible à l’écran, mais certains éditeurs de texte peuvent afficher de tels caractères non imprimables avec peu de graphiques.

Le mot "caractère" est un peu impropre dans ce cas, car une nouvelle ligne est souvent longue de plus d'un caractère. Par exemple, le système d'exploitation Windows, ainsi que le protocole HTTP, représentent des nouvelles lignes avec une paire de caractères. Parfois, vous devez tenir compte de ces différences pour concevoir des programmes réellement portables.

Pour savoir ce qui constitue une nouvelle ligne dans votre système d’exploitation, utilisez la commande intégrée de Python. os module.

Cela vous dira immédiatement que les fenêtres et DOS représenter la nouvelle ligne comme une séquence de r suivi par n:

>>>

>>> importation os
>>> os.lineep
' r  n'

Sur Unix, Linux, et les versions récentes de macOS, c’est un n personnage:

>>>

>>> importation os
>>> os.lineep
' n'

Le classique Mac OS Xreste cependant fidèle à sa philosophie "penser différemment" en choisissant une autre représentation:

>>>

>>> importation os
>>> os.lineep
' r'

Remarquez comment ces caractères apparaissent dans les littéraux de chaîne. Ils utilisent une syntaxe spéciale avec une barre oblique inverse précédente () pour indiquer le début d'un séquence de caractères d'échappement. De telles séquences permettent de représenter des caractères de contrôle, qui seraient autrement invisibles à l'écran.

La plupart des langages de programmation sont livrés avec un ensemble prédéfini de séquences d'échappement pour des caractères spéciaux tels que:

  • \: barre oblique inverse
  • b: retour arrière
  • t: languette
  • r: retour chariot (CR)
  • n: newline, également appelé saut de ligne (LF)

Les deux dernières rappellent les machines à écrire mécaniques, qui nécessitaient deux commandes distinctes pour insérer une nouvelle ligne. La première commande ramènerait le chariot au début de la ligne actuelle, tandis que la seconde ferait avancer le rouleau jusqu'à la ligne suivante.

En comparant le correspondant Codes de caractères ASCIIvous verrez que le fait de mettre une barre oblique inverse devant un personnage change complètement sa signification. Cependant, tous les personnages ne le permettent pas – seulement les spéciaux.

Pour comparer les codes de caractères ASCII, vous pouvez utiliser le logiciel intégré. ord () une fonction:

>>>

>>> ord('r')
114
>>> ord(' r')
13

Gardez à l'esprit que, pour former une séquence d'échappement correcte, il ne doit pas y avoir d'espace entre la barre oblique inverse et une lettre!

Comme vous venez de le voir, en appelant impression() sans argument aboutit à un ligne blanche, qui est une ligne composée uniquement du caractère newline. Ne confondez pas cela avec un ligne vide, qui ne contient aucun caractère, pas même la nouvelle ligne!

Vous pouvez utiliser les littéraux de chaîne de Python pour visualiser ces deux éléments:

' n'  # Ligne blanche
''    # Ligne vide

Le premier est long d'un caractère, tandis que le second n'a pas de contenu.

Dans un scénario plus courant, vous souhaitez communiquer un message à l’utilisateur final. Il y a plusieurs façons d'y parvenir.

Tout d'abord, vous pouvez passer un littéral de chaîne directement à impression():

>>>

>>> impression('Veuillez patienter pendant le chargement du programme ...')

Cela imprimera le message textuellement sur l'écran.

Littéraux de chaîne en Python peuvent être entre guillemets simples (') ou des guillemets doubles ("). Selon le guide de style officiel de PEP 8, vous devriez en choisir un et continuer à l’utiliser de manière constante. Il n’ya pas de différence, sauf si vous devez imbriquer un dans un autre.

Par exemple, vous ne pouvez pas utiliser de guillemets doubles pour le littéral et également inclure des guillemets doubles à l'intérieur, car cela est ambigu pour l'interpréteur Python:

"Mon livre favori est "Python Des trucs""  # Faux!

Ce que vous voulez faire, c'est mettre le texte qui contient des guillemets doubles entre guillemets simples:

"Mon livre préféré est" Python Tricks ""

Le même truc fonctionnerait dans l'autre sens:

"Mon livre préféré est" Les astuces en python ""

Vous pouvez également utiliser les séquences de caractères d'échappement mentionnées précédemment pour que Python traite ces doubles guillemets internes littéralement comme faisant partie du littéral de chaîne:

"Mon livre favori est  "Astuces de python ""

S'échapper, c'est bien, mais cela peut parfois gêner. Plus précisément, lorsque vous avez besoin que votre chaîne contienne un nombre relativement élevé de caractères de barre oblique inverse sous forme littérale.

Un exemple classique est un chemin de fichier sous Windows:

'C:  Utilisateurs  jdoe'    # Faux!
'C:\Utilisateurs\jdoe '

Notez que chaque caractère de barre oblique inverse doit être échappé avec une autre barre oblique inverse.

Ceci est encore plus évident avec les expressions régulières, qui sont rapidement compliquées à cause de l'utilisation intensive de caractères spéciaux:

'^\w:\\(? :(? :( ?:[^[^[^[^\\]+)? | (? ?:[^[^[^[^\\]+)\\[^[^[^[^\\]+) *) $ '

Heureusement, vous pouvez désactiver complètement l'échappement du personnage à l'aide de littéraux de chaîne brute. Simplement ajouter un r ou R avant la citation d'ouverture, et maintenant vous vous retrouvez avec ceci:

r'C:  Utilisateurs  jdoe'
r'^  w:\(? :(? :( ?:[^[^[^[^\]+)? | (? ?:[^[^[^[^\]+)\[^[^[^[^\]+) *) $ '

C’est beaucoup mieux, n’est-ce pas?

Il existe quelques autres préfixes qui donnent une signification particulière aux littéraux de chaîne en Python, mais vous ne les expliquerez pas ici.

Enfin, vous pouvez définir des littéraux de plusieurs lignes en les entourant '' ' ou "" ", qui sont souvent utilisés comme docstrings.

Voici un exemple:

"" "
Ceci est un exemple
d'une chaîne multiligne
en Python.
"" "

Pour éviter une nouvelle ligne initiale, il suffit de mettre le texte juste après l'ouverture "" ":

"""Ceci est un exemple
d'une chaîne multiligne
en Python.
"" "

Vous pouvez également utiliser une barre oblique inverse pour vous débarrasser de la nouvelle ligne:

"" "
Ceci est un exemple
d'une chaîne multiligne
en Python.
"" " 

Pour supprimer l’indentation d’une chaîne multiligne, vous pouvez tirer parti des fonctions intégrées. habillage de texte module:

>>>

>>> importation habillage de texte
>>> paragraphe = '' '
...                 Ceci est un exemple
...                 d'une chaîne multiligne
...                 en Python.
...                 '' '
...
>>> impression(paragraphe)

                Ceci est un exemple
                d'une chaîne multiligne
                en Python.

>>> impression(habillage de texte.déduction(paragraphe).bande())
Ceci est un exemple
d'une chaîne multiligne
en Python.

Cela prendra soin des paragraphes non-indenting pour vous. Il existe également quelques autres fonctions utiles dans habillage de texte pour l’alignement du texte, vous le trouverez dans un traitement de texte.

Deuxièmement, vous pouvez extraire ce message dans sa propre variable avec un nom explicite pour améliorer la lisibilité et promouvoir la réutilisation du code:

>>>

>>> message = 'Veuillez patienter pendant le chargement du programme ...'
>>> impression(message)

Enfin, vous pouvez passer une expression, telle que la concaténation de chaînes, à évaluer avant d’imprimer le résultat:

>>>

>>> importation os
>>> impression('Bonjour, ' + os.getlogin() + '! Comment vas-tu?')
Bonjour jdoe! Comment vas-tu?

En fait, il existe une douzaine de façons de formater les messages en Python. Je vous encourage vivement à consulter les chaînes de caractères f, introduites dans Python 3.6, car elles offrent la syntaxe la plus concise:

>>>

>>> importation os
>>> impression(F'Bonjour, os.getlogin ()! Comment vas-tu?')

De plus, les f-strings vous éviteront de commettre une erreur commune, qui consiste à oublier de taper des opérandes concaténés. Python est un langage fortement typé, ce qui signifie qu’il ne vous permettra pas de faire ceci:

>>>

>>> 'Mon âge est ' + 42
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    'Mon âge est ' + 42
Erreur-type: ne peut que concaténer str (pas "int") à str

C’est faux car l’ajout de nombres à des chaînes n’a pas de sens. Vous devez d'abord convertir explicitement le nombre en chaîne, afin de les réunir:

>>>

>>> 'Mon âge est ' + str(42)
'Mon âge a 42 ans'

Sauf si vous gérez vous-même de telles erreurs, l'interpréteur Python vous informera d'un problème en affichant une trace.

Comme pour toute fonction, peu importe que vous passiez un littéral, une variable ou une expression. Contrairement à beaucoup d'autres fonctions, cependant, impression() acceptera n'importe quoi, peu importe son type.

Jusqu'ici, vous avez seulement regardé la chaîne, mais qu'en est-il des autres types de données? Essayons des littéraux de différents types intégrés et voyons ce qui en sort:

>>>

>>> impression(42)                            # 
42
>>> impression(3.14)                          # 
3.14
>>> impression(1 + 2j)                        # 
(1 + 2j)
>>> impression(Vrai)                          # 
Vrai
>>> impression([[[[1, 2, 3])                     # 
[1, 2, 3]
>>> impression((1, 2, 3))                     # 
(1, 2, 3)
>>> impression('rouge', 'vert', 'bleu')      # 
'rouge', 'vert', 'bleu'
>>> impression('prénom': 'Alice', 'âge': 42)  # 
'name': 'Alice', 'age': 42
>>> impression('Bonjour')                       # 
Bonjour

Attention au Aucun constante, cependant. Bien qu’il soit utilisé pour indiquer l’absence d’une valeur, celle-ci apparaîtra comme 'Aucun' plutôt qu'une chaîne vide:

Comment impression() savoir comment travailler avec tous ces types différents? Eh bien, la réponse courte est que ce n’est pas le cas. Il appelle implicitement str () dans les coulisses pour taper jeté n'importe quel objet dans une chaîne. Ensuite, il traite les cordes de manière uniforme.

Dans la suite de ce didacticiel, vous apprendrez à utiliser ce mécanisme pour imprimer des types de données personnalisés tels que vos classes.

Ok, vous pouvez maintenant appeler impression() avec un seul argument ou sans aucun argument. Vous savez comment imprimer des messages fixes ou formatés à l'écran. La prochaine sous-section développera un peu la mise en forme des messages.

Pour obtenir le même résultat lors de la génération de langue précédente, vous souhaiterez normalement supprimer les parenthèses entourant le texte:

# Python 2
impression
impression 'S'il vous plaît, attendez...'
impression 'Bonjour, % s! Comment vas-tu?' % os.getlogin()
impression 'Bonjour, % s. Ton âge est %ré' % (prénom, âge)

C'est parce que impression n'était pas une fonction à l'époque, comme vous le verrez dans la section suivante. Notez toutefois que, dans certains cas, les parenthèses en Python sont redondantes. Il ne serait pas nocif de les inclure car ils seraient simplement ignorés. Est-ce que cela signifie que vous devriez utiliser le impression déclaration comme si c'était une fonction? Absolument pas!

Par exemple, les parenthèses entourant une seule expression ou un littéral sont facultatives. Les deux instructions produisent le même résultat dans Python 2:

>>>

>>> # Python 2
>>> impression 'S'il vous plaît, attendez...'
S'il vous plaît, attendez...
>>> impression('S'il vous plaît, attendez...')
S'il vous plaît, attendez...

Les crochets font en réalité partie de l’expression plutôt que du impression déclaration. Si votre expression ne contient qu’un seul élément, c’est comme si vous n’aviez pas inclus les crochets du tout.

D'autre part, mettre des parenthèses autour de plusieurs éléments forme un tuple:

>>>

>>> # Python 2
>>> impression 'Mon nom est', 'John'
Mon nom est John
>>> impression('Mon nom est', 'John')
('Mon nom est John')

C'est une source connue de confusion. En fait, vous obtiendriez également un tuple en ajoutant une virgule de fin au seul élément entouré de parenthèses:

>>>

>>> # Python 2
>>> impression('S'il vous plaît, attendez...')
S'il vous plaît, attendez...
>>> impression('S'il vous plaît, attendez...',)  # Remarquez la virgule
('S'il vous plaît, attendez...',)

L’essentiel est que vous ne devriez pas appeler impression entre parenthèses en Python 2. Bien que, pour être tout à fait exact, vous pouvez contourner ce problème à l’aide d’un __futur__ import, que vous lirez plus en détail dans la section correspondante.

Séparer plusieurs arguments

Vous avez vu impression() appelé sans aucun argument pour produire une ligne vide, puis avec un seul argument pour afficher un message fixe ou formaté.

Cependant, il s’avère que cette fonction peut accepter n’importe quel nombre de arguments de position, y compris zéro, un ou plusieurs arguments. C’est très pratique dans un cas courant de formatage de message, dans lequel vous voudriez joindre quelques éléments.

Les arguments peuvent être passés à une fonction de plusieurs façons. Une façon est de nommer explicitement les arguments lorsque vous appelez la fonction, comme ceci:

>>>

>>> def div(une, b):
...     revenir une / b
...
>>> div(une=3, b=4)
0,75

Comme les arguments peuvent être identifiés par leur nom, leur ordre n’a pas d’importance. Les échanger donnera toujours le même résultat:

>>>

>>> div(b=4, une=3)
0,75

Inversement, les arguments passés sans nom sont identifiés par leur position. C'est pourquoi arguments de position respecter scrupuleusement l'ordre imposé par la signature de la fonction:

>>>

>>> div(3, 4)
0,75
>>> div(4, 3)
1.3333333333333333

impression() permet un nombre arbitraire d'arguments de position grâce à la * args paramètre.

Voyons cet exemple:

>>>

>>> importation os
>>> impression('Mon nom est', os.getlogin(), 'et je suis', 42)
Je m'appelle jdoe et j'ai 42 ans

impression() a concaténé les quatre arguments qui lui ont été transmis, et il a inséré un seul espace entre eux afin que vous ne vous retrouviez pas avec un message écrasé comme 'Je m'appelle et je suis42'.

Notez qu’il s’est également occupé du bon typage en appelant implicitement str () sur chaque argument avant de les rejoindre. Si vous vous souvenez de la sous-section précédente, une concaténation naïve peut facilement entraîner une erreur en raison de types incompatibles:

>>>

>>> impression('Mon âge est: ' + 42)
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    impression('Mon âge est: ' + 42)
Erreur-type: ne peut que concaténer str (pas "int") à str

En plus d'accepter un nombre variable d'arguments de position, impression() définit quatre nommés ou arguments de mots clés, qui sont optionnels car ils ont tous des valeurs par défaut. Vous pouvez consulter leur brève documentation en appelant aide (imprimer) de l'interprète interactif.

Concentrons-nous sur SEP juste pour le moment. Ça signifie séparateur et se voit attribuer un seul espace ('') par défaut. Il détermine la valeur pour joindre des éléments.

Ce doit être soit une chaîne ou Aucun, mais ce dernier a le même effet que l'espace par défaut:

>>>

>>> impression('Bonjour', 'monde', SEP=Aucun)
Bonjour le monde
>>> impression('Bonjour', 'monde', SEP='')
Bonjour le monde
>>> impression('Bonjour', 'monde')
Bonjour le monde

Si vous voulez supprimer complètement le séparateur, vous devez passer une chaîne vide ('') au lieu:

>>>

>>> impression('Bonjour', 'monde', SEP='')
Bonjour le monde

Vous voudrez peut-être impression() pour joindre ses arguments en tant que lignes séparées. Dans ce cas, passez simplement le caractère de nouvelle ligne échappé décrit plus haut:

>>>

>>> impression('Bonjour', 'monde', SEP=' n')
Bonjour
monde

Un exemple plus utile de la SEP paramètre serait l'impression quelque chose comme les chemins de fichiers:

>>>

>>> impression('maison', 'utilisateur', 'documents', SEP='/')
accueil / utilisateur / documents

Rappelez-vous que le séparateur se situe entre les éléments, pas autour d'eux, vous devez donc en rendre compte d'une manière ou d'une autre:

>>>

>>> impression('/maison', 'utilisateur', 'documents', SEP='/')
/ home / utilisateur / documents
>>> impression('', 'maison', 'utilisateur', 'documents', SEP='/')
/ home / utilisateur / documents

Plus précisément, vous pouvez insérer un caractère barre oblique (/) dans le premier argument de position, ou utilisez une chaîne vide comme premier argument pour appliquer la barre oblique.

Un autre exemple intéressant pourrait être l’exportation de données au format CSV (valeurs séparées par des virgules):

>>>

>>> impression(1, «Tours de python», Dan Bader, SEP=',')
1, astuces de python, Dan Bader

Cela ne gèrerait pas les cas extrêmes tels que les virgules, mais cela devrait être le cas pour les cas d'utilisation simples. La ligne ci-dessus apparaît dans la fenêtre de votre terminal. Pour le sauvegarder dans un fichier, vous devez rediriger la sortie. Plus loin dans cette section, vous verrez comment utiliser impression() écrire du texte dans des fichiers directement à partir de Python.

Finalement, le SEP Ce paramètre n’est pas limité à un seul caractère. Vous pouvez joindre des éléments avec des chaînes de n'importe quelle longueur:

>>>

>>> impression('nœud', 'enfant', 'enfant', SEP='->')
noeud -> enfant -> enfant

Dans les sous-sections à venir, vous explorerez les autres arguments de mot-clé du impression() une fonction.

Pour imprimer plusieurs éléments dans Python 2, vous devez placer les parenthèses autour d'eux, comme auparavant:

>>>

>>> # Python 2
>>> importation os
>>> impression 'Mon nom est', os.getlogin(), 'et je suis', 42
Je m'appelle jdoe et j'ai 42 ans

Si vous les gardiez, par contre, vous ne feriez passer qu’un seul tuple à la impression déclaration:

>>>

>>> # Python 2
>>> importation os
>>> impression('Mon nom est', os.getlogin(), 'et je suis', 42)
('Mon nom est', 'jdoe', 'et je suis', 42)

De plus, il n’ya aucun moyen de modifier le séparateur par défaut des éléments joints dans Python 2, aussi une solution de contournement consiste à utiliser une interpolation de chaîne comme ceci:

>>>

>>> # Python 2
>>> importation os
>>> impression 'Mon nom est % s    et je suis %ré' % (os.getlogin(), 42)
Je m'appelle jdoe et j'ai 42 ans

C’était le moyen par défaut de formater les chaînes jusqu’à ce que le .format() La méthode a été rétroportée depuis Python 3.

Prévenir les sauts de ligne

Parfois, vous ne souhaitez pas mettre fin à votre message avec une fin de ligne, de sorte que les appels ultérieurs à impression() continuera sur la même ligne. Les exemples classiques incluent la mise à jour de la progression d'une opération de longue durée ou l'invite de l'utilisateur à entrer. Dans ce dernier cas, vous voulez que l'utilisateur tape la réponse sur la même ligne:

Es-tu sûr de vouloir faire ça? [y/n] y

De nombreux langages de programmation exposent des fonctions similaires à impression() via leurs bibliothèques standard, mais ils vous laissent décider d’ajouter ou non une nouvelle ligne. Par exemple, en Java et en C #, vous avez deux fonctions distinctes, alors que dans d’autres langages, vous devez ajouter explicitement n à la fin d'un littéral de chaîne.

Voici quelques exemples de syntaxe dans de tels langages:

La langue Exemple
Perl print "bonjour le monde n"
C printf ("bonjour le monde n");
C ++ std :: cout << "bonjour le monde" << std :: endl;

En revanche, Python impression() la fonction ajoute toujours n sans demander, car c’est ce que vous voulez dans la plupart des cas. Pour le désactiver, vous pouvez utiliser un autre argument de mot clé, fin, qui dicte avec quoi terminer la ligne.

En termes de sémantique, le fin paramètre est presque identique à la SEP celui que vous avez vu plus tôt:

  • Ce doit être une chaîne ou Aucun.
  • Cela peut être arbitrairement long.
  • Il a une valeur par défaut de ' n'.
  • Si égal à Aucun, cela aura le même effet que la valeur par défaut.
  • Si égal à une chaîne vide (''), il supprimera la nouvelle ligne.

Maintenant, vous comprenez ce qui se passe sous le capot lorsque vous appelez impression() sans argument. Etant donné que vous ne fournissez aucun argument de position à la fonction, il n’ya rien à joindre, le séparateur par défaut n’est donc pas utilisé du tout. Cependant, la valeur par défaut de fin s'applique toujours, et une ligne vide apparaît.

Pour désactiver la nouvelle ligne, vous devez spécifier une chaîne vide à travers le fin argument de mot clé:

impression('Vérification de l'intégrité du fichier ...', fin='')
# (...)
impression('D'accord')

Même si ce sont deux séparés impression() d’appels, qui peuvent s’exécuter très longtemps, vous ne verrez éventuellement plus qu’une seule ligne. Tout d’abord, cela ressemblera à ceci:

Vérification de l'intégrité du fichier ...

Cependant, après le deuxième appel à impression(), la même ligne apparaîtra à l'écran comme suit:

Vérification de l'intégrité du fichier ... ok

Comme avec SEP, vous pouvez utiliser fin pour joindre des éléments individuels en une grande quantité de texte avec un séparateur personnalisé. Au lieu de joindre plusieurs arguments, il ajoute le texte de chaque appel de fonction à la même ligne:

impression("La première phrase", fin='. ')
impression("La deuxième phrase", fin='. ')
impression('La dernière phrase.')

Ces trois instructions produiront une seule ligne de texte:

La première phrase La deuxième phrase. La dernière phrase.

Vous pouvez mélanger les deux arguments de mots clés:

impression('Mercure', 'Vénus', 'Terre', SEP=',', fin=',')
impression('Mars', 'Jupiter', 'Saturne', SEP=',', fin=',')
impression('Uranus', 'Neptune', 'Pluton', SEP=',')

Non seulement vous obtenez une seule ligne de texte, mais tous les éléments sont séparés par une virgule:

Mercure, Vénus, Terre, Mars, Jupiter, Saturne, Uranus, Neptune, Pluton

Rien ne vous empêche d’utiliser le caractère de nouvelle ligne avec un rembourrage supplémentaire:

impression('Imprimer en quelques mots', fin=' n    * ')
impression("Appel d'impression", fin=' n    * ')
impression('Séparer plusieurs arguments', fin=' n    * ')
impression("Prévenir les sauts de ligne")

Il imprimerait le texte suivant:

Imprimer en quelques mots
 * Appel Imprimer
 * Séparer plusieurs arguments
 * Prévenir les sauts de ligne

Comme vous pouvez le voir, le fin L'argument de mot clé acceptera des chaînes arbitraires.

Vous vous familiarisez davantage avec l’impression en Python, mais il reste encore beaucoup d’informations utiles. Dans la prochaine sous-section, vous apprendrez à intercepter et à rediriger le impression() sortie de la fonction.

Pour empêcher un saut de ligne dans Python 2, vous devez ajouter une virgule à la fin de l'expression:

Cependant, ce n’est pas idéal car cela ajoute aussi un espace indésirable, ce qui se traduirait par fin = '' au lieu de fin = '' en Python 3. Vous pouvez tester cela avec l'extrait de code suivant:

impression 'AVANT'
impression 'Bonjour',
impression 'APRÈS'

Notez qu’il y a un espace entre les mots Bonjour et APRÈS:

Pour obtenir le résultat attendu, vous devez utiliser l’une des astuces expliquées plus loin, à savoir importer les impression() fonction de __futur__ ou retomber sur le sys module:

importation sys
impression 'AVANT'
sys.stdout.écrire('Bonjour')
impression 'APRÈS' 

Ceci imprimera la sortie correcte sans espace supplémentaire:

En utilisant le sys module vous donne le contrôle sur ce qui est imprimé sur la sortie standard, le code devient un peu plus encombré.

Impression dans un fichier

Croyez-le ou non, impression() ne sait pas comment convertir des messages en texte sur votre écran et, franchement, ce n’est pas nécessaire. C’est un travail pour les couches de code de niveau inférieur, qui comprennent les octets et savent comment les déplacer.

impression() est une abstraction sur ces couches, fournissant une interface pratique qui délègue simplement l’impression réelle à un flux ou objet de type fichier. Un flux peut être n'importe quel fichier sur votre disque, un socket réseau ou peut-être un tampon en mémoire.

En plus de cela, il existe trois flux standard fournis par le système d'exploitation:

  1. stdin: entrée standard
  2. stdout: sortie standard
  3. stderr: erreur standard

Sortie standard Voici ce que vous voyez dans le terminal lorsque vous exécutez divers programmes en ligne de commande, y compris vos propres scripts Python:

$ chat bonjour.py 
print ('Ceci apparaîtra sur stdout')
$ python bonjour.py
Cela apparaîtra sur stdout

Sauf instruction contraire, impression() va écrire par défaut sur la sortie standard. Cependant, vous pouvez demander à votre système d’exploitation de remplacer temporairement stdout pour un flux de fichiers, de sorte que toute sortie finisse dans ce fichier plutôt que dans l'écran:

$ python bonjour.py> fichier.txt
$ cat fichier.txt
Cela apparaîtra sur stdout

C'est ce qu'on appelle la redirection de flux.

L'erreur type est similaire à stdout en ce sens qu'il apparaît également à l'écran. Néanmoins, il s’agit d’un flux séparé, destiné à consigner les messages d’erreur pour les diagnostics. En redirigeant l'un d'eux ou les deux, vous pouvez garder les choses propres.

Certains programmes utilisent des couleurs différentes pour distinguer les messages imprimés sur stdout et stderr:

La sortie d'un programme exécuté dans PyCharm
Exécuter la fenêtre d'outils dans PyCharm

Alors que les deux stdout et stderr sont en écriture seule, stdin est en lecture seule. Vous pouvez considérer l’entrée standard comme votre clavier, mais comme pour les deux autres, vous pouvez permuter stdin pour un fichier à partir duquel lire des données.

En Python, vous pouvez accéder à tous les flux standard via l’interface intégrée. sys module:

>>>

>>> importation sys
>>> sys.stdin
<_io.TextIOWrapper name = ''mode =' r 'encoding =' UTF-8 '>
>>> sys.stdin.fileno()
0
>>> sys.stdout
<_io.TextIOWrapper name = ''mode =' w 'encoding =' UTF-8 '>
>>> sys.stdout.fileno()
1
>>> sys.stderr
<_io.TextIOWrapper name = ''mode =' w 'encoding =' UTF-8 '>
>>> sys.stderr.fileno()
2

Comme vous pouvez le constater, ces valeurs prédéfinies ressemblent à des objets de type fichier avec mode et codage attributs ainsi que .lis() et .écrire() méthodes parmi tant d’autres.

Par défaut, impression() est lié à sys.stdout à travers ses fichier argument, mais vous pouvez changer cela. Utilisez cet argument de mot clé pour indiquer un fichier ouvert en mode écriture ou ajout, afin que les messages y soient directement envoyés:

avec ouvrir('file.txt', mode='w') comme file_object:
    impression('hello world', fichier=file_object)

This will make your code immune to stream redirection at the operating system level, which might or might not be desired.

For more information on working with files in Python, you can check out Reading and Writing Files in Python (Guide).

Notez que print() has no control over character encoding. It’s the stream’s responsibility to encode received Unicode strings into bytes correctly. In most cases, you won’t set the encoding yourself, because the default UTF-8 is what you want. If you really need to, perhaps for legacy systems, you can use the codage argument of open():

avec ouvrir('file.txt', mode='w', codage='iso-8859-1') comme file_object:
    impression('über naïve café', fichier=file_object)

Instead of a real file existing somewhere in your file system, you can provide a fake one, which would reside in your computer’s memory. You’ll use this technique later for mocking print() in unit tests:

>>>

>>> importation io
>>> fake_file = io.StringIO()
>>> impression('hello world', fichier=fake_file)
>>> fake_file.getvalue()
'hello worldn'

If you got to this point, then you’re left with only one keyword argument in print(), which you’ll see in the next subsection. It’s probably the least used of them all. Nevertheless, there are times when it’s absolutely necessary.

There’s a special syntax in Python 2 for replacing the default sys.stdout with a custom file in the impression statement:

avec ouvrir('file.txt', mode='w') comme file_object:
    impression >> file_object, 'hello world'

Because strings and bytes are represented with the same str type in Python 2, the impression statement can handle binary data just fine:

avec ouvrir('file.dat', mode='wb') comme file_object:
    impression >> file_object, 'x41x0a'

Although, there’s a problem with character encoding. le open() function in Python 2 lacks the codage parameter, which would often result in the dreadful UnicodeEncodeError:

>>>

>>> avec ouvrir('file.txt', mode='w') comme file_object:
...     unicode_text = vous'xfcber naxefve cafxe9'
...     impression >> file_object, unicode_text
... 
Traceback (most recent call last):
  Fichier "", line 3, dans 
UnicodeEncodeError: 'ascii' codec can't encode character u'xfc'...

Notice how non-Latin characters must be escaped in both Unicode and string literals to avoid a syntax error. Jetez un oeil à cet exemple:

unicode_literal = vous'xfcber naxefve cafxe9'
string_literal = 'xc3xbcber naxc3xafve cafxc3xa9'

Alternatively, you could specify source code encoding according to PEP 263 at the top of the file, but that wasn’t the best practice due to portability issues:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

unescaped_unicode_literal = vous'über naïve café'
unescaped_string_literal = 'über naïve café'

Your best bet is to encode the Unicode string just before printing it. You can do this manually:

avec ouvrir('file.txt', mode='w') comme file_object:
    unicode_text = vous'xfcber naxefve cafxe9'
    encoded_text = unicode_text.encoder('utf-8')
    impression >> file_object, encoded_text

However, a more convenient option is to use the built-in codecs module:

importation codecs

avec codecs.ouvrir('file.txt', 'w', codage='utf-8') comme file_object:
    unicode_text = vous'xfcber naxefve cafxe9'
    impression >> file_object, unicode_text

It’ll take care of making appropriate conversions when you need to read or write files.

Buffering Print Calls

In the previous subsection, you learned that print() delegates printing to a file-like object such as sys.stdout. Some streams, however, buffer certain I/O operations to enhance performance, which can get in the way. Let’s take a look at an example.

Imagine you were writing a countdown timer, which should append the remaining time to the same line every second:

Your first attempt may look something like this:

importation temps

num_seconds = 3
pour compte à rebours dans renversé(intervalle(num_seconds + 1)):
    si compte à rebours > 0:
        impression(compte à rebours, fin='...')
        temps.sommeil(1)
    autre:
        impression('Go!')

As long as the compte à rebours variable is greater than zero, the code keeps appending text without a trailing newline and then goes to sleep for one second. Finally, when the countdown is finished, it prints Go! and terminates the line.

Unexpectedly, instead of counting down every second, the program idles wastefully for three seconds, and then suddenly prints the entire line at once:

Terminal with buffered output

That’s because the operating system buffers subsequent writes to the standard output in this case. You need to know that there are three kinds of streams with respect to buffering:

  1. Unbuffered
  2. Line-buffered
  3. Block-buffered

Unbuffered is self-explanatory, that is, no buffering is taking place, and all writes have immediate effect. UNE line-buffered stream waits before firing any I/O calls until a line break appears somewhere in the buffer, whereas a block-buffered one simply allows the buffer to fill up to a certain size regardless of its content. Standard output is both line-buffered et block-buffered, depending on which event comes first.

Buffering helps to reduce the number of expensive I/O calls. Think about sending messages over a high-latency network, for example. When you connect to a remote server to execute commands over the SSH protocol, each of your keystrokes may actually produce an individual data packet, which is orders of magnitude bigger than its payload. What an overhead! It would make sense to wait until at least a few characters are typed and then send them together. That’s where buffering steps in.

On the other hand, buffering can sometimes have undesired effects as you just saw with the countdown example. To fix it, you can simply tell print() to forcefully flush the stream without waiting for a newline character in the buffer using its affleurer flag:

impression(compte à rebours, fin='...', affleurer=Vrai)

C'est tout. Your countdown should work as expected now, but don’t take my word for it. Go ahead and test it to see the difference.

Toutes nos félicitations! At this point, you’ve seen examples of calling print() that cover all of its parameters. You know their purpose and when to use them. Understanding the signature is only the beginning, however. In the upcoming sections, you’ll see why.

There isn’t an easy way to flush the stream in Python 2, because the impression statement doesn’t allow for it by itself. You need to get a handle of its lower-level layer, which is the standard output, and call it directly:

importation temps
importation sys

num_seconds = 3
pour compte à rebours dans renversé(intervalle(num_seconds + 1)):
    si compte à rebours > 0:
        sys.stdout.écrire('%s...' % compte à rebours)
        sys.stdout.affleurer()
        temps.sommeil(1)
    autre:
        impression 'Go!'

Alternatively, you could disable buffering of the standard streams either by providing the -u flag to the Python interpreter or by setting up the PYTHONUNBUFFERED environment variable:

$ python2 -u countdown.py
$ PYTHONUNBUFFERED=1 python2 countdown.py

Notez que print() was backported to Python 2 and made available through the __future__ module. Unfortunately, it doesn’t come with the affleurer parameter:

>>>

>>> de __future__ importation print_function
>>> Aidez-moi(impression)
Help on built-in function print in module __builtin__:

print(...)
                print(value, ..., sep=' ', end='n', file=sys.stdout)

What you’re seeing here is a docstring du print() une fonction. You can display docstrings of various objects in Python using the built-in help() une fonction.

Printing Custom Data Types

Up until now, you only dealt with built-in data types such as strings and numbers, but you’ll often want to print your own abstract data types. Let’s have a look at different ways of defining them.

For simple objects without any logic, whose purpose is to carry data, you’ll typically take advantage of namedtuple, which is available in the standard library. Named tuples have a neat textual representation out of the box:

>>>

>>> de collections importation namedtuple
>>> La personne = namedtuple('Person', 'name age')
>>> jdoe = La personne('John Doe', 42)
>>> impression(jdoe)
Person(name='John Doe', age=42)

That’s great as long as holding data is enough, but in order to add behaviors to the La personne type, you’ll eventually need to define a class. Jetez un oeil à cet exemple:

classe La personne:
    def __init__(soi, prénom, âge):
        soi.prénom, soi.âge = prénom, âge

If you now create an instance of the La personne class and try to print it, you’ll get this bizarre output, which is quite different from the equivalent namedtuple:

>>>

>>> jdoe = La personne('John Doe', 42)
>>> impression(jdoe)
<__main__.Person object at 0x7fcac3fed1d0>

It’s the default representation of objects, which comprises their address in memory, the corresponding class name and a module in which they were defined. You’ll fix that in a bit, but just for the record, as a quick workaround you could combine namedtuple and a custom class through inheritance:

de collections importation namedtuple

classe La personne(namedtuple('Person', 'name age')):
    passer

Your La personne class has just become a specialized kind of namedtuple with two attributes, which you can customize.

That’s better than a plain namedtuple, because not only do you get printing right for free, but you can also add custom methods and properties to the class. However, it solves one problem while introducing another. Remember that tuples, including named tuples, are immutable in Python, so they can’t change their values once created.

It’s true that designing immutable data types is desirable, but in many cases, you’ll want them to allow for change, so you’re back with regular classes again.

From earlier subsections, you already know that print() implicitly calls the built-in str() function to convert its positional arguments into strings. Indeed, calling str() manually against an instance of the regular La personne class yields the same result as printing it:

>>>

>>> jdoe = La personne('John Doe', 42)
>>> str(jdoe)
'<__main__.Person object at 0x7fcac3fed1d0>'

str(), in turn, looks for one of two magic methods within the class body, which you typically implement. If it doesn’t find one, then it falls back to the ugly default representation. Those magic methods are, in order of search:

  1. def __str__(self)
  2. def __repr__(self)

The first one is recommended to return a short, human-readable text, which includes information from the most relevant attributes. After all, you don’t want to expose sensitive data, such as user passwords, when printing objects.

However, the other one should provide complete information about an object, to allow for restoring its state from a string. Ideally, it should return valid Python code, so that you can pass it directly to eval():

>>>

>>> repr(jdoe)
"Person(name='John Doe', age=42)"
>>> type(eval(repr(jdoe)))

Notice the use of another built-in function, repr(), which always tries to call .__repr__() in an object, but falls back to the default representation if it doesn’t find that method.

Python gives you a lot of freedom when it comes to defining your own data types if none of the built-in ones meet your needs. Some of them, such as named tuples and data classes, offer string representations that look good without requiring any work on your part. Still, for the most flexibility, you’ll have to define a class and override its magic methods described above.

The semantics of .__str__() et .__repr__() didn’t change since Python 2, but you must remember that strings were nothing more than glorified byte arrays back then. To convert your objects into proper Unicode, which was a separate data type, you’d have to provide yet another magic method: .__unicode__().

Here’s an example of the same Utilisateur class in Python 2:

classe Utilisateur(objet):
    def __init__(soi, s'identifier, mot de passe):
        soi.s'identifier = s'identifier
        soi.mot de passe = mot de passe

    def __unicode__(soi):
        revenir soi.s'identifier

    def __str__(soi):
        revenir unicode(soi).encoder('utf-8')

    def __repr__(soi):
        utilisateur = vous"User('%s', '%s')" % (soi.s'identifier, soi.mot de passe)
        revenir utilisateur.encoder('unicode_escape')

As you can see, this implementation delegates some work to avoid duplication by calling the built-in unicode() function on itself.

Tous les deux .__str__() et .__repr__() methods must return strings, so they encode Unicode characters into specific byte representations called jeux de caractères. UTF-8 is the most widespread and safest encoding, while unicode_escape is a special constant to express funky characters, such as é, as escape sequences in plain ASCII, such as xe9.

le impression statement is looking for the magic .__str__() method in the class, so the chosen charset must correspond to the one used by the terminal. For example, default encoding in DOS and Windows is CP 852 rather than UTF-8, so running this can result in a UnicodeEncodeError or even garbled output:

>>>

>>> utilisateur = Utilisateur(vous'u043du0438u043au0438u0442u0430', vous's3cret')
>>> impression utilisateur
đŻđŞđ║đŞĐéđ░

However, if you ran the same code on a system with UTF-8 encoding, then you’d get the proper spelling of a popular Russian name:

>>>

>>> utilisateur = Utilisateur(vous'u043du0438u043au0438u0442u0430', vous's3cret')
>>> impression utilisateur
никита

It’s recommended to convert strings to Unicode as early as possible, for example, when you’re reading data from a file, and use it consistently everywhere in your code. At the same time, you should encode Unicode back to the chosen character set right before presenting it to the user.

It seems as if you have more control over string representation of objects in Python 2 because there’s no magic .__unicode__() method in Python 3 anymore. You may be asking yourself if it’s possible to convert an object to its byte string representation rather than a Unicode string in Python 3. It’s possible, with a special .__bytes__() method that does just that:

>>>

>>> classe Utilisateur(objet):
...     def __init__(soi, s'identifier, mot de passe):
...         soi.s'identifier = s'identifier
...         soi.mot de passe = mot de passe
...     
...     def __bytes__(soi):  # Python 3
...         revenir soi.s'identifier.encoder('utf-8')
...
>>> utilisateur = Utilisateur(vous'u043du0438u043au0438u0442u0430', vous's3cret')
>>> octets(utilisateur)
b'xd0xbdxd0xb8xd0xbaxd0xb8xd1x82xd0xb0'

Using the built-in bytes() function on an instance delegates the call to its __bytes__() method defined in the corresponding class.

Understanding Python Print

You know Comment utiliser print() quite well at this point, but knowing quoi it is will allow you to use it even more effectively and consciously. After reading this section, you’ll understand how printing in Python has improved over the years.

You’ve seen that print() is a function in Python 3. More specifically, it’s a built-in function, which means that you don’t need to import it from anywhere:

>>>

>>> impression

It’s always available in the global namespace so that you can call it directly, but you can also access it through a module from the standard library:

>>>

>>> importation builtins
>>> builtins.impression

This way, you can avoid name collisions with custom functions. Let’s say you wanted to redéfinir print() so that it doesn’t append a trailing newline. At the same time, you wanted to rename the original function to something like println():

>>>

>>> importation builtins
>>> println = builtins.impression
>>> def impression(*args, **kwargs):
...     builtins.impression(*args, **kwargs, fin='')
...
>>> println('hello')
Bonjour
>>> impression('hello n')
Bonjour

Now you have two separate printing functions just like in the Java programming language. You’ll define custom print() functions in the mocking section later as well. Also, note that you wouldn’t be able to overwrite print() in the first place if it wasn’t a function.

D'autre part, print() isn’t a function in the mathematical sense, because it doesn’t return any meaningful value other than the implicit Aucun:

>>>

>>> valeur = impression('hello world')
Bonjour le monde
>>> impression(valeur)
Aucun

Such functions are, in fact, procedures or subroutines that you call to achieve some kind of side-effect, which ultimately is a change of a global state. Dans le cas de print(), that side-effect is showing a message on the standard output or writing to a file.

Because print() is a function, it has a well-defined signature with known attributes. You can quickly find its documentation using the editor of your choice, without having to remember some weird syntax for performing a certain task.

Besides, functions are easier to étendre. Adding a new feature to a function is as easy as adding another keyword argument, whereas changing the language to support that new feature is much more cumbersome. Think of stream redirection or buffer flushing, for example.

Another benefit of print() being a function is composabilité. Functions are so-called first-class objects or first-class citizens in Python, which is a fancy way of saying they’re values just like strings or numbers. This way, you can assign a function to a variable, pass it to another function, or even return one from another. print() isn’t different in this regard. For instance, you can take advantage of it for dependency injection:

def Télécharger(url, bûche=impression):
    bûche(F'Downloading url')
    # ...

def custom_print(*args):
    passer  # Do not print anything

Télécharger('/js/app.js', bûche=custom_print)

Here, the bûche parameter lets you inject a callback function, which defaults to print() but can be any callable. In this example, printing is completely disabled by substituting print() with a dummy function that does nothing.

Composition allows you to combine a few functions into a new one of the same kind. Let’s see this in action by specifying a custom error() function that prints to the standard error stream and prefixes all messages with a given log level:

>>>

>>> de functools importation partiel
>>> importation sys
>>> réorienter = lambda une fonction, courant: partiel(une fonction, fichier=courant)
>>> préfixe = lambda une fonction, préfixe: partiel(une fonction, préfixe)
>>> Erreur = préfixe(réorienter(impression, sys.stderr), '[ERROR]')
>>> Erreur('Something went wrong')
[ERROR]    Something went wrong

This custom function uses partial functions to achieve the desired effect. It’s an advanced concept borrowed from the functional programming paradigm, so you don’t need to go too deep into that topic for now. However, if you’re interested in this topic, I recommend taking a look at the functools module.

Unlike statements, functions are values. That means you can mix them with expressions, in particular, lambda expressions. Instead of defining a full-blown function to replace print() with, you can make an anonymous lambda expression that calls it:

>>>

>>> Télécharger('/js/app.js', lambda msg: impression('[INFO]', msg))
[INFO]    Downloading /js/app.js

However, because a lambda expression is defined in place, there’s no way of referring to it elsewhere in the code.

Another kind of expression is a ternary conditional expression:

>>>

>>> utilisateur = 'jdoe'
>>> impression('Hi!') si utilisateur est Aucun autre impression(F'Hi, user.')
Hi, jdoe.

Python has both conditional statements and conditional expressions. The latter is evaluated to a single value that can be assigned to a variable or passed to a function. In the example above, you’re interested in the side-effect rather than the value, which evaluates to Aucun, so you simply ignore it.

As you can see, functions allow for an elegant and extensible solution, which is consistent with the rest of the language. In the next subsection, you’ll discover how not having print() as a function caused a lot of headaches.

UNE déclaration is an instruction that may evoke a side-effect when executed but never evaluates to a value. In other words, you wouldn’t be able to print a statement or assign it to a variable like this:

résultat = impression 'hello world'

That’s a syntax error in Python 2.

Here are a few more examples of statements in Python:

  • assignment: =
  • conditional: si
  • loop: tandis que
  • affirmation: affirmer

Statements are usually comprised of reserved keywords such as si, pour, ou impression that have fixed meaning in the language. You can’t use them to name your variables or other symbols. That’s why redefining or mocking the impression statement isn’t possible in Python 2. You’re stuck with what you get.

Furthermore, you can’t print from anonymous functions, because statements aren’t accepted in lambda expressions:

>>>

>>> lambda: impression 'hello world'
  Fichier "", line 1
    lambda: impression 'hello world'
                ^
SyntaxError: Syntaxe invalide

The syntax of the impression statement is ambiguous. Sometimes you can add parentheses around the message, and they’re completely optional:

>>>

>>> impression 'Please wait...'
S'il vous plaît, attendez...
>>> impression('Please wait...')
S'il vous plaît, attendez...

At other times they change how the message is printed:

>>>

>>> impression 'My name is', 'John'
My name is John
>>> impression('My name is', 'John')
('My name is', 'John')

String concatenation can raise a TypeError due to incompatible types, which you have to handle manually, for example:

>>>

>>> valeurs = [[[['jdoe', 'is', 42, 'years old']
>>> impression ' '.joindre(carte(str, valeurs))
jdoe is 42 years old

Compare this with similar code in Python 3, which leverages sequence unpacking:

>>>

>>> valeurs = [[[['jdoe', 'is', 42, 'years old']
>>> impression(*valeurs)  # Python 3
jdoe is 42 years old

There aren’t any keyword arguments for common tasks such as flushing the buffer or stream redirection. You need to remember the quirky syntax instead. Even the built-in help() function isn’t that helpful with regards to the impression statement:

>>>

>>> Aidez-moi(impression)
  Fichier "", line 1
    Aidez-moi(impression)
             ^
SyntaxError: Syntaxe invalide

Trailing newline removal doesn’t work quite right, because it adds an unwanted space. You can’t compose multiple impression statements together, and, on top of that, you have to be extra diligent about character encoding.

The list of problems goes on and on. If you’re curious, you can jump back to the previous section and look for more detailed explanations of the syntax in Python 2.

However, you can mitigate some of those problems with a much simpler approach. It turns out the print() function was backported to ease the migration to Python 3. You can import it from a special __future__ module, which exposes a selection of language features released in later Python versions.

To enable the print() function in Python 2, you need to add this import statement at the beginning of your source code:

de __future__ importation print_function

From now on the impression statement is no longer available, but you have the print() function at your disposal. Note that it isn’t the same function like the one in Python 3, because it’s missing the affleurer keyword argument, but the rest of the arguments are the same.

Other than that, it doesn’t spare you from managing character encodings properly.

Here’s an example of calling the print() function in Python 2:

>>>

>>> de __future__ importation print_function
>>> importation sys
>>> impression('I am a function in Python', sys.version_info.Majeur)
I am a function in Python 2

You now have an idea of how printing in Python evolved and, most importantly, understand why these backward-incompatible changes were necessary. Knowing this will surely help you become a better Python programmer.

Printing With Style

If you thought that printing was only about lighting pixels up on the screen, then technically you’d be right. However, there are ways to make it look cool. In this section, you’ll find out how to format complex data structures, add colors and other decorations, build interfaces, use animation, and even play sounds with text!

Pretty-Printing Nested Data Structures

Computer languages allow you to represent data as well as executable code in a structured way. Unlike Python, however, most languages give you a lot of freedom in using whitespace and formatting. This can be useful, for example in compression, but it sometimes leads to less readable code.

Pretty-printing is about making a piece of data or code look more appealing to the human eye so that it can be understood more easily. This is done by indenting certain lines, inserting newlines, reordering elements, and so forth.

Python comes with the pprint module in its standard library, which will help you in pretty-printing large data structures that don’t fit on a single line. Because it prints in a more human-friendly way, many popular REPL tools, including JupyterLab and IPython, use it by default in place of the regular print() une fonction.

If you don’t care about not having access to the original print() function, then you can replace it with pprint() in your code using import renaming:

>>>

>>> de pprint importation pprint comme impression
>>> impression

Personally, I like to have both functions at my fingertips, so I’d rather use something like pp as a short alias:

de pprint importation pprint comme pp

At first glance, there’s hardly any difference between the two functions, and in some cases there’s virtually none:

>>>

>>> impression(42)
42
>>> pp(42)
42
>>> impression('hello')
Bonjour
>>> pp('hello')
'hello'  # Did you spot the difference?

That’s because pprint() appels repr() instead of the usual str() for type casting, so that you may evaluate its output as Python code if you want to. The differences become apparent as you start feeding it more complex data structures:

>>>

>>> Les données = 'powers': [[[[X**dix pour X dans intervalle(dix)]
>>> pp(Les données)
'powers':[0[0[0[0
                                                1,
                                                1024,
                                                59049,
                                                1048576,
                                                9765625,
                                                60466176,
                                                282475249,
                                                1073741824,
                                                3486784401]

The function applies reasonable formatting to improve readability, but you can customize it even further with a couple of parameters. For example, you may limit a deeply nested hierarchy by showing an ellipsis below a given level:

>>>

>>> villes = 'USA': 'Texas': 'Dallas': [[[['Irving']
>>> pp(villes, profondeur=3)
'USA': 'Texas': 'Dallas': [...]

The ordinary print() also uses ellipses but for displaying recursive data structures, which form a cycle, to avoid stack overflow error:

>>>

>>> articles = [[[[1, 2, 3]
>>> articles.ajouter(articles)
>>> impression(articles)
[123[123[123[123[...]]

However, pprint() is more explicit about it by including the unique identity of a self-referencing object:

>>>

>>> pp(articles)
[123[123[123[123]
>>> identifiant(articles)
140635757287688

The last element in the list is the same object as the entire list.

pprint() automatically sorts dictionary keys for you before printing, which allows for consistent comparison. When you’re comparing strings, you often don’t care about a particular order of serialized attributes. Anyways, it’s always best to compare actual dictionaries before serialization.

Dictionaries often represent JSON data, which is widely used on the Internet. To correctly serialize a dictionary into a valid JSON-formatted string, you can take advantage of the JSON module. It too has pretty-printing capabilities:

>>>

>>> importation JSON
>>> Les données = 'username': 'jdoe', 'password': 's3cret'
>>> laid = JSON.décharges(Les données)
>>> jolie = JSON.décharges(Les données, retrait=4, sort_keys=Vrai)
>>> impression(laid)
"username": "jdoe", "password": "s3cret"
>>> impression(jolie)

                "password": "s3cret",
                "username": "jdoe"

Notice, however, that you need to handle printing yourself, because it’s not something you’d typically want to do. Similarly, the pprint module has an additional pformat() function that returns a string, in case you had to do something other than printing it.

Surprisingly, the signature of pprint() is nothing like the print() function’s one. You can’t even pass more than one positional argument, which shows how much it focuses on printing data structures.

Adding Colors With ANSI Escape Sequences

As personal computers got more sophisticated, they had better graphics and could display more colors. However, different vendors had their own idea about the API design for controlling it. That changed a few decades ago when people at the American National Standards Institute decided to unify it by defining ANSI escape codes.

Most of today’s terminal emulators support this standard to some degree. Until recently, the Windows operating system was a notable exception. Therefore, if you want the best portability, use the colorama library in Python. It translates ANSI codes to their appropriate counterparts in Windows while keeping them intact in other operating systems.

To check if your terminal understands a subset of the ANSI escape sequences, for example, related to colors, you can try using the following command:

My default terminal on Linux says it can display 256 distinct colors, while xterm gives me only 8. The command would return a negative number if colors were unsupported.

ANSI escape sequences are like a markup language for the terminal. In HTML you work with tags, such as ou , to change how elements look in the document. These tags are mixed with your content, but they’re not visible themselves. Similarly, escape codes won’t show up in the terminal as long as it recognizes them. Otherwise, they’ll appear in the literal form as if you were viewing the source of a website.

As its name implies, a sequence must begin with the non-printable Esc character, whose ASCII value is 27, sometimes denoted as 0x1b in hexadecimal or 033 in octal. You may use Python number literals to quickly verify it’s indeed the same number:

>>>

>>> 27 == 0x1b == 0o33
Vrai

Additionally, you can obtain it with the e escape sequence in the shell:

The most common ANSI escape sequences take the following form:

Élément La description Example
Esc non-printable escape character 33
[[[[ opening square bracket [[[[
numeric code one or more numbers separated with ; 0
character code uppercase or lowercase letter m

le numeric code can be one or more numbers separated with a semicolon, while the character code is just one letter. Their specific meaning is defined by the ANSI standard. For example, to reset all formatting, you would type one of the following commands, which use the code zero and the letter m:

$ écho -e "e[0m"[0m"[0m"[0m"
$ écho -e "x1b[0m"[0m"[0m"[0m"
$ écho -e "33[0m"[0m"[0m"[0m"

At the other end of the spectrum, you have compound code values. To set foreground and background with RGB channels, given that your terminal supports 24-bit depth, you could provide multiple numbers:

$ écho -e "e[38;2;0;0;0me[48;2;255;255;255mBlackonwhitee[0m"[38;2;0;0;0me[48;2;255;255;255mBlackonwhitee[0m"[38;2;0;0;0me[48;2;255;255;255mBlackonwhitee[0m"[38;2;0;0;0me[48;2;255;255;255mBlackonwhitee[0m"

It’s not just text color that you can set with the ANSI escape codes. You can, for example, clear and scroll the terminal window, change its background, move the cursor around, make the text blink or decorate it with an underline.

In Python, you’d probably write a helper function to allow for wrapping arbitrary codes into a sequence:

>>>

>>> def esc(code):
...     revenir F'33[[[[codem'
...
>>> impression(esc('31;1;4') + 'really' + esc(0) + ' important')

This would make the word vraiment appear in red, bold, and underlined font:

Text formatted with ANSI escape codes

However, there are higher-level abstractions over ANSI escape codes, such as the mentioned colorama library, as well as tools for building user interfaces in the console.

Building Console User Interfaces

While playing with ANSI escape codes is undeniably a ton of fun, in the real world you’d rather have more abstract building blocks to put together a user interface. There are a few libraries that provide such a high level of control over the terminal, but malédictions seems to be the most popular choice.

Primarily, it allows you to think in terms of independent graphical widgets instead of a blob of text. Besides, you get a lot of freedom in expressing your inner artist, because it’s really like painting a blank canvas. The library hides the complexities of having to deal with different terminals. Other than that, it has great support for keyboard events, which might be useful for writing video games.

How about making a retro snake game? Let’s create a Python snake simulator:

The retro snake game built with curses library

First, you need to import the malédictions module. Since it modifies the state of a running terminal, it’s important to handle errors and gracefully restore the previous state. You can do this manually, but the library comes with a convenient wrapper for your main function:

importation malédictions

def principale(écran):
    passer

si __name__ == '__main__':
    malédictions.emballage(principale)

Note, the function must accept a reference to the screen object, also known as stdscr, that you’ll use later for additional setup.

If you run this program now, you won’t see any effects, because it terminates immediately. However, you can add a small delay to have a sneak peek:

importation temps, malédictions

def principale(écran):
    temps.sommeil(1)

si __name__ == '__main__':
    malédictions.emballage(principale)

This time the screen went completely blank for a second, but the cursor was still blinking. To hide it, just call one of the configuration functions defined in the module:

importation temps, malédictions

def principale(écran):
    malédictions.curs_set(0)  # Hide the cursor
    temps.sommeil(1)

si __name__ == '__main__':
    malédictions.emballage(principale)

Let’s define the snake as a list of points in screen coordinates:

serpent = [([([([(0, je) pour je dans renversé(intervalle(20))]

The head of the snake is always the first element in the list, whereas the tail is the last one. The initial shape of the snake is horizontal, starting from the top-left corner of the screen and facing to the right. While its y-coordinate stays at zero, its x-coordinate decreases from head to tail.

To draw the snake, you’ll start with the head and then follow with the remaining segments. Each segment carries (y, x) coordinates, so you can unpack them:

# Draw the snake
écran.addstr(*serpent[[[[0], '@')
pour segment dans serpent[[[[1:]:
    écran.addstr(*segment, '*')

Again, if you run this code now, it won’t display anything, because you must explicitly refresh the screen afterward:

importation temps, malédictions

def principale(écran):
    malédictions.curs_set(0)  # Hide the cursor

    serpent = [([([([(0, je) pour je dans renversé(intervalle(20))]

    # Draw the snake
    écran.addstr(*serpent[[[[0], '@')
    pour segment dans serpent[[[[1:]:
        écran.addstr(*segment, '*')

    écran.rafraîchir()
    temps.sommeil(1)

si __name__ == '__main__':
    malédictions.emballage(principale)

You want to move the snake in one of four directions, which can be defined as vectors. Eventually, the direction will change in response to an arrow keystroke, so you may hook it up to the library’s key codes:

directions = 
    malédictions.KEY_UP: (-1, 0),
    malédictions.KEY_DOWN: (1, 0),
    malédictions.KEY_LEFT: (0, -1),
    malédictions.KEY_RIGHT: (0, 1),


direction = directions[[[[malédictions.KEY_RIGHT]

How does a snake move? It turns out that only its head really moves to a new location, while all other segments shift towards it. In each step, almost all segments remain the same, except for the head and the tail. Assuming the snake isn’t growing, you can remove the tail and insert a new head at the beginning of the list:

# Move the snake
serpent.pop()
serpent.insérer(0, tuple(carte(somme, Zip *: français(serpent[[[[0], direction))))

To get the new coordinates of the head, you need to add the direction vector to it. However, adding tuples in Python results in a bigger tuple instead of the algebraic sum of the corresponding vector components. One way to fix this is by using the built-in zip(), sum(), et map() functions.

The direction will change on a keystroke, so you need to call .getch() to obtain the pressed key code. However, if the pressed key doesn’t correspond to the arrow keys defined earlier as dictionary keys, the direction won’t change:

# Change direction on arrow keystroke
direction = directions.obtenir(écran.getch(), direction)

By default, however, .getch() is a blocking call that would prevent the snake from moving unless there was a keystroke. Therefore, you need to make the call non-blocking by adding yet another configuration:

def principale(écran):
    malédictions.curs_set(0)    # Hide the cursor
    écran.nodelay(Vrai)  # Don't block I/O calls

You’re almost done, but there’s just one last thing left. If you now loop this code, the snake will appear to be growing instead of moving. That’s because you have to erase the screen explicitly before each iteration.

Finally, this is all you need to play the snake game in Python:

importation temps, malédictions

def principale(écran):
    malédictions.curs_set(0)    # Hide the cursor
    écran.nodelay(Vrai)  # Don't block I/O calls

    directions = 
        malédictions.KEY_UP: (-1, 0),
        malédictions.KEY_DOWN: (1, 0),
        malédictions.KEY_LEFT: (0, -1),
        malédictions.KEY_RIGHT: (0, 1),
    

    direction = directions[[[[malédictions.KEY_RIGHT]
    serpent = [([([([(0, je) pour je dans renversé(intervalle(20))]

    tandis que Vrai:
        écran.effacer()

        # Draw the snake
        écran.addstr(*serpent[[[[0], '@')
        pour segment dans serpent[[[[1:]:
            écran.addstr(*segment, '*')

        # Move the snake
        serpent.pop()
        serpent.insérer(0, tuple(carte(somme, Zip *: français(serpent[[[[0], direction))))

        # Change direction on arrow keystroke
        direction = directions.obtenir(écran.getch(), direction)

        écran.rafraîchir()
        temps.sommeil(0.1)

si __name__ == '__main__':
    malédictions.emballage(principale)

This is merely scratching the surface of the possibilities that the malédictions module opens up. You may use it for game development like this or more business-oriented applications.

Living It Up With Cool Animations

Not only can animations make the user interface more appealing to the eye, but they also improve the overall user experience. When you provide early feedback to the user, for example, they’ll know if your program’s still working or if it’s time to kill it.

To animate text in the terminal, you have to be able to freely move the cursor around. You can do this with one of the tools mentioned previously, that is ANSI escape codes or the malédictions bibliothèque. However, I’d like to show you an even simpler way.

If the animation can be constrained to a single line of text, then you might be interested in two special escape character sequences:

  • Carriage return: r
  • Backspace: b

The first one moves the cursor to the beginning of the line, whereas the second one moves it only one character to the left. They both work in a non-destructive way without overwriting text that’s already been written.

Let’s take a look at a few examples.

You’ll often want to display some kind of a Rouet to indicate a work in progress without knowing exactly how much time’s left to finish:

Indefinite animation in the terminal

Many command line tools use this trick while downloading data over the network. You can make a really simple stop motion animation from a sequence of characters that will cycle in a round-robin fashion:

de itertools importation cycle
de temps importation sommeil

pour Cadre dans cycle(r'-|/-|/'):
    impression('r', Cadre, SEP='', fin='', affleurer=Vrai)
    sommeil(0.2)

The loop gets the next character to print, then moves the cursor to the beginning of the line, and overwrites whatever there was before without adding a newline. You don’t want extra space between positional arguments, so separator argument must be blank. Also, notice the use of Python’s raw strings due to backslash characters present in the literal.

When you know the remaining time or task completion percentage, then you’re able to show an animated progress bar:

Progress bar animation in the terminal

First, you need to calculate how many hashtags to display and how many blank spaces to insert. Next, you erase the line and build the bar from scratch:

de temps importation sommeil

def le progrès(pour cent=0, largeur=30):
    la gauche = largeur * pour cent // 100
    droite = largeur - la gauche
    impression('r['['['[', '#' * la gauche, ' ' * droite, ']',
          F' percent:.0f%',
          SEP='', fin='', affleurer=Vrai)

pour je dans intervalle(101):
    le progrès(je)
    sommeil(0.1)

As before, each request for update repaints the entire line.

Making Sounds With Print

If you’re old enough to remember computers with a PC speaker, then you must also remember their distinctive bip sound, often used to indicate hardware problems. They could barely make any more noises than that, yet video games seemed so much better with it.

Today you can still take advantage of this small loudspeaker, but chances are your laptop didn’t come with one. In such a case, you can enable terminal bell emulation in your shell, so that a system warning sound is played instead.

Go ahead and type this command to see if your terminal can play a sound:

This would normally print text, but the -e flag enables the interpretation of backslash escapes. As you can see, there’s a dedicated escape sequence a, which stands for “alert”, that outputs a special bell character. Some terminals make a sound whenever they see it.

Similarly, you can print this character in Python. Perhaps in a loop to form some kind of melody. While it’s only a single note, you can still vary the length of pauses between consecutive instances. That seems like a perfect toy for Morse code playback!

The rules are the following:

  • Letters are encoded with a sequence of point (·) and tiret (–) symbols.
  • UNE point is one unit of time.
  • UNE tiret is three units of time.
  • Individual des symboles in a letter are spaced one unit of time apart.
  • Symbols of two adjacent des lettres are spaced three units of time apart.
  • Symbols of two adjacent mots are spaced seven units of time apart.

According to those rules, you could be “printing” an SOS signal indefinitely in the following way:

tandis que Vrai:
    point()
    symbol_space()
    point()
    symbol_space()
    point()
    letter_space()
    tiret()
    symbol_space()
    tiret()
    symbol_space()
    tiret()
    letter_space()
    point()
    symbol_space()
    point()
    symbol_space()
    point()
    word_space()

In Python, you can implement it in merely ten lines of code:

de temps importation sommeil

la vitesse = 0.1

def signal(durée, symbole):
    sommeil(durée)
    impression(symbole, fin='', affleurer=Vrai)

point = lambda: signal(la vitesse, a')
tiret = lambda: signal(3*la vitesse, '−a')
symbol_space = lambda: signal(la vitesse, '')
letter_space = lambda: signal(3*la vitesse, '')
word_space = lambda: signal(7*la vitesse, ' ')

Maybe you could even take it one step further and make a command line tool for translating text into Morse code? Either way, I hope you’re having fun with this!

Mocking Python Print in Unit Tests

Nowadays, it’s expected that you ship code that meets high quality standards. If you aspire to become a professional, you must learn how to test your code.

Software testing is especially important in dynamically typed languages, such as Python, which don’t have a compiler to warn you about obvious mistakes. Defects can make their way to the production environment and remain dormant for a long time, until that one day when a branch of code finally gets executed.

Sure, you have linters, type checkers, and other tools for static code analysis to assist you. But they won’t tell you whether your program does what it’s supposed to do on the business level.

So, should you be testing print()? No. After all, it’s a built-in function that must have already gone through a comprehensive suite of tests. What you want to test, though, is whether your code is calling print() at the right time with the expected parameters. That’s known as a comportement.

You can test behaviors by mocking real objects or functions. In this case, you want to mock print() to record and verify its invocations.

Mocking in Python can be done twofold. First, you can take the traditional path of statically-typed languages by employing dependency injection. This may sometimes require you to change the code under test, which isn’t always possible if the code is defined in an external library:

def Télécharger(url, bûche=impression):
    bûche(F'Downloading url')
    # ...

This is the same example I used in an earlier section to talk about function composition. It basically allows for substituting print() with a custom function of the same interface. To check if it prints the right message, you have to intercept it by injecting a mocked function:

>>>

>>> def mock_print(message):
...     mock_print.last_message = message
...
>>> Télécharger('resource', mock_print)
>>> affirmer 'Downloading resource' == mock_print.last_message

Calling this mock makes it save the last message in an attribute, which you can inspect later, for example in an affirmer statement.

In a slightly alternative solution, instead of replacing the entire print() function with a custom wrapper, you could redirect the standard output to an in-memory file-like stream of characters:

>>>

>>> def Télécharger(url, courant=Aucun):
...     impression(F'Downloading url', fichier=courant)
...     # ...
...
>>> importation io
>>> memory_buffer = io.StringIO()
>>> Télécharger('app.js', memory_buffer)
>>> Télécharger('style.css', memory_buffer)
>>> memory_buffer.getvalue()
'Downloading app.jsnDownloading style.cssn'

This time the function explicitly calls print(), but it exposes its fichier parameter to the outside world.

However, a more Pythonic way of mocking objects takes advantage of the built-in moquer module, which uses a technique called monkey patching. This derogatory name stems from it being a “dirty hack” that you can easily shoot yourself in the foot with. It’s less elegant than dependency injection but definitely quick and convenient.

What monkey patching does is alter implementation dynamically at runtime. Such a change is visible globally, so it may have unwanted consequences. In practice, however, patching only affects the code for the duration of test execution.

To mock print() in a test case, you’ll typically use the @patch decorator and specify a target for patching by referring to it with a fully qualified name, that is including the module name:

de unittest.mock importation pièce

@patch('builtins.print')
def test_print(mock_print):
    impression('not a real print')
    mock_print.assert_called_with('not a real print')

This will automatically create the mock for you and inject it to the test function. However, you need to declare that your test function accepts a mock now. The underlying mock object has lots of useful methods and attributes for verifying behavior.

Did you notice anything peculiar about that code snippet?

Despite injecting a mock to the function, you’re not calling it directly, although you could. That injected mock is only used to make assertions afterward and maybe to prepare the context before running the test.

In real life, mocking helps to isolate the code under test by removing dependencies such as a database connection. You rarely call mocks in a test, because that doesn’t make much sense. Rather, it’s other pieces of code that call your mock indirectly without knowing it.

Here’s what that means:

de unittest.mock importation pièce

def saluer(prénom):
    impression(F'Hello, name!')

@patch('builtins.print')
def test_greet(mock_print):
    saluer('John')
    mock_print.assert_called_with('Hello, John!')

The code under test is a function that prints a greeting. Even though it’s a fairly simple function, you can’t test it easily because it doesn’t return a value. It has a side-effect.

To eliminate that side-effect, you need to mock the dependency out. Patching lets you avoid making changes to the original function, which can remain agnostic about print(). It thinks it’s calling print(), but in reality, it’s calling a mock you’re in total control of.

There are many reasons for testing software. One of them is looking for bugs. When you write tests, you often want to get rid of the print() function, for example, by mocking it away. Paradoxically, however, that same function can help you find bugs during a related process of debugging you’ll read about in the next section.

You can’t monkey patch the impression statement in Python 2, nor can you inject it as a dependency. However, you have a few other options:

  • Use stream redirection.
  • Patch the standard output defined in the sys module.
  • Importation print() du __future__ module.

Let’s examine them one by one.

Stream redirection is almost identical to the example you saw earlier:

>>>

>>> def Télécharger(url, courant=Aucun):
...     impression >> courant, 'Downloading %s' % url
...     # ...
...
>>> de StringIO importation StringIO
>>> memory_buffer = StringIO()
>>> Télécharger('app.js', memory_buffer)
>>> Télécharger('style.css', memory_buffer)
>>> memory_buffer.getvalue()
'Downloading app.jsnDownloading style.cssn'

There are only two differences. First, the syntax for stream redirection uses chevron (>>) instead of the fichier argument. The other difference is where StringIO is defined. You can import it from a similarly named StringIO module, or cStringIO for a faster implementation.

Patching the standard output from the sys module is exactly what it sounds like, but you need to be aware of a few gotchas:

de moquer importation pièce, appel

def saluer(prénom):
    impression 'Hello, %s!' % prénom

@patch('sys.stdout')
def test_greet(mock_stdout):
    saluer('John')
    mock_stdout.écrire.assert_has_calls([[[[
       appel('Hello, John!'),
       appel(' n')
    ])

First of all, remember to install the moquer module as it wasn’t available in the standard library in Python 2.

Secondly, the impression statement calls the underlying .write() method on the mocked object instead of calling the object itself. That’s why you’ll run assertions against mock_stdout.write.

Finally, a single impression statement doesn’t always correspond to a single call to sys.stdout.write(). In fact, you’ll see the newline character written separately.

The last option you have is importing print() de futur and patching it:

de __future__ importation print_function
de moquer importation pièce

def saluer(prénom):
    impression('Hello, %s!' % prénom)

@patch('__builtin__.print')
def test_greet(mock_print):
    saluer('John')
    mock_print.assert_called_with('Hello, John!')

Again, it’s nearly identical to Python 3, but the print() function is defined in the __builtin__ module rather than builtins.

In this section, you’ll take a look at the available tools for debugging in Python, starting from a humble print() function, through the enregistrement module, to a fully fledged debugger. After reading it, you’ll be able to make an educated decision about which of them is the most suitable in a given situation.

Tracing

Also known as print debugging ou caveman debugging, it’s the most basic form of debugging. While a little bit old-fashioned, it’s still powerful and has its uses.

The idea is to follow the path of program execution until it stops abruptly, or gives incorrect results, to identify the exact instruction with a problem. You do that by inserting print statements with words that stand out in carefully chosen places.

Take a look at this example, which manifests a rounding error:

>>>

>>> def moyenne(Nombres):
...     impression('debug1:', Nombres)
...     si len(Nombres) > 0:
...         impression('debug2:', somme(Nombres))
...         revenir somme(Nombres) / len(Nombres)
...
>>> 0.1 == moyenne(3*[[[[0.1])
debug1: [0.1, 0.1, 0.1]
debug2: 0.30000000000000004
Faux

As you can see, the function doesn’t return the expected value of 0.1, but now you know it’s because the sum is a little off. Tracing the state of variables at different steps of the algorithm can give you a hint where the issue is.

In this case, the problem lies in how point flottant numbers are represented in computer memory. Remember that numbers are stored in binary form. Decimal value of 0.1 turns out to have an infinite binary representation, which gets rounded.

For more information on rounding numbers in Python, you can check out How to Round Numbers in Python.

This method is simple and intuitive and will work in pretty much every programming language out there. Not to mention, it’s a great exercise in the learning process.

On the other hand, once you master more advanced techniques, it’s hard to go back, because they allow you to find bugs much quicker. Tracing is a laborious manual process, which can let even more errors slip through. The build and deploy cycle takes time. Afterward, you need to remember to meticulously remove all the print() calls you made without accidentally touching the genuine ones.

Besides, it requires you to make changes in the code, which isn’t always possible. Maybe you’re debugging an application running in a remote web server or want to diagnose a problem in a post-mortem mode. Sometimes you simply don’t have access to the standard output.

That’s precisely where logging shines.

Enregistrement

Let’s pretend for a minute that you’re running an e-commerce website. One day, an angry customer makes a phone call complaining about a failed transaction and saying he lost his money. He claims to have tried purchasing a few items, but in the end, there was some cryptic error that prevented him from finishing that order. Yet, when he checked his bank account, the money was gone.

You apologize sincerely and make a refund, but also don’t want this to happen again in the future. How do you debug that? If only you had some trace of what happened, ideally in the form of a chronological list of events with their context.

Whenever you find yourself doing print debugging, consider turning it into permanent log messages. This may help in situations like this, when you need to analyze a problem after it happened, in an environment that you don’t have access to.

There are sophisticated tools for log aggregation and searching, but at the most basic level, you can think of logs as text files. Each line conveys detailed information about an event in your system. Usually, it won’t contain personally identifying information, though, in some cases, it may be mandated by law.

Here’s a breakdown of a typical log record:

[2019-06-14 15:18:34,517][DEBUG][root][MainThread]    Customer(id=123) logged out

As you can see, it has a structured form. Apart from a descriptive message, there are a few customizable fields, which provide the context of an event. Here, you have the exact date and time, the log level, the logger name, and the thread name.

Log levels allow you to filter messages quickly to reduce noise. If you’re looking for an error, you don’t want to see all the warnings or debug messages, for example. It’s trivial to disable or enable messages at certain log levels through the configuration, without even touching the code.

With logging, you can keep your debug messages separate from the standard output. All the log messages go to the standard error stream by default, which can conveniently show up in different colors. However, you can redirect log messages to separate files, even for individual modules!

Quite commonly, misconfigured logging can lead to running out of space on the server’s disk. To prevent that, you may set up log rotation, which will keep the log files for a specified duration, such as one week, or once they hit a certain size. Nevertheless, it’s always a good practice to archive older logs. Some regulations enforce that customer data be kept for as long as five years!

Compared to other programming languages, logging in Python is simpler, because the enregistrement module is bundled with the standard library. You just import and configure it in as little as two lines of code:

importation enregistrement
enregistrement.basicConfig(niveau=enregistrement.DEBUG)

You can call functions defined at the module level, which are hooked to the root logger, but more the common practice is to obtain a dedicated logger for each of your source files:

enregistrement.déboguer('hello')  # Module-level function

enregistreur = enregistrement.getLogger(__name__)
enregistreur.déboguer('hello')   # Logger's method

The advantage of using custom loggers is more fine-grain control. They’re usually named after the module they were defined in through the __name__ variable.

One last reason to switch from the print() function to logging is thread safety. In the upcoming section, you’ll see that the former doesn’t play well with multiple threads of execution.

Débogage

The truth is that neither tracing nor logging can be considered real debugging. To do actual debugging, you need a debugger tool, which allows you to do the following:

  • Step through the code interactively.
  • Set breakpoints, including conditional breakpoints.
  • Introspect variables in memory.
  • Evaluate custom expressions at runtime.

A crude debugger that runs in the terminal, unsurprisingly named pdb for “The Python Debugger,” is distributed as part of the standard library. This makes it always available, so it may be your only choice for performing remote debugging. Perhaps that’s a good reason to get familiar with it.

However, it doesn’t come with a graphical interface, so using pdb may be a bit tricky. If you can’t edit the code, you have to run it as a module and pass your script’s location:

$ python -m pdb my_script.py

Otherwise, you can set up a breakpoint directly in the code, which will pause the execution of your script and drop you into the debugger. The old way of doing this required two steps:

>>>

>>> importation pdb
>>> pdb.set_trace()
--Return--
> (1)()->None
(Pdb)

This shows up an interactive prompt, which might look intimidating at first. However, you can still type native Python at this point to examine or modify the state of local variables. Apart from that, there’s really only a handful of debugger-specific commands that you want to use for stepping through the code.

Since Python 3.7, you can also call the built-in breakpoint() function, which does the same thing, but in a more compact way and with some additional bells and whistles:

def moyenne(Nombres):
    si len(Nombres) > 0:
        breakpoint()  # Python 3.7+
        revenir somme(Nombres) / len(Nombres)

You’re probably going to use a visual debugger integrated with a code editor for the most part. PyCharm has an excellent debugger, which boasts high performance, but you’ll find plenty of alternative IDEs with debuggers, both paid and free of charge.

Debugging isn’t the proverbial silver bullet. Sometimes logging or tracing will be a better solution. For example, defects that are hard to reproduce, such as race conditions, often result from temporal coupling. When you stop at a breakpoint, that little pause in program execution may mask the problem. It’s kind of like the Heisenberg principle: you can’t measure and observe a bug at the same time.

These methods aren’t mutually exclusive. They complement each other.

Thread-Safe Printing

I briefly touched upon the thread safety issue before, recommending enregistrement au dessus de print() une fonction. If you’re still reading this, then you must be comfortable with the concept of threads.

Thread safety means that a piece of code can be safely shared between multiple threads of execution. The simplest strategy for ensuring thread-safety is by sharing immuable objects only. If threads can’t modify an object’s state, then there’s no risk of breaking its consistency.

Another method takes advantage of local memory, which makes each thread receive its own copy of the same object. That way, other threads can’t see the changes made to it in the current thread.

But that doesn’t solve the problem, does it? You often want your threads to cooperate by being able to mutate a shared resource. The most common way of synchronizing concurrent access to such a resource is by verrouillage it. This gives exclusive write access to one or sometimes a few threads at a time.

However, locking is expensive and reduces concurrent throughput, so other means for controlling access have been invented, such as atomic variables ou la compare-and-swap algorithm.

Printing isn’t thread-safe in Python. le print() function holds a reference to the standard output, which is a shared global variable. In theory, because there’s no locking, a context switch could happen during a call to sys.stdout.write(), intertwining bits of text from multiple print() calls.

In practice, however, that doesn’t happen. No matter how hard you try, writing to the standard output seems to be atomic. The only problem that you may sometimes observe is with messed up line breaks:

[Thread-3 A][Thread-2 A][Thread-1 A]

[Thread-3 B][Thread-1 B]


[Thread-1 C][Thread-3 C]

[Thread-2 B]
[Thread-2 C]

To simulate this, you can increase the likelihood of a context switch by making the underlying .write() method go to sleep for a random amount of time. Comment? By mocking it, which you already know about from an earlier section:

importation sys

de temps importation sommeil
de au hasard importation au hasard
de filetage importation current_thread, Thread
de unittest.mock importation pièce

écrire = sys.stdout.écrire

def slow_write(texte):
    sommeil(au hasard())
    écrire(texte)

def tâche():
    thread_name = current_thread().prénom
    pour lettre dans 'ABC':
        impression(F'[[[[thread_name letter]')

avec pièce('sys.stdout') comme mock_stdout:
    mock_stdout.écrire = slow_write
    pour _ dans intervalle(3):
        Thread(cible=tâche).début()

First, you need to store the original .write() method in a variable, which you’ll delegate to later. Then you provide your fake implementation, which will take up to one second to execute. Each thread will make a few print() calls with its name and a letter: A, B, and C.

If you read the mocking section before, then you may already have an idea of why printing misbehaves like that. Nonetheless, to make it crystal clear, you can capture values fed into your slow_write() une fonction. You’ll notice that you get a slightly different sequence each time:

[[[[
    '[Thread-3 A]',
    '[Thread-2 A]',
    '[Thread-1 A]',
    ' n',
    ' n',
    '[Thread-3 B]',
    (...)
]

Even though sys.stdout.write() itself is an atomic operation, a single call to the print() function can yield more than one write. For example, line breaks are written separately from the rest of the text, and context switching takes place between those writes.

You can make the newline character become an integral part of the message by handling it manually:

impression(F'[[[[thread_name letter] n', fin='')

This will fix the output:

[Thread-2 A]
[Thread-1 A]
[Thread-3 A]
[Thread-1 B]
[Thread-3 B]
[Thread-2 B]
[Thread-1 C]
[Thread-2 C]
[Thread-3 C]

Notice, however, that the print() function still keeps making a separate call for the empty suffix, which translates to useless sys.stdout.write('') instruction:

[[[[
    '[Thread-2 A] n',
    '[Thread-1 A] n',
    '[Thread-3 A] n',
    '',
    '',
    '',
    '[Thread-1 B] n',
    (...)
]

A truly thread-safe version of the print() function could look like this:

importation filetage

fermer à clé = filetage.Lock()

def thread_safe_print(*args, **kwargs):
    avec fermer à clé:
        impression(*args, **kwargs)

You can put that function in a module and import it elsewhere:

de thread_safe_print importation thread_safe_print

def tâche():
    thread_name = current_thread().prénom
    pour lettre dans 'ABC':
        thread_safe_print(F'[[[[thread_name letter]')

Now, despite making two writes per each print() request, only one thread is allowed to interact with the stream, while the rest must wait:

[[[[
    # Lock acquired by Thread-3 
    '[Thread-3 A]',
    ' n',
    # Lock released by Thread-3
    # Lock acquired by Thread-1
    '[Thread-1 B]',
    ' n',
    # Lock released by Thread-1
    (...)
]

I added comments to indicate how the lock is limiting access to the shared resource.

Conversely, the enregistrement module is thread-safe by design, which is reflected by its ability to display thread names in the formatted message:

>>>

>>> importation enregistrement
>>> enregistrement.basicConfig(format='%(threadName)s %(message)s')
>>> enregistrement.Erreur('hello')
MainThread hello

It’s another reason why you might not want to use the print() function all the time.

Python Print Counterparts

By now, you know a lot of what there is to know about print()! The subject, however, wouldn’t be complete without talking about its counterparts a little bit. Tandis que print() is about the output, there are functions and libraries for the input.

Built-In

Python comes with a built-in function for accepting input from the user, predictably called input(). It accepts data from the standard input stream, which is usually the keyboard:

>>>

>>> prénom = contribution('Enter your name: ')
Enter your name: jdoe
>>> impression(prénom)
jdoe

The function always returns a string, so you might need to parse it accordingly:

essayer:
    âge = int(contribution('How old are you? '))
sauf ValueError:
    passer

The prompt parameter is completely optional, so nothing will show if you skip it, but the function will still work:

>>>

>>> X = contribution()
Bonjour le monde
>>> impression(X)
Bonjour le monde

Nevertheless, throwing in a descriptive call to action makes the user experience so much better.

Asking the user for a password with input() is a bad idea because it’ll show up in plaintext as they’re typing it. In this case, you should be using the getpass() function instead, which masks typed characters. This function is defined in a module under the same name, which is also available in the standard library:

>>>

>>> de getpass importation getpass
>>> mot de passe = getpass()
Password: 
>>> impression(mot de passe)
s3cret

le getpass module has another function for getting the user’s name from an environment variable:

>>>

>>> de getpass importation getuser
>>> getuser()
'jdoe'

Python’s built-in functions for handling the standard input are quite limited. At the same time, there are plenty of third-party packages, which offer much more sophisticated tools.

Third-Party

There are external Python packages out there that allow for building complex graphical interfaces specifically to collect data from the user. Some of their features include:

  • Advanced formatting and styling
  • Automated parsing, validation, and sanitization of user data
  • A declarative style of defining layouts
  • Interactive autocompletion
  • Mouse support
  • Predefined widgets such as checklists or menus
  • Searchable history of typed commands
  • Syntax highlighting

Demonstrating such tools is outside of the scope of this article, but you may want to try them out. I personally got to know about some of those through the Python Bytes Podcast. Here they are:

Nonetheless, it’s worth mentioning a command line tool called rlwrap that adds powerful line editing capabilities to your Python scripts for free. You don’t have to do anything for it to work!

Let’s assume you wrote a command-line interface that understands three instructions, including one for adding numbers:

impression('Type "help", "exit", "add a [b [c ...]]"')
tandis que Vrai:
    commander, *arguments = contribution('~ ').Divisé(' ')
    si len(commander) > 0:
        si commander.inférieur() == 'exit':
            Pause
        elif commander.inférieur() == 'help':
            impression('This is help.')
        elif commander.inférieur() == 'add':
            impression(somme(carte(int, arguments)))
        autre:
            impression('Unknown command')

At first glance, it seems like a typical prompt when you run it:

$ python calculator.py
Type "help", "exit", "add a [b [c ...]]"
~ add 1 2 3 4
dix
~ aad 2 3
Unknown command
~ exit
$

But as soon as you make a mistake and want to fix it, you’ll see that none of the function keys work as expected. Hitting the La gauche arrow, for example, results in this instead of moving the cursor back:

$ python calculator.py
Type "help", "exit", "add a [b [c ...]]"
~ aad^[[D[[D[[D[[D

Now, you can wrap the same script with the rlwrap command. Not only will you get the arrow keys working, but you’ll also be able to search through the persistent history of your custom commands, use autocompletion, and edit the line with shortcuts:

$ rlwrap python calculator.py
Type "help", "exit", "add a [b [c ...]]"
(reverse-i-search)`a': add 1 2 3 4

Isn’t that great?

Conclusion

You’re now armed with a body of knowledge about the print() function in Python, as well as many surrounding topics. You have a deep understanding of what it is and how it works, involving all of its key elements. Numerous examples gave you insight into its evolution from Python 2.

Apart from that, you learned how to:

  • Avoid common mistakes with print() in Python
  • Deal with newlines, character encodings and buffering
  • Write text to files
  • Mock the print() function in unit tests
  • Build advanced user interfaces in the terminal

Now that you know all this, you can make interactive programs that communicate with users or produce data in popular file formats. You’re able to quickly diagnose problems in your code and protect yourself from them. Last but not least, you know how to implement the classic snake game.

If you’re still thirsty for more information, have questions, or simply would like to share your thoughts, then feel free to reach out in the comments section below.