Ihr Leitfaden zur Python-Druckfunktion

Ihr Leitfaden zur Python-Druckfunktion

Wenn Sie wie die meisten Python-Benutzer sind, einschließlich mir, haben Sie Ihre Python-Reise wahrscheinlich damit begonnen, etwas überprint() zu lernen. Es hat Ihnen geholfen, Ihren eigenen Einzeilerhello worldzu schreiben. Sie können es verwenden, um formatierte Nachrichten auf dem Bildschirm anzuzeigen und möglicherweise einige Fehler zu finden. Wenn Sie jedoch der Meinung sind, dass dies alles ist, was Sie über dieprint()-Funktion von Python wissen müssen, dann verpassen Sie viel!

Lesen Sie weiter, um diese scheinbar langweilige und unbeachtete kleine Funktion voll auszunutzen. In diesem Tutorial werden Sie mit der effektiven Verwendung von Pythonprint()vertraut gemacht. Bereiten Sie sich jedoch auf einen tiefen Tauchgang vor, während Sie durch die Abschnitte gehen. Sie werden überrascht sein, wie vielprint() zu bieten hat!

Am Ende dieses Tutorials erfahren Sie, wie Sie:

  • Vermeiden Sie häufige Fehler mit Pythonsprint()

  • Beschäftigen Sie sich mit Zeilenumbrüchen, Zeichenkodierungen und Pufferung

  • Schreiben Sie Text in Dateien

  • Mockprint() in Unit-Tests

  • Erstellen Sie erweiterte Benutzeroberflächen im Terminal

Wenn Sie ein absoluter Anfänger sind, profitieren Sie am meisten vom Lesen des ersten Teils dieses Tutorials, in dem die Grundlagen des Druckens in Python erläutert werden. Andernfalls können Sie diesen Teil überspringen und nach Belieben herumspringen.

Note:print() war eine wichtige Ergänzung zu Python 3, in der die in Python 2 verfügbare alteprint-Anweisung ersetzt wurde.

Dafür gab es eine Reihe guter Gründe, wie Sie gleich sehen werden. Obwohl sich dieses Tutorial auf Python 3 konzentriert, zeigt es die alte Art des Druckens in Python als Referenz.

Free Bonus:Click here to get our free Python Cheat Sheet zeigt Ihnen die Grundlagen von Python 3, z. B. das Arbeiten mit Datentypen, Wörterbüchern, Listen und Python-Funktionen.

Drucken auf den Punkt gebracht

Schauen wir uns einige Beispiele aus der Praxis für das Drucken in Python an. Am Ende dieses Abschnitts kennen Sie alle Möglichkeiten,print() aufzurufen. Oder Sie sagen im Programmierjargon, dass Sie mit denfunction signaturevertraut sind.

Drucken aufrufen

Das einfachste Beispiel für die Verwendung von Pythonprint() erfordert nur wenige Tastenanschläge:

>>>

>>> print()

Sie übergeben keine Argumente, müssen jedoch am Ende leere Klammern setzen, die Python anweisen, tatsächlichexecute the function zu verwenden, anstatt nur mit Namen darauf zu verweisen.

Dies erzeugt ein unsichtbares Zeilenumbruchzeichen, das wiederum dazu führt, dass eine leere Zeile auf Ihrem Bildschirm angezeigt wird. Sie könnenprint() mehrmals so aufrufen, um vertikalen Raum hinzuzufügen. Es ist so, als würden Sie in einem Textverarbeitungsprogramm[.kbd .key-enter]#Enter # auf Ihrer Tastatur drücken.

Wie Sie gerade gesehen haben, führt das Aufrufen vonprint() ohne Argumente zublank line, einer Zeile, die ausschließlich aus dem Zeilenumbruchzeichen besteht. Verwechseln Sie dies nicht mit einemempty line, das überhaupt keine Zeichen enthält, nicht einmal die neue Zeile!

Sie können diestring-Literale von Python verwenden, um diese beiden zu visualisieren:

'\n'  # Blank line
''    # Empty line

Der erste ist ein Zeichen lang, während der zweite keinen Inhalt hat.

Note: Um das Zeilenumbruchzeichen aus einer Zeichenfolge in Python zu entfernen, verwenden Sie die Methode.rstrip() wie folgt:

>>>

>>> 'A line of text.\n'.rstrip()
'A line of text.'

Dadurch werden alle nachgestellten Leerzeichen vom rechten Rand der Zeichenfolge entfernt.

In einem häufigeren Szenario möchten Sie dem Endbenutzer eine Nachricht übermitteln. Es gibt einige Möglichkeiten, dies zu erreichen.

Zunächst können Sie ein Zeichenfolgenliteral direkt anprint() übergeben:

>>>

>>> print('Please wait while the program is loading...')

Dadurch wird die Nachricht wörtlich auf dem Bildschirm gedruckt.

Zweitens können Sie diese Nachricht in eine eigene Variable mit einem aussagekräftigen Namen extrahieren, um die Lesbarkeit zu verbessern und die Wiederverwendung von Code zu fördern:

>>>

>>> message = 'Please wait while the program is loading...'
>>> print(message)

Zuletzt können Sie einen Ausdruck wiestring concatenation übergeben, der ausgewertet werden soll, bevor Sie das Ergebnis drucken:

>>>

>>> import os
>>> print('Hello, ' + os.getlogin() + '! How are you?')
Hello, jdoe! How are you?

Tatsächlich gibt es ein Dutzend Möglichkeiten, Nachrichten in Python zu formatieren. Ich empfehle Ihnen dringend, sichf-strings anzusehen, das in Python 3.6 eingeführt wurde, da sie die präziseste Syntax von allen bieten:

>>>

>>> import os
>>> print(f'Hello, {os.getlogin()}! How are you?')

Darüber hinaus verhindern F-Strings, dass Sie einen häufigen Fehler machen, bei dem Sie vergessen, gegossene verkettete Operanden einzugeben. Python ist eine stark typisierte Sprache, was bedeutet, dass Sie dies nicht zulassen:

>>>

>>> 'My age is ' + 42
Traceback (most recent call last):
  File "", line 1, in 
    'My age is ' + 42
TypeError: can only concatenate str (not "int") to str

Das ist falsch, weil das Hinzufügen von Zahlen zu Zeichenfolgen keinen Sinn ergibt. Sie müssen die Zahl zuerst explizit in eine Zeichenfolge konvertieren, um sie miteinander zu verbinden:

>>>

>>> 'My age is ' + str(42)
'My age is 42'

Sofern Sie nichthandle such errorselbst sind, informiert Sie der Python-Interpreter über ein Problem, indem ertraceback anzeigt.

Note:str() ist eine globale integrierte Funktion, die ein Objekt in seine Zeichenfolgendarstellung konvertiert.

Sie können es direkt auf einem beliebigen Objekt aufrufen, z. B. eine Nummer:

>>>

>>> str(3.14)
'3.14'

Integrierte Datentypen verfügen standardmäßig über eine vordefinierte Zeichenfolgendarstellung. Später in diesem Artikel erfahren Sie, wie Sie eine für Ihre benutzerdefinierten Klassen bereitstellen können.

Wie bei jeder Funktion spielt es keine Rolle, ob Sie ein Literal, eine Variable oder einen Ausdruck übergeben. Im Gegensatz zu vielen anderen Funktionen akzeptiertprint() jedoch alles, unabhängig von seinem Typ.

Bisher haben Sie nur die Zeichenfolge betrachtet, aber wie steht es mit anderen Datentypen? Probieren wir Literale verschiedener integrierter Typen aus und sehen, was dabei herauskommt:

>>>

>>> print(42)                            # 
42
>>> print(3.14)                          # 
3.14
>>> print(1 + 2j)                        # 
(1+2j)
>>> print(True)                          # 
True
>>> print([1, 2, 3])                     # 
[1, 2, 3]
>>> print((1, 2, 3))                     # 
(1, 2, 3)
>>> print({'red', 'green', 'blue'})      # 
{'red', 'green', 'blue'}
>>> print({'name': 'Alice', 'age': 42})  # 
{'name': 'Alice', 'age': 42}
>>> print('hello')                       # 
hello

Achten Sie jedoch auf die KonstanteNone. Obwohl es verwendet wird, um das Fehlen eines Werts anzuzeigen, wird es als'None' und nicht als leere Zeichenfolge angezeigt:

>>>

>>> print(None)
None

Woher weißprint(), wie man mit all diesen verschiedenen Typen arbeitet? Die kurze Antwort lautet, dass dies nicht der Fall ist. Es ruft implizitstr() hinter den Kulissen auf, um cast any object in eine Zeichenfolge einzugeben. Danach werden die Saiten einheitlich behandelt.

Später in diesem Lernprogramm erfahren Sie, wie Sie diesen Mechanismus zum Drucken benutzerdefinierter Datentypen wie z. B. Ihrer Klassen verwenden.

Okay, Sie können jetztprint() mit einem einzelnen Argument oder ohne Argumente aufrufen. Sie wissen, wie Sie feste oder formatierte Nachrichten auf dem Bildschirm drucken. Der nächste Unterabschnitt wird die Formatierung von Nachrichten etwas erweitern.

Mehrere Argumente trennen

Sie haben gesehen, dassprint() ohne Argumente aufgerufen wurde, um eine leere Zeile zu erzeugen, und dann mit einem einzelnen Argument aufgerufen wurde, um entweder eine feste oder eine formatierte Nachricht anzuzeigen.

Es stellt sich jedoch heraus, dass diese Funktion eine beliebige Anzahl vonpositional arguments akzeptieren kann, einschließlich null, eins oder mehr Argumente. Dies ist sehr praktisch in einem häufigen Fall der Nachrichtenformatierung, in dem Sie einige Elemente zusammenfügen möchten.

Schauen wir uns dieses Beispiel an:

>>>

>>> import os
>>> print('My name is', os.getlogin(), 'and I am', 42)
My name is jdoe and I am 42

print() hat alle vier an ihn übergebenen Argumente verkettet und ein einzelnes Leerzeichen zwischen ihnen eingefügt, damit Sie keine gequetschte Nachricht wie'My name isjdoeand I am42' erhalten.

Beachten Sie, dass es sich auch um die ordnungsgemäße Typumwandlung gekümmert hat, indem implizitstr() für jedes Argument aufgerufen wurde, bevor sie zusammengefügt wurden. Wenn Sie sich an den vorherigen Unterabschnitt erinnern, kann eine naive Verkettung aufgrund inkompatibler Typen leicht zu einem Fehler führen:

>>>

>>> print('My age is: ' + 42)
Traceback (most recent call last):
  File "", line 1, in 
    print('My age is: ' + 42)
TypeError: can only concatenate str (not "int") to str

print() akzeptiert nicht nur eine variable Anzahl von Positionsargumenten, sondern definiert auch vier benannte oderkeyword arguments, die optional sind, da sie alle Standardwerte haben. Sie können ihre kurze Dokumentation anzeigen, indem Siehelp(print) vom interaktiven Interpreter aufrufen.

Konzentrieren wir uns vorerst aufsep. Es steht fürseparator und erhält standardmäßig ein einzelnes Leerzeichen (' '). Es bestimmt den Wert, mit dem Elemente verbunden werden sollen.

Es muss entweder ein String oderNone sein, aber letzteres hat den gleichen Effekt wie der Standardbereich:

>>>

>>> print('hello', 'world', sep=None)
hello world
>>> print('hello', 'world', sep=' ')
hello world
>>> print('hello', 'world')
hello world

Wenn Sie das Trennzeichen vollständig unterdrücken möchten, müssen Sie stattdessen eine leere Zeichenfolge ('') übergeben:

>>>

>>> print('hello', 'world', sep='')
helloworld

Möglicherweise möchten Sie, dassprint() seine Argumente als separate Zeilen verbindet. Übergeben Sie in diesem Fall einfach das zuvor beschriebene maskierte Zeilenumbruchzeichen:

>>>

>>> print('hello', 'world', sep='\n')
hello
world

Ein nützlicheres Beispiel für den Parametersepwäre das Drucken von Dateipfaden:

>>>

>>> print('home', 'user', 'documents', sep='/')
home/user/documents

Denken Sie daran, dass das Trennzeichen zwischen den Elementen und nicht um sie herum liegt. Sie müssen dies also auf die eine oder andere Weise berücksichtigen:

>>>

>>> print('/home', 'user', 'documents', sep='/')
/home/user/documents
>>> print('', 'home', 'user', 'documents', sep='/')
/home/user/documents

Insbesondere können Sie ein Schrägstrichzeichen (/) in das erste Positionsargument einfügen oder eine leere Zeichenfolge als erstes Argument verwenden, um den führenden Schrägstrich zu erzwingen.

Note: Achten Sie darauf, Elemente einer Liste oder eines Tupels zu verbinden.

Wenn Sie dies manuell tun, erhalten Sie ein bekanntesTypeError, wenn mindestens eines der Elemente keine Zeichenfolge ist:

>>>

>>> print(' '.join(['jdoe is', 42, 'years old']))
Traceback (most recent call last):
  File "", line 1, in 
    print(','.join(['jdoe is', 42, 'years old']))
TypeError: sequence item 1: expected str instance, int found

Es ist sicherer, die Sequenz einfach mit dem Sternoperator (*) auszupacken undprint() Typguss handhaben zu lassen:

>>>

>>> print(*['jdoe is', 42, 'years old'])
jdoe is 42 years old

Das Entpacken entspricht praktisch dem Aufrufen vonprint() mit einzelnen Elementen der Liste.

Ein weiteres interessantes Beispiel könnte das Exportieren von Daten in eincomma-separated values (CSV) -Format sein:

>>>

>>> print(1, 'Python Tricks', 'Dan Bader', sep=',')
1,Python Tricks,Dan Bader

Dies würde Kantenfälle wie das Komma-Komma nicht korrekt behandeln, aber für einfache Anwendungsfälle sollte dies der Fall sein. Die obige Zeile wird in Ihrem Terminalfenster angezeigt. Um es in einer Datei zu speichern, müssen Sie die Ausgabe umleiten. Später in diesem Abschnitt erfahren Sie, wie Sie mitprint() Text direkt aus Python in Dateien schreiben.

Schließlich ist der Parametersepnicht nur auf ein einzelnes Zeichen beschränkt. Sie können Elemente mit Zeichenfolgen beliebiger Länge verbinden:

>>>

>>> print('node', 'child', 'child', sep=' -> ')
node -> child -> child

In den nächsten Unterabschnitten werden Sie die verbleibenden Keyword-Argumente derprint()-Funktion untersuchen.

Zeilenumbrüche verhindern

Manchmal möchten Sie Ihre Nachricht nicht mit einem nachgestellten Zeilenumbruch beenden, damit nachfolgende Aufrufe vonprint() in derselben Zeile fortgesetzt werden. Klassische Beispiele sind das Aktualisieren des Fortschritts eines lang laufenden Vorgangs oder das Auffordern des Benutzers zur Eingabe. Im letzteren Fall soll der Benutzer die Antwort in derselben Zeile eingeben:

Are you sure you want to do this? [y/n] y

Viele Programmiersprachen stellen Funktionen ähnlich wieprint() über ihre Standardbibliotheken bereit, aber Sie können entscheiden, ob Sie eine neue Zeile hinzufügen möchten oder nicht. In Java und C # haben Sie beispielsweise zwei unterschiedliche Funktionen, während Sie in anderen Sprachen explizit am Ende eines Zeichenfolgenliteral anhängen müssen.

Hier einige Beispiele für die Syntax in solchen Sprachen:

Sprache Beispiel

Perl

print "hello world\n"

C

printf("hello world\n");

C++

std::cout << "hello world" << std::endl;

Im Gegensatz dazu fügt dieprint()-Funktion von Python immer hinzu, ohne zu fragen, da dies in den meisten Fällen gewünscht wird. Um es zu deaktivieren, können Sie ein weiteres Schlüsselwortargument verwenden,end, das vorschreibt, mit was die Zeile beendet werden soll.

In Bezug auf die Semantik ist der Parameterendfast identisch mit dem Parametersep, den Sie zuvor gesehen haben:

  • Es muss eine Zeichenfolge oderNone sein.

  • Es kann beliebig lang sein.

  • Es hat einen Standardwert von' '.

  • Wenn es gleichNone ist, hat es den gleichen Effekt wie der Standardwert.

  • Wenn dies einer leeren Zeichenfolge ('') entspricht, wird der Zeilenumbruch unterdrückt.

Jetzt verstehen Sie, was unter der Haube passiert, wenn Sieprint() ohne Argumente aufrufen. Da Sie der Funktion keine Positionsargumente angeben, muss nichts verbunden werden, sodass das Standardtrennzeichen überhaupt nicht verwendet wird. Der Standardwert vonend gilt jedoch weiterhin, und eine leere Zeile wird angezeigt.

Note: Sie fragen sich möglicherweise, warum der Parameterend einen festen Standardwert hat und nicht, was auf Ihrem Betriebssystem sinnvoll ist.

Nun, Sie müssen sich beim Drucken keine Gedanken über die Darstellung von Zeilenumbrüchen unter verschiedenen Betriebssystemen machen, daprint() die Konvertierung automatisch übernimmt. Denken Sie daran, immer die Escape-Sequenz von in Zeichenfolgenliteralen zu verwenden.

Dies ist derzeit die portabelste Methode zum Drucken eines Zeilenumbruchs in Python:

>>>

>>> print('line1\nline2\nline3')
line1
line2
line3

Wenn Sie beispielsweise versuchen, ein Windows-spezifisches Zeilenumbruchzeichen auf einem Linux-Computer mit Gewalt zu drucken, wird die Ausgabe unterbrochen:

>>>

>>> print('line1\r\nline2\r\nline3')


line3

Auf der anderen Seite müssen Sie sich beim Öffnen einer Datei zum Lesen mitopen() auch nicht um die Darstellung von Zeilenumbrüchen kümmern. Die Funktion übersetzt alle systemspezifischen Zeilenumbrüche, auf die sie stößt, in universelle' '. Gleichzeitig haben Sie die Kontrolle darüber, wie die Zeilenumbrüche sowohl bei der Eingabe als auch bei der Ausgabe behandelt werden sollen, wenn Sie dies wirklich benötigen.

Um den Zeilenumbruch zu deaktivieren, müssen Sie eine leere Zeichenfolge über das Schlüsselwortargumentendangeben:

print('Checking file integrity...', end='')
# (...)
print('ok')

Obwohl dies zwei separateprint()-Aufrufe sind, die einen langen Abstand voneinander ausführen können, wird möglicherweise nur eine Zeile angezeigt. Zunächst sieht es so aus:

Checking file integrity...

Nach dem zweiten Aufruf vonprint() wird jedoch dieselbe Zeile auf dem Bildschirm angezeigt wie:

Checking file integrity...ok

Wie beisep können Sieend verwenden, um einzelne Teile mit einem benutzerdefinierten Trennzeichen zu einem großen Textblock zusammenzufügen. Anstatt jedoch mehrere Argumente zu verknüpfen, wird Text aus jedem Funktionsaufruf an dieselbe Zeile angehängt:

print('The first sentence', end='. ')
print('The second sentence', end='. ')
print('The last sentence.')

Diese drei Anweisungen geben eine einzelne Textzeile aus:

The first sentence. The second sentence. The last sentence.

Sie können die beiden Schlüsselwortargumente mischen:

print('Mercury', 'Venus', 'Earth', sep=', ', end=', ')
print('Mars', 'Jupiter', 'Saturn', sep=', ', end=', ')
print('Uranus', 'Neptune', 'Pluto', sep=', ')

Sie erhalten nicht nur eine einzelne Textzeile, sondern alle Elemente werden durch ein Komma getrennt:

Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto

Nichts hindert Sie daran, das Zeilenumbruchzeichen mit zusätzlichen Auffüllungen zu verwenden:

print('Printing in a Nutshell', end='\n * ')
print('Calling Print', end='\n * ')
print('Separating Multiple Arguments', end='\n * ')
print('Preventing Line Breaks')

Es würde den folgenden Text ausdrucken:

Printing in a Nutshell
 * Calling Print
 * Separating Multiple Arguments
 * Preventing Line Breaks

Wie Sie sehen können, akzeptiert das Schlüsselwortargumentendbeliebige Zeichenfolgen.

Note: Beim Durchlaufen von Zeilen in einer Textdatei bleiben die eigenen Zeilenumbruchzeichen erhalten. In Kombination mit dem Standardverhalten der Funktionprint()führt dies zu einem redundanten Zeilenumbruchzeichen:

>>>

>>> with open('file.txt') as file_object:
...     for line in file_object:
...         print(line)
...
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

Nach jeder Textzeile befinden sich zwei Zeilenumbrüche. Sie möchten eine davon entfernen, wie weiter oben in diesem Artikel gezeigt, bevor Sie die Zeile drucken:

print(line.rstrip())

Alternativ können Sie die neue Zeile im Inhalt behalten, die durchprint() angehängte Zeile jedoch automatisch unterdrücken. Verwenden Sie dazu das Schlüsselwortargumentend:

>>>

>>> with open('file.txt') as file_object:
...     for line in file_object:
...         print(line, end='')
...
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

Indem Sie eine Zeile mit einer leeren Zeichenfolge beenden, deaktivieren Sie effektiv eine der Zeilenumbrüche.

Sie werden mit dem Drucken in Python besser vertraut gemacht, aber es liegen noch viele nützliche Informationen vor Ihnen. Im nächsten Unterabschnitt erfahren Sie, wie Sie die Ausgabe derprint()-Funktion abfangen und umleiten.

Drucken in eine Datei

Ob Sie es glauben oder nicht,print()weiß nicht, wie Nachrichten auf Ihrem Bildschirm in Text umgewandelt werden sollen, und ehrlich gesagt muss dies nicht der Fall sein. Dies ist eine Aufgabe für untergeordnete Codeebenen, die Bytes verstehen und wissen, wie sie verschoben werden.

print() ist eine Abstraktion über diese Ebenen und bietet eine praktische Schnittstelle, die lediglich den eigentlichen Druck an einen Stream oderfile-like object delegiert. Ein Stream kann eine beliebige Datei auf Ihrer Festplatte, ein Netzwerk-Socket oder möglicherweise ein In-Memory-Puffer sein.

Darüber hinaus gibt es drei Standard-Streams, die vom Betriebssystem bereitgestellt werden:

  1. stdin: Standardeingabe

  2. stdout: Standardausgabe

  3. stderr: Standardfehler

In Python können Sie über das integrierte Modulsysauf alle Standard-Streams zugreifen:

>>>

>>> import 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

Wie Sie sehen können, ähneln diese vordefinierten Werte dateiähnlichen Objekten mit den Attributenmode undencoding sowie den Methoden.read() und.write() unter vielen anderen.

Standardmäßig istprint() durch das Argumentfile ansys.stdout gebunden, aber Sie können dies ändern. Verwenden Sie dieses Schlüsselwortargument, um eine Datei anzugeben, die im Schreib- oder Anhänge-Modus geöffnet war, sodass Nachrichten direkt dorthin gelangen:

with open('file.txt', mode='w') as file_object:
    print('hello world', file=file_object)

Dadurch ist Ihr Code immun gegen Stream-Umleitungen auf Betriebssystemebene, die möglicherweise erwünscht sind oder nicht.

Note: Versuchen Sie nicht,print() zum Schreiben von Binärdaten zu verwenden, da dies nur für Text geeignet ist.

Rufen Sie einfach die.write() der Binärdatei direkt auf:

with open('file.dat', 'wb') as file_object:
    file_object.write(bytes(4))
    file_object.write(b'\xff')

Wenn Sie Rohbytes in die Standardausgabe schreiben möchten, schlägt dies ebenfalls fehl, dasys.stdout ein Zeichenstrom ist:

>>>

>>> import sys
>>> sys.stdout.write(bytes(4))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: write() argument must be str, not bytes

Sie müssen tiefer graben, um stattdessen einen Handle des zugrunde liegenden Bytestreams zu erhalten:

>>>

>>> import sys
>>> num_bytes_written = sys.stdout.buffer.write(b'\x41\x0a')
A

Dies druckt einen GroßbuchstabenA und ein Zeilenumbruchzeichen, die Dezimalwerten von 65 und 10 in ASCII entsprechen. Sie werden jedoch in hexadezimaler Schreibweise im Byte-Literal codiert.

Beachten Sie, dassprint() keine Kontrolle übercharacter encoding hat. Es liegt in der Verantwortung des Streams, empfangene Unicode-Zeichenfolgen korrekt in Bytes zu codieren. In den meisten Fällen werden Sie die Codierung nicht selbst festlegen, da Sie standardmäßig UTF-8 verwenden möchten. Wenn Sie es wirklich brauchen, vielleicht für Legacy-Systeme, können Sie das Argumentencoding vonopen() verwenden:

with open('file.txt', mode='w', encoding='iso-8859-1') as file_object:
    print('über naïve café', file=file_object)

Anstelle einer echten Datei, die irgendwo in Ihrem Dateisystem vorhanden ist, können Sie eine gefälschte Datei bereitstellen, die sich im Speicher Ihres Computers befindet. Sie werden diese Technik später verwenden, umprint() in Komponententests zu verspotten:

>>>

>>> import io
>>> fake_file = io.StringIO()
>>> print('hello world', file=fake_file)
>>> fake_file.getvalue()
'hello world\n'

Wenn Sie an diesem Punkt angelangt sind, bleibt nur ein Schlüsselwortargument inprint() übrig, das Sie im nächsten Unterabschnitt sehen werden. Es ist wahrscheinlich das am wenigsten genutzte von allen. Es gibt jedoch Zeiten, in denen dies unbedingt erforderlich ist.

Druckanrufe puffern

Im vorherigen Unterabschnitt haben Sie erfahren, dassprint() das Drucken an ein dateiähnliches Objekt wiesys.stdout delegiert. Einige Streams puffern jedoch bestimmte E / A-Vorgänge, um die Leistung zu verbessern, was im Weg stehen kann. Schauen wir uns ein Beispiel an.

Stellen Sie sich vor, Sie schreiben einen Countdown-Timer, der die verbleibende Zeit jede Sekunde an dieselbe Zeile anhängt:

3...2...1...Go!

Ihr erster Versuch könnte ungefähr so ​​aussehen:

import time

num_seconds = 3
for countdown in reversed(range(num_seconds + 1)):
    if countdown > 0:
        print(countdown, end='...')
        time.sleep(1)
    else:
        print('Go!')

Solange die Variablecountdown größer als Null ist, hängt der Code weiterhin Text ohne nachfolgende Zeilenumbruch an und geht dann für eine Sekunde in den Ruhezustand. Wenn der Countdown beendet ist, wirdGo! gedruckt und die Zeile beendet.

Anstatt jede Sekunde herunterzuzählen, läuft das Programm unerwartet drei Sekunden lang verschwenderisch im Leerlauf und druckt dann plötzlich die gesamte Zeile auf einmal:

Terminal with buffered output

Dies liegt daran, dass das Betriebssystem in diesem Fall nachfolgende Schreibvorgänge in die Standardausgabe puffert. Sie müssen wissen, dass es in Bezug auf die Pufferung drei Arten von Streams gibt:

  1. Ungepuffert

  2. Zeilengepuffert

  3. Blockgepuffert

Unbuffered ist selbsterklärend, dh es findet keine Pufferung statt und alle Schreibvorgänge haben sofortige Wirkung. Der Stream vonline-bufferedwartet, bevor E / A-Aufrufe ausgelöst werden, bis irgendwo im Puffer ein Zeilenumbruch angezeigt wird, während der Stream vonblock-bufferedeinfach zulässt, dass der Puffer unabhängig von seinem Inhalt bis zu einer bestimmten Größe gefüllt wird. Die Standardausgabe ist sowohlline-buffered als auchblock-buffered, je nachdem, welches Ereignis zuerst eintritt.

Durch das Puffern wird die Anzahl teurer E / A-Anrufe reduziert. Denken Sie beispielsweise daran, Nachrichten über ein Netzwerk mit hoher Latenz zu senden. Wenn Sie eine Verbindung zu einem Remote-Server herstellen, um Befehle über das SSH-Protokoll auszuführen, kann jeder Ihrer Tastenanschläge tatsächlich ein einzelnes Datenpaket erzeugen, das um Größenordnungen größer ist als seine Nutzlast. Was für ein Overhead! Es wäre sinnvoll zu warten, bis mindestens einige Zeichen eingegeben sind, und sie dann zusammen zu senden. Hier setzt die Pufferung an.

Andererseits kann das Puffern manchmal unerwünschte Auswirkungen haben, wie Sie gerade im Countdown-Beispiel gesehen haben. Um dies zu beheben, können Sieprint() einfach anweisen, den Stream zwangsweise zu leeren, ohne auf ein Zeilenumbruchzeichen im Puffer zu warten. Verwenden Sie dazu das Flagflush:

print(countdown, end='...', flush=True)

Das ist alles. Ihr Countdown sollte jetzt wie erwartet funktionieren, aber nehmen Sie nicht mein Wort dafür. Testen Sie es, um den Unterschied zu erkennen.

Herzliche Glückwünsche! Zu diesem Zeitpunkt haben Sie Beispiele für den Aufruf vonprint() gesehen, die alle Parameter abdecken. Sie kennen ihren Zweck und wann Sie sie verwenden müssen. Das Verständnis der Signatur ist jedoch nur der Anfang. In den nächsten Abschnitten erfahren Sie, warum.

Benutzerdefinierte Datentypen drucken

Bisher haben Sie sich nur mit integrierten Datentypen wie Zeichenfolgen und Zahlen befasst, möchten jedoch häufig Ihre eigenen abstrakten Datentypen drucken. Schauen wir uns verschiedene Arten der Definition an.

Bei einfachen Objekten ohne Logik, deren Zweck darin besteht, Daten zu übertragen, nutzen Sie normalerweisenamedtuple, das in der Standardbibliothek verfügbar ist. Benannte Tupel haben eine übersichtliche Textdarstellung:

>>>

>>> from collections import namedtuple
>>> Person = namedtuple('Person', 'name age')
>>> jdoe = Person('John Doe', 42)
>>> print(jdoe)
Person(name='John Doe', age=42)

Das ist großartig, solange das Speichern von Daten ausreicht. Um jedoch dem TypPersonVerhaltensweisen hinzuzufügen, müssen Sie eventuell eine Klasse definieren. Schauen Sie sich dieses Beispiel an:

class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age

Wenn Sie jetzt eine Instanz der KlassePersonerstellen und versuchen, sie zu drucken, erhalten Sie diese bizarre Ausgabe, die sich erheblich von der entsprechenden Ausgabe vonnamedtupleunterscheidet:

>>>

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

Dies ist die Standarddarstellung von Objekten, die ihre Adresse im Speicher, den entsprechenden Klassennamen und ein Modul umfasst, in dem sie definiert wurden. Sie werden das in Kürze beheben, aber nur zur Veranschaulichung. Als schnelle Problemumgehung können Sienamedtuple und eine benutzerdefinierte Klasse durchinheritance kombinieren:

from collections import namedtuple

class Person(namedtuple('Person', 'name age')):
    pass

IhrePerson-Klasse ist gerade zu einer speziellen Art vonnamedtuple mit zwei Attributen geworden, die Sie anpassen können.

Note: In Python 3 kann die Anweisungpass durch das Literalellipsis (...) ersetzt werden, um einen Platzhalter anzugeben:

def delta(a, b, c):
    ...

Dies verhindert, dass der InterpreterIndentationError aufgrund eines fehlenden eingerückten Codeblocks erhöht.

Dies ist besser als ein einfachesnamedtuple, da Sie nicht nur kostenlos drucken können, sondern der Klasse auch benutzerdefinierte Methoden und Eigenschaften hinzufügen können. Es löst jedoch ein Problem, während ein anderes eingeführt wird. Denken Sie daran, dass Tupel, einschließlich benannter Tupel, in Python unveränderlich sind, sodass sie ihre einmal erstellten Werte nicht ändern können.

Das Entwerfen unveränderlicher Datentypen ist zwar wünschenswert, aber in vielen Fällen möchten Sie, dass sie Änderungen zulassen, sodass Sie wieder mit regulären Klassen arbeiten können.

Note: Nach anderen Sprachen und Frameworks hat Python 3.7data classes eingeführt, die Sie sich als veränderbare Tupel vorstellen können. Auf diese Weise erhalten Sie das Beste aus beiden Welten:

>>>

>>> from dataclasses import dataclass
>>> @dataclass
... class Person:
...     name: str
...     age: int
...
...     def celebrate_birthday(self):
...         self.age += 1
...
>>> jdoe = Person('John Doe', 42)
>>> jdoe.celebrate_birthday()
>>> print(jdoe)
Person(name='John Doe', age=43)

Die Syntax fürvariable annotations, die erforderlich ist, um Klassenfelder mit ihren entsprechenden Typen anzugeben, wurde in Python 3.6 definiert.

Aus früheren Unterabschnitten wissen Sie bereits, dassprint() implizit die integrierte Funktionstr()aufruft, um ihre Positionsargumente in Zeichenfolgen umzuwandeln. Wenn Siestr() manuell für eine Instanz der regulärenPerson-Klasse aufrufen, erhalten Sie dasselbe Ergebnis wie beim Drucken:

>>>

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

str() sucht wiederum nach einem von zweimagic methods innerhalb des Klassenkörpers, den Sie normalerweise implementieren. Wenn es keine findet, wird auf die hässliche Standarddarstellung zurückgegriffen. Diese magischen Methoden sind in der Reihenfolge der Suche:

  1. def __str__(self)

  2. def __repr__(self)

Der erste wird empfohlen, einen kurzen, für Menschen lesbaren Text zurückzugeben, der Informationen aus den wichtigsten Attributen enthält. Schließlich möchten Sie beim Drucken von Objekten keine vertraulichen Daten wie Benutzerkennwörter verfügbar machen.

Der andere sollte jedoch vollständige Informationen zu einem Objekt bereitstellen, damit sein Status aus einer Zeichenfolge wiederhergestellt werden kann. Im Idealfall sollte gültiger Python-Code zurückgegeben werden, damit Sie ihn direkt aneval() übergeben können:

>>>

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

Beachten Sie die Verwendung einer anderen integrierten Funktion,repr(), die immer versucht,.__repr__() in einem Objekt aufzurufen, aber auf die Standarddarstellung zurückgreift, wenn diese Methode nicht gefunden wird.

Note: Obwohlprint() selbststr() für das Typ-Casting verwendet, delegieren einige zusammengesetzte Datentypen, dierepr() für ihre Mitglieder aufrufen. Dies geschieht beispielsweise bei Listen und Tupeln.

Betrachten Sie diese Klasse mit beiden magischen Methoden, die alternative Zeichenfolgendarstellungen desselben Objekts zurückgeben:

class User:
    def __init__(self, login, password):
        self.login = login
        self.password = password

    def __str__(self):
        return self.login

    def __repr__(self):
        return f"User('{self.login}', '{self.password}')"

Wenn Sie ein einzelnes Objekt der KlasseUser drucken, wird das Kennwort nicht angezeigt, daprint(user)str(user) aufruft, wodurch schließlichuser.__str__() aufgerufen wird:

>>>

>>> user = User('jdoe', 's3cret')
>>> print(user)
jdoe

Wenn Sie jedoch dieselbeuser-Variable in eine Liste einfügen, indem Sie sie in eckige Klammern setzen, wird das Kennwort deutlich sichtbar:

>>>

>>> print([user])
[User('jdoe', 's3cret')]

Dies liegt daran, dass Sequenzen wie Listen und Tupel ihre.__str__()-Methode implementieren, sodass alle ihre Elemente zuerst mitrepr() konvertiert werden.

Python gibt Ihnen viel Freiheit beim Definieren Ihrer eigenen Datentypen, wenn keiner der integrierten Ihren Anforderungen entspricht. Einige von ihnen, wie z. B. benannte Tupel und Datenklassen, bieten Zeichenfolgendarstellungen, die gut aussehen, ohne dass Sie arbeiten müssen. Für die größtmögliche Flexibilität müssen Sie jedoch eine Klasse definieren und ihre oben beschriebenen magischen Methoden überschreiben.

Grundlegendes zu Python Print

Sie wissen, dasshowprint() zu diesem Zeitpunkt recht gut verwendet, aber wenn Siewhat kennen, können Sie es noch effektiver und bewusster verwenden. Nachdem Sie diesen Abschnitt gelesen haben, werden Sie verstehen, wie sich das Drucken in Python im Laufe der Jahre verbessert hat.

Drucken ist eine Funktion in Python 3

Sie haben gesehen, dassprint() eine Funktion in Python 3 ist. Insbesondere handelt es sich um eine integrierte Funktion, sodass Sie sie nicht von irgendwoher importieren müssen:

>>>

>>> print

Es ist immer im globalen Namespace verfügbar, sodass Sie es direkt aufrufen können. Sie können jedoch auch über ein Modul aus der Standardbibliothek darauf zugreifen:

>>>

>>> import builtins
>>> builtins.print

Auf diese Weise können Sie Namenskollisionen mit benutzerdefinierten Funktionen vermeiden. Angenommen, Sie wolltenredefineprint(), damit keine nachfolgende neue Zeile angehängt wird. Gleichzeitig wollten Sie die ursprüngliche Funktion inprintln() umbenennen:

>>>

>>> import builtins
>>> println = builtins.print
>>> def print(*args, **kwargs):
...     builtins.print(*args, **kwargs, end='')
...
>>> println('hello')
hello
>>> print('hello\n')
hello

Jetzt haben Sie zwei separate Druckfunktionen, genau wie in der Programmiersprache Java. Sie werden die benutzerdefinierten Funktionen vonprint()auch später inmocking sectiondefinieren. Beachten Sie außerdem, dass Sieprint() überhaupt nicht überschreiben könnten, wenn dies keine Funktion wäre.

Andererseits istprint() keine Funktion im mathematischen Sinne, da es keinen anderen aussagekräftigen Wert als das impliziteNone zurückgibt:

>>>

>>> value = print('hello world')
hello world
>>> print(value)
None

Solche Funktionen sind in der Tat Prozeduren oder Unterprogramme, die Sie aufrufen, um eine Art Nebenwirkung zu erzielen, die letztendlich eine Änderung eines globalen Zustands darstellt. Im Fall vonprint() zeigt dieser Nebeneffekt eine Meldung in der Standardausgabe oder das Schreiben in eine Datei.

Daprint() eine Funktion ist, hat sie eine genau definierte Signatur mit bekannten Attributen. Sie können diedocumentation schnell mit dem Editor Ihrer Wahl finden, ohne sich an eine seltsame Syntax für die Ausführung einer bestimmten Aufgabe erinnern zu müssen.

Außerdem sind Funktionen fürextend einfacher. Das Hinzufügen einer neuen Funktion zu einer Funktion ist so einfach wie das Hinzufügen eines weiteren Schlüsselwortarguments, während das Ändern der Sprache zur Unterstützung dieser neuen Funktion viel umständlicher ist. Denken Sie beispielsweise an Stream-Umleitung oder Puffer-Leerung.

Ein weiterer Vorteil vonprint() als Funktion istcomposability. Funktionen sind in Python sogenanntefirst-class objects oderfirst-class citizens. Dies ist eine ausgefallene Art zu sagen, dass sie Werte wie Zeichenfolgen oder Zahlen sind. Auf diese Weise können Sie einer Variablen eine Funktion zuweisen, sie an eine andere Funktion übergeben oder sogar eine von einer anderen zurückgeben. print() unterscheidet sich in dieser Hinsicht nicht. Sie können es beispielsweise für die Abhängigkeitsinjektion nutzen:

def download(url, log=print):
    log(f'Downloading {url}')
    # ...

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

download('/js/app.js', log=custom_print)

Hier können Sie mit dem Parameterlog eine Rückruffunktion einfügen, die standardmäßigprint() verwendet, aber beliebig aufrufbar sein kann. In diesem Beispiel wird das Drucken vollständig deaktiviert, indemprint() durch eine Dummy-Funktion ersetzt wird, die nichts bewirkt.

Note: Adependency ist ein beliebiger Code, der von einem anderen Codebit benötigt wird.

Dependency injection ist eine Technik, die im Code-Design verwendet wird, um sie testbarer, wiederverwendbarer und für Erweiterungen offener zu machen. Sie können dies erreichen, indem Sie indirekt über abstrakte Schnittstellen auf Abhängigkeiten verweisen und diese inpushtatt inpullangeben.

Es gibt eine lustige Erklärung für die im Internet verbreitete Abhängigkeitsinjektion:

Abhängigkeitsinjektion für Fünfjährige

Wenn Sie Dinge selbst aus dem Kühlschrank holen, können Sie Probleme verursachen. Sie könnten die Tür offen lassen, Sie könnten etwas bekommen, das Mama oder Papa nicht wollen. Möglicherweise suchen Sie sogar nach etwas, das wir nicht einmal haben oder das abgelaufen ist.

Was Sie tun sollten, ist zu sagen: "Ich brauche etwas zum Mittagessen zu trinken", und dann werden wir sicherstellen, dass Sie etwas haben, wenn Sie sich zum Essen hinsetzen.

-John Munsch, 28 October 2009. (Source)

Mit der Komposition können Sie einige Funktionen zu einer neuen der gleichen Art kombinieren. Sehen wir uns dies in Aktion an, indem wir eine benutzerdefinierteerror()-Funktion angeben, die im Standardfehlerstrom gedruckt wird und allen Nachrichten eine bestimmte Protokollstufe voranstellt:

>>>

>>> from functools import partial
>>> import sys
>>> redirect = lambda function, stream: partial(function, file=stream)
>>> prefix = lambda function, prefix: partial(function, prefix)
>>> error = prefix(redirect(print, sys.stderr), '[ERROR]')
>>> error('Something went wrong')
[ERROR] Something went wrong

Diese benutzerdefinierte Funktion verwendetpartial functions, um den gewünschten Effekt zu erzielen. Es handelt sich um ein fortschrittliches Konzept, das dem Paradigma vonfunctional programmingentlehnt ist, sodass Sie sich vorerst nicht zu sehr mit diesem Thema befassen müssen. Wenn Sie sich jedoch für dieses Thema interessieren, empfehlen wir Ihnen, einen Blick auf das Modulfunctoolszu werfen.

Im Gegensatz zu Anweisungen sind Funktionen Werte. Das heißt, Sie können sie mitexpressions mischen, insbesondere mitlambda expressions. Anstatt eine vollständige Funktion zu definieren, durch dieprint() ersetzt werden soll, können Sie einen anonymen Lambda-Ausdruck erstellen, der sie aufruft:

>>>

>>> download('/js/app.js', lambda msg: print('[INFO]', msg))
[INFO] Downloading /js/app.js

Da jedoch ein Lambda-Ausdruck an Ort und Stelle definiert ist, kann an keiner anderen Stelle im Code darauf verwiesen werden.

Note: In Python können Sie keine Anweisungen wie Zuweisungen, bedingte Anweisungen, Schleifen usw. inanonymous lambda function einfügen. Es muss ein einziger Ausdruck sein!

Eine andere Art von Ausdruck ist ein ternärer bedingter Ausdruck:

>>>

>>> user = 'jdoe'
>>> print('Hi!') if user is None else print(f'Hi, {user}.')
Hi, jdoe.

Python hat sowohlconditional statements als auchconditional expressions. Letzteres wird zu einem einzelnen Wert ausgewertet, der einer Variablen zugewiesen oder an eine Funktion übergeben werden kann. Im obigen Beispiel interessieren Sie sich eher für den Nebeneffekt als für den Wert, derNone ergibt. Sie ignorieren ihn also einfach.

Wie Sie sehen können, ermöglichen Funktionen eine elegante und erweiterbare Lösung, die mit dem Rest der Sprache übereinstimmt. Im nächsten Unterabschnitt erfahren Sie, wie das Fehlen vonprint() als Funktion viele Kopfschmerzen verursacht hat.

Drucken war eine Aussage in Python 2

Astatement ist eine Anweisung, die bei der Ausführung einen Nebeneffekt hervorrufen kann, jedoch niemals einen Wert ergibt. Mit anderen Worten, Sie können eine Anweisung nicht drucken oder einer Variablen wie der folgenden zuweisen:

result = print 'hello world'

Dies ist ein Syntaxfehler in Python 2.

Hier sind einige weitere Beispiele für Anweisungen in Python:

  • Zuordnung: =

  • bedingt: if

  • Schleife: while

  • assertion:assert

Note: Python 3.8 bringt ein umstritteneswalrus operator (:=), das einassignment expression ist. Damit können Sie einen Ausdruck auswerten und das Ergebnis gleichzeitig einer Variablen zuordnen, auch innerhalb eines anderen Ausdrucks!

Schauen Sie sich dieses Beispiel an, das eine teure Funktion einmal aufruft und das Ergebnis dann für weitere Berechnungen wiederverwendet:

# Python 3.8+
values = [y := f(x), y**2, y**3]

Dies ist nützlich, um den Code zu vereinfachen, ohne seine Effizienz zu verlieren. In der Regel ist performanter Code ausführlicher:

y = f(x)
values = [y, y**2, y**3]

Die Kontroverse hinter dieser neuen Syntax hat viele Argumente hervorgerufen. Eine Fülle negativer Kommentare und hitziger Debatten führte schließlich dazu, dass Guido van Rossum von der PositionBenevolent Dictator For Life oder BDFL zurücktrat.

Anweisungen bestehen normalerweise aus reservierten Schlüsselwörtern wieif,for oderprint, die in der Sprache eine feste Bedeutung haben. Sie können sie nicht verwenden, um Ihre Variablen oder andere Symbole zu benennen. Aus diesem Grund ist es in Python 2 nicht möglich, die Anweisungprintneu zu definieren oder zu verspotten. Du bleibst bei dem, was du bekommst.

Darüber hinaus können Sie nicht aus anonymen Funktionen drucken, da Anweisungen in Lambda-Ausdrücken nicht akzeptiert werden:

>>>

>>> lambda: print 'hello world'
  File "", line 1
    lambda: print 'hello world'
                ^
SyntaxError: invalid syntax

Die Syntax der Anweisungprintist nicht eindeutig. Manchmal können Sie der Nachricht Klammern hinzufügen, die völlig optional sind:

>>>

>>> print 'Please wait...'
Please wait...
>>> print('Please wait...')
Please wait...

Zu anderen Zeiten ändern sie die Art und Weise, wie die Nachricht gedruckt wird:

>>>

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

Die Verkettung von Zeichenfolgen kann aufgrund inkompatibler Typen, die Sie manuell verarbeiten müssen, einTypeError erhöhen, zum Beispiel:

>>>

>>> values = ['jdoe', 'is', 42, 'years old']
>>> print ' '.join(map(str, values))
jdoe is 42 years old

Vergleichen Sie dies mit ähnlichem Code in Python 3, der das Entpacken von Sequenzen nutzt:

>>>

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

Es gibt keine Schlüsselwortargumente für allgemeine Aufgaben wie das Leeren des Puffers oder die Stream-Umleitung. Sie müssen sich stattdessen an die skurrile Syntax erinnern. Selbst die eingebautehelp()-Funktion ist in Bezug auf dieprint-Anweisung nicht so hilfreich:

>>>

>>> help(print)
  File "", line 1
    help(print)
             ^
SyntaxError: invalid syntax

Das Entfernen nachfolgender Zeilenumbrüche funktioniert nicht ganz richtig, da dadurch ein unerwünschter Speicherplatz hinzugefügt wird. Sie können nicht mehrereprint-Anweisungen zusammenstellen. Darüber hinaus müssen Sie bei der Zeichencodierung besonders sorgfältig vorgehen.

Die Liste der Probleme geht weiter und weiter. Wenn Sie neugierig sind, können Sie zuprevious section zurückkehren und nach detaillierteren Erklärungen der Syntax in Python 2 suchen.

Sie können jedoch einige dieser Probleme mit einem viel einfacheren Ansatz abmildern. Es stellte sich heraus, dass die Funktionprint()zurückportiert wurde, um die Migration zu Python 3 zu vereinfachen. Sie können es aus einem speziellen__future__-Modul importieren, das eine Auswahl von Sprachfunktionen bereitstellt, die in späteren Python-Versionen veröffentlicht wurden.

Note: Sie können zukünftige Funktionen sowie eingebaute Sprachkonstrukte wie die Anweisungwithimportieren.

Überprüfen Sie das Modul, um herauszufinden, welche Funktionen Ihnen genau zur Verfügung stehen:

>>>

>>> import __future__
>>> __future__.all_feature_names
['nested_scopes',
 'generators',
 'division',
 'absolute_import',
 'with_statement',
 'print_function',
 'unicode_literals']

Sie könnten auchdir(__future__) aufrufen, aber das würde viele uninteressante interne Details des Moduls anzeigen.

Um die Funktionprint()in Python 2 zu aktivieren, müssen Sie diese Importanweisung am Anfang Ihres Quellcodes hinzufügen:

from __future__ import print_function

Ab sofort ist die Anweisungprintnicht mehr verfügbar, aber Sie haben die Funktionprint()zur Verfügung. Beachten Sie, dass es nicht dieselbe Funktion wie in Python 3 ist, da das Schlüsselwortargumentflushfehlt, die restlichen Argumente jedoch identisch sind.

Davon abgesehen erspart es Ihnen nicht, die Zeichenkodierungen ordnungsgemäß zu verwalten.

Hier ist ein Beispiel für den Aufruf derprint()-Funktion in Python 2:

>>>

>>> from __future__ import print_function
>>> import sys
>>> print('I am a function in Python', sys.version_info.major)
I am a function in Python 2

Sie haben jetzt eine Vorstellung davon, wie sich das Drucken in Python entwickelt hat, und vor allem, warum diese rückwärts inkompatiblen Änderungen erforderlich waren. Wenn Sie dies wissen, werden Sie sicherlich ein besserer Python-Programmierer.

Drucken mit Stil

Wenn Sie der Meinung sind, dass es beim Drucken nur darum geht, Pixel auf dem Bildschirm zu beleuchten, haben Sie technisch gesehen Recht. Es gibt jedoch Möglichkeiten, es cool aussehen zu lassen. In diesem Abschnitt erfahren Sie, wie Sie komplexe Datenstrukturen formatieren, Farben und andere Dekorationen hinzufügen, Schnittstellen erstellen, Animationen verwenden und sogar Sounds mit Text abspielen können!

Verschachtelte Datenstrukturen schön drucken

Mithilfe von Computersprachen können Sie sowohl Daten als auch ausführbaren Code strukturiert darstellen. Im Gegensatz zu Python bieten Ihnen die meisten Sprachen jedoch viel Freiheit bei der Verwendung von Leerzeichen und Formatierungen. Dies kann beispielsweise bei der Komprimierung nützlich sein, führt jedoch manchmal zu weniger lesbarem Code.

Beim Pretty-Printing geht es darum, Daten oder Code für das menschliche Auge attraktiver erscheinen zu lassen, damit sie leichter verstanden werden können. Dies erfolgt durch Einrücken bestimmter Zeilen, Einfügen von Zeilenumbrüchen, Neuanordnen von Elementen usw.

Python enthält das Modulpprint in seiner Standardbibliothek, mit dem Sie große Datenstrukturen, die nicht in eine einzelne Zeile passen, hübsch drucken können. Viele beliebteREPL-Tools, einschließlichJupyterLab and IPython, verwenden sie standardmäßig anstelle der regulärenprint()-Funktion, da sie benutzerfreundlicher gedruckt werden.

Note: Geben Sie den folgenden Befehl ein, um das hübsche Drucken in IPython umzuschalten:

>>>

In [1]: %pprint
Pretty printing has been turned OFF
In [2]: %pprint
Pretty printing has been turned ON

Dies ist ein Beispiel fürMagic in IPython. Es gibt viele integrierte Befehle, die mit einem Prozentzeichen (%) beginnen, aber Sie können mehr überPyPI finden oder sogar Ihre eigenen erstellen.

Wenn Sie keinen Zugriff auf die ursprünglicheprint()-Funktion haben möchten, können Sie sie durch Importumbenennung durchpprint() in Ihrem Code ersetzen:

>>>

>>> from pprint import pprint as print
>>> print

Persönlich möchte ich beide Funktionen zur Hand haben, daher verwende ich lieberppals kurzen Alias:

from pprint import pprint as pp

Auf den ersten Blick gibt es kaum einen Unterschied zwischen den beiden Funktionen, und in einigen Fällen gibt es praktisch keinen:

>>>

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

Dies liegt daran, dasspprint()repr() anstelle der üblichenstr() für das Typ-Casting aufruft, sodass Sie die Ausgabe als Python-Code auswerten können, wenn Sie möchten. Die Unterschiede werden deutlich, wenn Sie komplexere Datenstrukturen einspeisen:

>>>

>>> data = {'powers': [x**10 for x in range(10)]}
>>> pp(data)
{'powers': [0,
            1,
            1024,
            59049,
            1048576,
            9765625,
            60466176,
            282475249,
            1073741824,
            3486784401]}

Die Funktion wendet eine angemessene Formatierung an, um die Lesbarkeit zu verbessern. Sie können sie jedoch mit einigen Parametern noch weiter anpassen. Sie können beispielsweise eine tief verschachtelte Hierarchie einschränken, indem Sie eine Ellipse unterhalb einer bestimmten Ebene anzeigen:

>>>

>>> cities = {'USA': {'Texas': {'Dallas': ['Irving']}}}
>>> pp(cities, depth=3)
{'USA': {'Texas': {'Dallas': [...]}}}

Das gewöhnlicheprint() verwendet ebenfalls Ellipsen, jedoch zum Anzeigen rekursiver Datenstrukturen, die einen Zyklus bilden, um Stapelüberlauffehler zu vermeiden:

>>>

>>> items = [1, 2, 3]
>>> items.append(items)
>>> print(items)
[1, 2, 3, [...]]

pprint() ist jedoch expliziter, indem es die eindeutige Identität eines selbstreferenzierenden Objekts enthält:

>>>

>>> pp(items)
[1, 2, 3, ]
>>> id(items)
140635757287688

Das letzte Element in der Liste ist dasselbe Objekt wie die gesamte Liste.

Note: Rekursive oder sehr große Datenmengen können auch mit dem Modulreprlib verarbeitet werden:

>>>

>>> import reprlib
>>> reprlib.repr([x**10 for x in range(10)])
'[0, 1, 1024, 59049, 1048576, 9765625, ...]'

Dieses Modul unterstützt die meisten integrierten Typen und wird vom Python-Debugger verwendet.

pprint() sortiert vor dem Drucken automatisch die Wörterbuchschlüssel für Sie, was einen konsistenten Vergleich ermöglicht. Wenn Sie Zeichenfolgen vergleichen, ist Ihnen eine bestimmte Reihenfolge serialisierter Attribute häufig egal. In jedem Fall ist es immer am besten, die tatsächlichen Wörterbücher vor der Serialisierung zu vergleichen.

Wörterbücher stellen häufigJSON data dar, was im Internet weit verbreitet ist. Um ein Wörterbuch korrekt in eine gültige JSON-formatierte Zeichenfolge zu serialisieren, können Sie das Moduljson verwenden. Es hat auch hübsche Druckfunktionen:

>>>

>>> import json
>>> data = {'username': 'jdoe', 'password': 's3cret'}
>>> ugly = json.dumps(data)
>>> pretty = json.dumps(data, indent=4, sort_keys=True)
>>> print(ugly)
{"username": "jdoe", "password": "s3cret"}
>>> print(pretty)
{
    "password": "s3cret",
    "username": "jdoe"
}

Beachten Sie jedoch, dass Sie das Drucken selbst durchführen müssen, da dies normalerweise nicht der Fall ist. In ähnlicher Weise verfügt das Modulpprintüber eine zusätzliche Funktionpformat(), die eine Zeichenfolge zurückgibt, falls Sie etwas anderes als das Drucken tun mussten.

Überraschenderweise entspricht die Signatur vonpprint() nicht der vonprint(). Sie können nicht einmal mehr als ein Positionsargument übergeben, das zeigt, wie sehr es sich auf das Drucken von Datenstrukturen konzentriert.

Hinzufügen von Farben mit ANSI-Escape-Sequenzen

Je ausgefeilter die PCs wurden, desto besser waren die Grafiken und desto mehr Farben konnten angezeigt werden. Verschiedene Anbieter hatten jedoch ihre eigene Vorstellung vom API-Design zur Steuerung. Dies änderte sich vor einigen Jahrzehnten, als die Mitarbeiter des American National Standards Institute beschlossen, es durch die Definition vonANSI escape codes zu vereinheitlichen.

Die meisten heutigen Terminalemulatoren unterstützen diesen Standard bis zu einem gewissen Grad. Bis vor kurzem war das Windows-Betriebssystem eine bemerkenswerte Ausnahme. Wenn Sie die beste Portabilität wünschen, verwenden Sie daher die Bibliothekcoloramain Python. Es übersetzt ANSI-Codes in die entsprechenden Gegenstücke in Windows, während sie in anderen Betriebssystemen intakt bleiben.

Mit dem folgenden Befehl können Sie überprüfen, ob Ihr Terminal eine Teilmenge der ANSI-Escape-Sequenzen versteht, die sich beispielsweise auf Farben beziehen.

$ tput colors

Mein Standardterminal unter Linux sagt, dass es 256 verschiedene Farben anzeigen kann, während xterm mir nur 8 gibt. Der Befehl würde eine negative Zahl zurückgeben, wenn Farben nicht unterstützt würden.

ANSI-Escape-Sequenzen sind wie eine Auszeichnungssprache für das Terminal. In HTML arbeiten Sie mit Tags wie<b> oder<i>, um das Aussehen von Elementen im Dokument zu ändern. Diese Tags werden mit Ihren Inhalten gemischt, sind jedoch selbst nicht sichtbar. Ebenso werden Escape-Codes nicht im Terminal angezeigt, solange sie erkannt werden. Andernfalls werden sie in der wörtlichen Form angezeigt, als würden Sie die Quelle einer Website anzeigen.

Wie der Name schon sagt, muss eine Sequenz mit dem nicht druckbaren Zeichen[.kbd .key-escape]#Esc # beginnen, dessen ASCII-Wert 27 ist und manchmal als0x1b hexadezimal oder033 oktal bezeichnet wird. Sie können Python-Zahlenliterale verwenden, um schnell zu überprüfen, ob es sich tatsächlich um dieselbe Zahl handelt:

>>>

>>> 27 == 0x1b == 0o33
True

Zusätzlich können Sie es mit der Escape-Sequenz\ein der Shell erhalten:

$ echo -e "\e"

Die häufigsten ANSI-Escape-Sequenzen haben folgende Form:

Element Beschreibung Beispiel

[.kbd .key-escape]#Esc #

nicht druckbares Escapezeichen

\033

[

eckige Klammer öffnen

[

numerischer Code

eine oder mehrere durch; getrennte Zahlen

0

Zeichencode

Groß- oder Kleinbuchstaben

m

Dienumeric code können eine oder mehrere durch ein Semikolon getrennte Zahlen sein, während diecharacter code nur ein Buchstabe sind. Ihre spezifische Bedeutung wird durch den ANSI-Standard definiert. Um beispielsweise alle Formatierungen zurückzusetzen, geben Sie einen der folgenden Befehle ein, die den Code Null und den Buchstabenm verwenden:

$ echo -e "\e[0m"
$ echo -e "\x1b[0m"
$ echo -e "\033[0m"

Am anderen Ende des Spektrums haben Sie zusammengesetzte Codewerte. Um Vordergrund und Hintergrund mit RGB-Kanälen einzustellen, können Sie mehrere Zahlen angeben, da Ihr Terminal eine 24-Bit-Tiefe unterstützt:

$ echo -e "\e[38;2;0;0;0m\e[48;2;255;255;255mBlack on white\e[0m"

Es ist nicht nur die Textfarbe, die Sie mit den ANSI-Escape-Codes festlegen können. Sie können beispielsweise das Terminalfenster löschen und scrollen, den Hintergrund ändern, den Cursor bewegen, den Text blinken lassen oder ihn mit einer Unterstreichung dekorieren.

In Python würden Sie wahrscheinlich eine Hilfsfunktion schreiben, um beliebige Codes in eine Sequenz zu verpacken:

>>>

>>> def esc(code):
...     return f'\033[{code}m'
...
>>> print(esc('31;1;4') + 'really' + esc(0) + ' important')

Dadurch wird das Wortreally in roter, fetter und unterstrichener Schrift angezeigt:

Text formatted with ANSI escape codes

Es gibt jedoch übergeordnete Abstraktionen über ANSI-Escape-Codes, wie z. B. die erwähntecolorama-Bibliothek, sowie Tools zum Erstellen von Benutzeroberflächen in der Konsole.

Erstellen von Konsolenbenutzeroberflächen

Während das Spielen mit ANSI-Escape-Codes zweifellos eine Menge Spaß macht, hätten Sie in der realen Welt eher abstraktere Bausteine, um eine Benutzeroberfläche zusammenzustellen. Es gibt einige Bibliotheken, die ein so hohes Maß an Kontrolle über das Terminal bieten, abercurses scheint die beliebteste Wahl zu sein.

Note: Um die Bibliothekcurses in Windows zu verwenden, müssen Sie ein Paket eines Drittanbieters installieren:

C:\> pip install windows-curses

Dies liegt daran, dasscurses in der Standardbibliothek der Python-Distribution für Windows nicht verfügbar ist.

In erster Linie können Sie damit in unabhängigen grafischen Widgets anstelle eines Textblobs denken. Außerdem hast du viel Freiheit, deinen inneren Künstler auszudrücken, weil es wirklich so ist, als würde man eine leere Leinwand malen. Die Bibliothek verbirgt die Komplexität, mit verschiedenen Terminals umgehen zu müssen. Abgesehen davon bietet es eine hervorragende Unterstützung für Tastaturereignisse, die zum Schreiben von Videospielen nützlich sein können.

Wie wäre es mit einem Retro-Schlangenspiel? Erstellen wir einen Python-Schlangensimulator:

The retro snake game built with curses library

Zunächst müssen Sie das Modulcurses importieren. Da der Status eines laufenden Terminals geändert wird, ist es wichtig, Fehler zu behandeln und den vorherigen Status ordnungsgemäß wiederherzustellen. Sie können dies manuell tun, aber die Bibliothek wird mit einem praktischen Wrapper für Ihre Hauptfunktion geliefert:

import curses

def main(screen):
    pass

if __name__ == '__main__':
    curses.wrapper(main)

Beachten Sie, dass die Funktion einen Verweis auf das Bildschirmobjekt, auch alsstdscr bezeichnet, akzeptieren muss, den Sie später für zusätzliche Einstellungen verwenden werden.

Wenn Sie dieses Programm jetzt ausführen, werden keine Effekte angezeigt, da es sofort beendet wird. Sie können jedoch eine kleine Verzögerung hinzufügen, um einen kurzen Blick darauf zu werfen:

import time, curses

def main(screen):
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

Diesmal war der Bildschirm für eine Sekunde völlig leer, aber der Cursor blinkte immer noch. Um es auszublenden, rufen Sie einfach eine der im Modul definierten Konfigurationsfunktionen auf:

import time, curses

def main(screen):
    curses.curs_set(0)  # Hide the cursor
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

Definieren wir die Schlange als Liste von Punkten in Bildschirmkoordinaten:

snake = [(0, i) for i in reversed(range(20))]

Der Kopf der Schlange ist immer das erste Element in der Liste, während der Schwanz das letzte ist. Die ursprüngliche Form der Schlange ist horizontal und beginnt in der oberen linken Ecke des Bildschirms und zeigt nach rechts. Während seine y-Koordinate bei Null bleibt, nimmt seine x-Koordinate von Kopf bis Schwanz ab.

Um die Schlange zu zeichnen, beginnen Sie mit dem Kopf und folgen dann den verbleibenden Segmenten. Jedes Segment enthält(y, x)-Koordinaten, sodass Sie sie entpacken können:

# Draw the snake
screen.addstr(*snake[0], '@')
for segment in snake[1:]:
    screen.addstr(*segment, '*')

Wenn Sie diesen Code jetzt ausführen, wird nichts angezeigt, da Sie den Bildschirm anschließend explizit aktualisieren müssen:

import time, curses

def main(screen):
    curses.curs_set(0)  # Hide the cursor

    snake = [(0, i) for i in reversed(range(20))]

    # Draw the snake
    screen.addstr(*snake[0], '@')
    for segment in snake[1:]:
        screen.addstr(*segment, '*')

    screen.refresh()
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

Sie möchten die Schlange in eine von vier Richtungen bewegen, die als Vektoren definiert werden können. Schließlich ändert sich die Richtung als Reaktion auf einen Pfeiltastenanschlag, sodass Sie ihn möglicherweise an die Schlüsselcodes der Bibliothek anschließen:

directions = {
    curses.KEY_UP: (-1, 0),
    curses.KEY_DOWN: (1, 0),
    curses.KEY_LEFT: (0, -1),
    curses.KEY_RIGHT: (0, 1),
}

direction = directions[curses.KEY_RIGHT]

Wie bewegt sich eine Schlange? Es stellt sich heraus, dass sich nur sein Kopf wirklich an einen neuen Ort bewegt, während sich alle anderen Segmente dorthin bewegen. In jedem Schritt bleiben fast alle Segmente bis auf Kopf und Schwanz gleich. Angenommen, die Schlange wächst nicht, können Sie den Schwanz entfernen und am Anfang der Liste einen neuen Kopf einfügen:

# Move the snake
snake.pop()
snake.insert(0, tuple(map(sum, zip(snake[0], direction))))

Um die neuen Koordinaten des Kopfes zu erhalten, müssen Sie den Richtungsvektor hinzufügen. Das Hinzufügen von Tupeln in Python führt jedoch zu einem größeren Tupel anstelle der algebraischen Summe der entsprechenden Vektorkomponenten. Eine Möglichkeit, dies zu beheben, besteht darin, die integrierten Funktionenzip(),sum() undmap() zu verwenden.

Die Richtung ändert sich per Tastendruck, daher müssen Sie.getch() aufrufen, um den Code für die gedrückte Taste zu erhalten. Wenn die gedrückte Taste jedoch nicht den zuvor als Wörterbuchtasten definierten Pfeiltasten entspricht, ändert sich die Richtung nicht:

# Change direction on arrow keystroke
direction = directions.get(screen.getch(), direction)

Standardmäßig ist.getch() jedoch ein blockierender Aufruf, der verhindern würde, dass sich die Schlange bewegt, es sei denn, es gab einen Tastenanschlag. Daher müssen Sie den Anruf nicht blockieren, indem Sie eine weitere Konfiguration hinzufügen:

def main(screen):
    curses.curs_set(0)    # Hide the cursor
    screen.nodelay(True)  # Don't block I/O calls

Du bist fast fertig, aber es gibt nur noch eine letzte Sache. Wenn Sie diesen Code jetzt in einer Schleife ausführen, scheint die Schlange zu wachsen, anstatt sich zu bewegen. Dies liegt daran, dass Sie den Bildschirm vor jeder Iteration explizit löschen müssen.

Schließlich ist dies alles, was Sie brauchen, um das Schlangenspiel in Python zu spielen:

import time, curses

def main(screen):
    curses.curs_set(0)    # Hide the cursor
    screen.nodelay(True)  # Don't block I/O calls

    directions = {
        curses.KEY_UP: (-1, 0),
        curses.KEY_DOWN: (1, 0),
        curses.KEY_LEFT: (0, -1),
        curses.KEY_RIGHT: (0, 1),
    }

    direction = directions[curses.KEY_RIGHT]
    snake = [(0, i) for i in reversed(range(20))]

    while True:
        screen.erase()

        # Draw the snake
        screen.addstr(*snake[0], '@')
        for segment in snake[1:]:
            screen.addstr(*segment, '*')

        # Move the snake
        snake.pop()
        snake.insert(0, tuple(map(sum, zip(snake[0], direction))))

        # Change direction on arrow keystroke
        direction = directions.get(screen.getch(), direction)

        screen.refresh()
        time.sleep(0.1)

if __name__ == '__main__':
    curses.wrapper(main)

Dies kratzt lediglich an der Oberfläche der Möglichkeiten, die das Modulcurseseröffnet. Sie können es für Spieleentwicklungen wie diese oder mehr geschäftsorientierte Anwendungen verwenden.

Mit coolen Animationen leben

Animationen können nicht nur die Benutzeroberfläche für das Auge attraktiver machen, sondern auch die allgemeine Benutzererfahrung verbessern. Wenn Sie dem Benutzer beispielsweise frühzeitig Feedback geben, wird er wissen, ob Ihr Programm noch funktioniert oder ob es Zeit ist, es zu beenden.

Um Text im Terminal zu animieren, müssen Sie den Cursor frei bewegen können. Sie können dies mit einem der zuvor genannten Tools tun, nämlich ANSI-Escape-Codes oder der Bibliothekcurses. Ich möchte Ihnen jedoch einen noch einfacheren Weg zeigen.

Wenn die Animation auf eine einzelne Textzeile beschränkt werden kann, sind Sie möglicherweise an zwei speziellen Escape-Zeichenfolgen interessiert:

  • Wagenrücklauf: \r

  • Rücktaste: \b

Der erste bewegt den Cursor an den Anfang der Zeile, während der zweite den Cursor nur um ein Zeichen nach links bewegt. Beide arbeiten zerstörungsfrei, ohne bereits geschriebenen Text zu überschreiben.

Schauen wir uns einige Beispiele an.

Oft möchten Sie eine Artspinning wheelanzeigen, um eine laufende Arbeit anzuzeigen, ohne genau zu wissen, wie viel Zeit noch zu Ende ist:

Indefinite animation in the terminal

Viele Befehlszeilentools verwenden diesen Trick beim Herunterladen von Daten über das Netzwerk. Sie können eine wirklich einfache Stop-Motion-Animation aus einer Folge von Zeichen erstellen, die im Round-Robin-Modus durchlaufen:

from itertools import cycle
from time import sleep

for frame in cycle(r'-\|/-\|/'):
    print('\r', frame, sep='', end='', flush=True)
    sleep(0.2)

Die Schleife erhält das nächste zu druckende Zeichen, bewegt dann den Cursor an den Anfang der Zeile und überschreibt alles, was zuvor vorhanden war, ohne eine neue Zeile hinzuzufügen. Sie möchten keinen zusätzlichen Abstand zwischen Positionsargumenten, daher muss das Trennzeichen leer sein. Beachten Sie auch die Verwendung von Pythons Rohzeichenfolgen aufgrund von Backslash-Zeichen im Literal.

Wenn Sie die verbleibende Zeit oder den Prozentsatz der Aufgabenerfüllung kennen, können Sie einen animierten Fortschrittsbalken anzeigen:

Progress bar animation in the terminal

Zunächst müssen Sie berechnen, wie viele Hashtags angezeigt und wie viele Leerzeichen eingefügt werden sollen. Als nächstes löschen Sie die Linie und erstellen die Leiste von Grund auf neu:

from time import sleep

def progress(percent=0, width=30):
    left = width * percent // 100
    right = width - left
    print('\r[', '#' * left, ' ' * right, ']',
          f' {percent:.0f}%',
          sep='', end='', flush=True)

for i in range(101):
    progress(i)
    sleep(0.1)

Nach wie vor wird bei jeder Aktualisierungsanforderung die gesamte Zeile neu gestrichen.

Note: Es gibt eine funktionsreicheprogressbar2-Bibliothek sowie einige andere ähnliche Tools, die den Fortschritt auf viel umfassendere Weise anzeigen können.

Mit Print Sounds machen

Wenn Sie alt genug sind, um sich an Computer mit einem PC-Lautsprecher zu erinnern, müssen Sie sich auch an deren charakteristischenbeep-Sound erinnern, der häufig zur Anzeige von Hardwareproblemen verwendet wird. Sie konnten kaum mehr Geräusche machen, aber Videospiele schienen damit viel besser zu sein.

Heute können Sie diesen kleinen Lautsprecher noch nutzen, aber wahrscheinlich hat Ihr Laptop keinen. In einem solchen Fall können Sie die Emulation vonterminal bellin Ihrer Shell aktivieren, sodass stattdessen ein Systemwarnton abgespielt wird.

Geben Sie diesen Befehl ein, um festzustellen, ob Ihr Terminal einen Sound wiedergeben kann:

$ echo -e "\a"

Dies würde normalerweise Text drucken, aber das-e-Flag ermöglicht die Interpretation von Backslash-Escapezeichen. Wie Sie sehen können, gibt es eine dedizierte Escape-Sequenz, die für "alert" steht und ein speziellesbell character ausgibt. Einige Terminals geben immer dann ein Geräusch von sich, wenn sie es sehen.

Ebenso können Sie dieses Zeichen in Python drucken. Vielleicht in einer Schleife, um eine Art Melodie zu bilden. Obwohl es sich nur um eine einzelne Notiz handelt, können Sie die Länge der Pausen zwischen aufeinanderfolgenden Instanzen variieren. Das scheint ein perfektes Spielzeug für die Morsecode-Wiedergabe zu sein!

Die Regeln sind folgende:

  • Buchstaben werden mit einer Folge vondot (·) unddash (-) Symbolen codiert.

  • Adot ist eine Zeiteinheit.

  • Adash sind drei Zeiteinheiten.

  • Einzelnesymbols in einem Buchstaben sind eine Zeiteinheit voneinander entfernt.

  • Symbole von zwei benachbartenletters sind drei Zeiteinheiten voneinander entfernt.

  • Symbole von zwei benachbartenwords sind sieben Zeiteinheiten voneinander entfernt.

Nach diesen Regeln können Sie ein SOS-Signal auf unbestimmte Zeit auf folgende Weise „drucken“:

while True:
    dot()
    symbol_space()
    dot()
    symbol_space()
    dot()
    letter_space()
    dash()
    symbol_space()
    dash()
    symbol_space()
    dash()
    letter_space()
    dot()
    symbol_space()
    dot()
    symbol_space()
    dot()
    word_space()

In Python können Sie es in nur zehn Codezeilen implementieren:

from time import sleep

speed = 0.1

def signal(duration, symbol):
    sleep(duration)
    print(symbol, end='', flush=True)

dot = lambda: signal(speed, '·\a')
dash = lambda: signal(3*speed, '−\a')
symbol_space = lambda: signal(speed, '')
letter_space = lambda: signal(3*speed, '')
word_space = lambda: signal(7*speed, ' ')

Vielleicht könnten Sie sogar noch einen Schritt weiter gehen und ein Befehlszeilenprogramm zum Übersetzen von Text in Morsecode erstellen? Wie auch immer, ich hoffe du hast Spaß damit!

Verspotten von Python-Druck in Unit-Tests

Heutzutage wird erwartet, dass Sie Code versenden, der hohen Qualitätsstandards entspricht. Wenn Sie ein Profi werden möchten, müssen Siehow to test Ihres Codes lernen.

Softwaretests sind besonders wichtig in dynamisch typisierten Sprachen wie Python, die keinen Compiler haben, der Sie vor offensichtlichen Fehlern warnt. Fehler können in die Produktionsumgebung gelangen und lange Zeit ruhen, bis eines Tages ein Codezweig endgültig ausgeführt wird.

Sicher, Sie habenlinters,type checkers und andere Tools für die statische Code-Analyse, die Sie unterstützen. Sie sagen Ihnen jedoch nicht, ob Ihr Programm das tut, was es auf geschäftlicher Ebene tun soll.

Sollten Sie alsoprint() testen? No. Immerhin handelt es sich um eine integrierte Funktion, die bereits eine umfassende Reihe von Tests durchlaufen haben muss. Sie möchten jedoch testen, ob Ihr Codeprint() zum richtigen Zeitpunkt mit den erwarteten Parametern aufruft. Das ist alsbehavior bekannt.

Sie können das Verhalten anhand der realen Objekte oder Funktionen vonmockingtesten. In diesem Fall möchten Sieprint() verspotten, um die Aufrufe aufzuzeichnen und zu überprüfen.

Note: Möglicherweise haben Sie die Begriffedummy,fake,stub,spy odermock gehört, die synonym verwendet werden. Einige Leute unterscheiden zwischen ihnen, andere nicht.

Martin Fowler erklärt ihre Unterschiede inshort glossary und nennt sie gemeinsamtest doubles.

Das Verspotten in Python kann auf zwei Arten erfolgen. Erstens können Sie den traditionellen Weg statisch typisierter Sprachen einschlagen, indem Sie die Abhängigkeitsinjektion verwenden. Dies kann manchmal erfordern, dass Sie den zu testenden Code ändern. Dies ist nicht immer möglich, wenn der Code in einer externen Bibliothek definiert ist:

def download(url, log=print):
    log(f'Downloading {url}')
    # ...

Dies ist das gleiche Beispiel, das ich in einem früheren Abschnitt verwendet habe, um über die Funktionszusammensetzung zu sprechen. Grundsätzlich könnenprint() durch eine benutzerdefinierte Funktion derselben Schnittstelle ersetzt werden. Um zu überprüfen, ob die richtige Nachricht gedruckt wird, müssen Sie sie abfangen, indem Sie eine verspottete Funktion einfügen:

>>>

>>> def mock_print(message):
...     mock_print.last_message = message
...
>>> download('resource', mock_print)
>>> assert 'Downloading resource' == mock_print.last_message

Wenn Sie dieses Modell aufrufen, wird die letzte Nachricht in einem Attribut gespeichert, das Sie später überprüfen können, z. B. in einerassert-Anweisung.

In einer etwas alternativen Lösung könnten Sie die Standardausgabe in einen speicherinternen dateiähnlichen Zeichenstrom umleiten, anstatt die gesamteprint()-Funktion durch einen benutzerdefinierten Wrapper zu ersetzen:

>>>

>>> def download(url, stream=None):
...     print(f'Downloading {url}', file=stream)
...     # ...
...
>>> import io
>>> memory_buffer = io.StringIO()
>>> download('app.js', memory_buffer)
>>> download('style.css', memory_buffer)
>>> memory_buffer.getvalue()
'Downloading app.js\nDownloading style.css\n'

Diesmal ruft die Funktion explizitprint() auf, setzt jedoch ihren Parameterfile der Außenwelt aus.

Eine pythonischere Methode zum Verspotten von Objekten nutzt jedoch das integrierte Modulmock, das eine Technik namensmonkey patching verwendet. Dieser abfällige Name ist darauf zurückzuführen, dass es sich um einen „schmutzigen Hack“ handelt, mit dem Sie sich leicht in den Fuß schießen können. Es ist weniger elegant als die Abhängigkeitsinjektion, aber auf jeden Fall schnell und bequem.

Note: Das Modulmock wurde von der Standardbibliothek in Python 3 übernommen, war jedoch zuvor ein Paket eines Drittanbieters. Sie mussten es separat installieren:

$ pip2 install mock

Davon abgesehen haben Sie es alsmock bezeichnet, während es in Python 3 Teil des Unit-Testing-Moduls ist, sodass Sie ausunittest.mock importieren müssen.

Durch das Patchen von Affen wird die Implementierung zur Laufzeit dynamisch geändert. Eine solche Änderung ist global sichtbar und kann daher unerwünschte Folgen haben. In der Praxis wirkt sich das Patchen jedoch nur auf den Code für die Dauer der Testausführung aus.

Umprint() in einem Testfall zu verspotten, verwenden Sie normalerweise@patchdecorator und geben ein Ziel für das Patchen an, indem Sie auf es mit einem vollständig qualifizierten Namen verweisen, der den Modulnamen enthält ::

from unittest.mock import patch

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

Dadurch wird automatisch das Modell für Sie erstellt und in die Testfunktion eingefügt. Sie müssen jedoch deklarieren, dass Ihre Testfunktion jetzt einen Mock akzeptiert. Das zugrunde liegende Scheinobjekt verfügt über viele nützliche Methoden und Attribute zur Überprüfung des Verhaltens.

Haben Sie etwas Besonderes an diesem Code-Snippet bemerkt?

Obwohl Sie der Funktion einen Mock hinzufügen, rufen Sie sie nicht direkt auf, obwohl Sie dies könnten. Dieses injizierte Modell wird nur verwendet, um anschließend Aussagen zu treffen und möglicherweise den Kontext vorzubereiten, bevor der Test ausgeführt wird.

Im wirklichen Leben hilft das Verspotten, den zu testenden Code zu isolieren, indem Abhängigkeiten wie eine Datenbankverbindung entfernt werden. In einem Test nennt man Mocks selten, weil das nicht viel Sinn macht. Es sind vielmehr andere Codeteile, die Ihren Mock indirekt aufrufen, ohne es zu wissen.

Das bedeutet Folgendes:

from unittest.mock import patch

def greet(name):
    print(f'Hello, {name}!')

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

Der zu testende Code ist eine Funktion, die eine Begrüßung druckt. Obwohl es sich um eine recht einfache Funktion handelt, können Sie sie nicht einfach testen, da sie keinen Wert zurückgibt. Es hat eine Nebenwirkung.

Um diesen Nebeneffekt zu beseitigen, müssen Sie die Abhängigkeit verspotten. Durch das Patchen können Sie Änderungen an der ursprünglichen Funktion vermeiden, die beiprint() agnostisch bleiben können. Es glaubt, es ruftprint() auf, aber in Wirklichkeit nennt es einen Mock, über den Sie die vollständige Kontrolle haben.

Es gibt viele Gründe, Software zu testen. Einer von ihnen sucht nach Fehlern. Wenn Sie Tests schreiben, möchten Sie häufig die Funktionprint()entfernen, indem Sie sie beispielsweise verspotten. Paradoxerweise kann dieselbe Funktion Ihnen jedoch dabei helfen, Fehler während eines verwandten Debugging-Prozesses zu finden, über den Sie im nächsten Abschnitt lesen werden.

Debugging drucken

In diesem Abschnitt werfen Sie einen Blick auf die verfügbaren Tools zum Debuggen in Python, angefangen von einer bescheidenenprint()-Funktion über daslogging-Modul bis hin zu einem vollwertigen Debugger. Nachdem Sie es gelesen haben, können Sie eine fundierte Entscheidung treffen, welche davon in einer bestimmten Situation am besten geeignet ist.

Note: Beim Debuggen werden die Hauptursachen fürbugs oder Fehler in der Software gesucht, nachdem sie entdeckt wurden, und es werden Schritte unternommen, um diese zu beheben.

Der Begriffbug hat einamusing story über die Herkunft seines Namens.

Rückverfolgung

Es wird auch alsprint debugging odercaveman debugging bezeichnet und ist die grundlegendste Form des Debuggens. Obwohl es ein bisschen altmodisch ist, ist es immer noch mächtig und hat seine Verwendung.

Die Idee ist, dem Pfad der Programmausführung zu folgen, bis er abrupt stoppt oder falsche Ergebnisse liefert, um die genaue Anweisung mit einem Problem zu identifizieren. Sie tun dies, indem Sie print-Anweisungen mit Wörtern einfügen, die an sorgfältig ausgewählten Stellen hervorstechen.

Schauen Sie sich dieses Beispiel an, bei dem ein Rundungsfehler auftritt:

>>>

>>> def average(numbers):
...     print('debug1:', numbers)
...     if len(numbers) > 0:
...         print('debug2:', sum(numbers))
...         return sum(numbers) / len(numbers)
...
>>> 0.1 == average(3*[0.1])
debug1: [0.1, 0.1, 0.1]
debug2: 0.30000000000000004
False

Wie Sie sehen können, gibt die Funktion nicht den erwarteten Wert von0.1 zurück, aber jetzt wissen Sie, dass die Summe etwas abweicht. Wenn Sie den Status von Variablen in verschiedenen Schritten des Algorithmus verfolgen, erhalten Sie einen Hinweis darauf, wo das Problem liegt.

Diese Methode ist einfach und intuitiv und funktioniert in nahezu jeder Programmiersprache. Ganz zu schweigen davon, dass es eine großartige Übung im Lernprozess ist.

Wenn Sie jedoch fortgeschrittenere Techniken beherrschen, ist es schwierig, zurück zu gehen, da Sie damit Fehler viel schneller finden können. Die Rückverfolgung ist ein mühsamer manueller Vorgang, bei dem noch mehr Fehler auftreten können. Der Erstellungs- und Bereitstellungszyklus benötigt Zeit. Danach müssen Sie daran denken, alle von Ihnen getätigtenprint()-Anrufe sorgfältig zu entfernen, ohne versehentlich die echten zu berühren.

Außerdem müssen Sie Änderungen am Code vornehmen, was nicht immer möglich ist. Möglicherweise debuggen Sie eine Anwendung, die auf einem Remote-Webserver ausgeführt wird, oder möchten ein Problem inpost-mortemWeise diagnostizieren. Manchmal haben Sie einfach keinen Zugriff auf die Standardausgabe.

Genau hier leuchtetlogging.

Protokollierung

Stellen wir uns für eine Minute vor, Sie betreiben eine E-Commerce-Website. Eines Tages telefoniert ein verärgerter Kunde über eine fehlgeschlagene Transaktion und sagt, er habe sein Geld verloren. Er behauptet, versucht zu haben, ein paar Artikel zu kaufen, aber am Ende gab es einen kryptischen Fehler, der ihn daran hinderte, diese Bestellung abzuschließen. Doch als er sein Bankkonto überprüfte, war das Geld weg.

Sie entschuldigen sich aufrichtig und leisten eine Rückerstattung, möchten aber auch nicht, dass dies in Zukunft erneut geschieht. Wie debuggen Sie das? Wenn Sie nur eine Spur von dem hätten, was passiert ist, idealerweise in Form einer chronologischen Liste von Ereignissen mit ihrem Kontext.

Wenn Sie das Debuggen von Drucken durchführen, sollten Sie es in permanente Protokollnachrichten umwandeln. Dies kann in solchen Situationen hilfreich sein, wenn Sie ein Problem nach dem Auftreten in einer Umgebung analysieren müssen, auf die Sie keinen Zugriff haben.

Es gibt ausgefeilte Tools für die Protokollaggregation und -suche, aber auf der einfachsten Ebene können Sie sich Protokolle als Textdateien vorstellen. Jede Zeile enthält detaillierte Informationen zu einem Ereignis in Ihrem System. Normalerweise enthält es keine personenbezogenen Daten, in einigen Fällen kann es jedoch gesetzlich vorgeschrieben sein.

Hier ist eine Aufschlüsselung eines typischen Protokolldatensatzes:

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

Wie Sie sehen können, hat es eine strukturierte Form. Abgesehen von einer beschreibenden Nachricht gibt es einige anpassbare Felder, die den Kontext eines Ereignisses bereitstellen. Hier haben Sie das genaue Datum und die Uhrzeit, die Protokollstufe, den Protokollierungsnamen und den Threadnamen.

Mithilfe von Protokollstufen können Sie Nachrichten schnell filtern, um das Rauschen zu reduzieren. Wenn Sie nach einem Fehler suchen, möchten Sie beispielsweise nicht alle Warnungen oder Debug-Meldungen anzeigen. Es ist trivial, Nachrichten auf bestimmten Protokollebenen über die Konfiguration zu deaktivieren oder zu aktivieren, ohne den Code zu berühren.

Mit der Protokollierung können Sie Ihre Debug-Meldungen von der Standardausgabe trennen. Alle Protokollmeldungen werden standardmäßig an den Standardfehlerstrom gesendet, der bequem in verschiedenen Farben angezeigt werden kann. Sie können Protokollnachrichten jedoch auch für einzelne Module in separate Dateien umleiten!

Häufig kann eine falsch konfigurierte Protokollierung dazu führen, dass nicht mehr genügend Speicherplatz auf der Serverfestplatte vorhanden ist. Um dies zu verhindern, können Sielog rotation einrichten, wodurch die Protokolldateien für eine bestimmte Dauer, z. B. eine Woche oder sobald sie eine bestimmte Größe erreicht haben, aufbewahrt werden. Trotzdem ist es immer eine gute Praxis, ältere Protokolle zu archivieren. Einige Vorschriften schreiben vor, dass Kundendaten bis zu fünf Jahre aufbewahrt werden!

Im Vergleich zu anderen Programmiersprachen istlogging in Python einfacher, da das Modullogging mit der Standardbibliothek gebündelt ist. Sie importieren und konfigurieren es einfach in nur zwei Codezeilen:

import logging
logging.basicConfig(level=logging.DEBUG)

Sie können auf Modulebene definierte Funktionen aufrufen, die mitroot logger verknüpft sind. Es ist jedoch üblicher, für jede Ihrer Quelldateien einen dedizierten Logger zu erhalten:

logging.debug('hello')  # Module-level function

logger = logging.getLogger(__name__)
logger.debug('hello')   # Logger's method

Der Vorteil der Verwendung von benutzerdefinierten Loggern ist eine feinkörnigere Kontrolle. Sie werden normalerweise nach dem Modul benannt, in dem sie durch die Variable__name__definiert wurden.

Note: In Python gibt es ein etwas verwandteswarnings-Modul, mit dem auch Nachrichten im Standardfehlerstrom protokolliert werden können. Es gibt jedoch ein engeres Anwendungsspektrum, hauptsächlich im Bibliothekscode, während Clientanwendungen das Modulloggingverwenden sollten.

Sie können sie jedoch zusammenarbeiten lassen, indem Sielogging.captureWarnings(True) aufrufen.

Ein letzter Grund, von derprint()-Funktion zur Protokollierung zu wechseln, ist die Thread-Sicherheit. Im nächsten Abschnitt werden Sie sehen, dass Ersteres mit mehreren Ausführungsthreads nicht gut funktioniert.

Debuggen

Die Wahrheit ist, dass weder Ablaufverfolgung noch Protokollierung als echtes Debugging betrachtet werden können. Für das eigentliche Debuggen benötigen Sie ein Debugger-Tool, mit dem Sie Folgendes ausführen können:

  • Gehen Sie interaktiv durch den Code.

  • Legen Sie Haltepunkte fest, einschließlich bedingter Haltepunkte.

  • Introspect-Variablen im Speicher.

  • Bewerten Sie benutzerdefinierte Ausdrücke zur Laufzeit.

Ein roher Debugger, der im Terminal ausgeführt wird und nicht überraschendpdb für "The Python Debugger" heißt, wird als Teil der Standardbibliothek verteilt. Dadurch ist es immer verfügbar, sodass Sie möglicherweise nur das Remote-Debugging durchführen können. Vielleicht ist das ein guter Grund, sich damit vertraut zu machen.

Da es jedoch keine grafische Oberfläche gibt, kannusing pdb etwas schwierig sein. Wenn Sie den Code nicht bearbeiten können, müssen Sie ihn als Modul ausführen und den Speicherort Ihres Skripts übergeben:

$ python -m pdb my_script.py

Andernfalls können Sie einen Haltepunkt direkt im Code einrichten, wodurch die Ausführung Ihres Skripts unterbrochen und Sie in den Debugger verschoben werden. Die alte Vorgehensweise erforderte zwei Schritte:

>>>

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

Daraufhin wird eine interaktive Eingabeaufforderung angezeigt, die zunächst einschüchternd wirken kann. Sie können jedoch zu diesem Zeitpunkt noch natives Python eingeben, um den Status lokaler Variablen zu untersuchen oder zu ändern. Abgesehen davon gibt es wirklich nur eine Handvoll Debugger-spezifischer Befehle, die Sie zum Durchlaufen des Codes verwenden möchten.

Note: Es ist üblich, die beiden Anweisungen zum Hochfahren eines Debuggers in eine einzelne Zeile zu setzen. Dies erfordert die Verwendung eines Semikolons, das in Python-Programmen selten vorkommt:

import pdb; pdb.set_trace()

Es ist sicherlich nicht Pythonic, aber es ist eine Erinnerung daran, es zu entfernen, nachdem Sie mit dem Debuggen fertig sind.

Seit Python 3.7 können Sie auch die integriertebreakpoint()-Funktion aufrufen, die dasselbe tut, jedoch kompakter und mit einigen zusätzlichenbells and whistles:

def average(numbers):
    if len(numbers) > 0:
        breakpoint()  # Python 3.7+
        return sum(numbers) / len(numbers)

Sie werden wahrscheinlich einen visuellen Debugger verwenden, der größtenteils in einen Code-Editor integriert ist. PyCharm verfügt über einen hervorragenden Debugger, der sich durch hohe Leistung auszeichnet. Sie finden jedochplenty of alternative IDEs mit kostenpflichtigen und kostenlosen Debuggern.

Debuggen ist nicht die sprichwörtliche Silberkugel. Manchmal ist die Protokollierung oder Nachverfolgung eine bessere Lösung. Beispielsweise resultieren schwer reproduzierbare Defekte wierace conditions häufig aus einer zeitlichen Kopplung. Wenn Sie an einem Haltepunkt anhalten, kann diese kleine Pause in der Programmausführung das Problem maskieren. Es ist wie beiHeisenberg principle: Sie können einen Fehler nicht gleichzeitig messen und beobachten.

Diese Methoden schließen sich nicht gegenseitig aus. Sie ergänzen sich.

Thread-sicheres Drucken

Ich habe zuvor kurz auf das Thread-Sicherheitsproblem eingegangen undlogging gegenüber derprint()-Funktion empfohlen. Wenn Sie dies noch lesen, müssen Sie mitthe concept of threads vertraut sein.

Thread-Sicherheit bedeutet, dass ein Code sicher von mehreren Ausführungsthreads gemeinsam genutzt werden kann. Die einfachste Strategie zur Gewährleistung der Thread-Sicherheit besteht darin, nurimmutable-Objekte gemeinsam zu nutzen. Wenn Threads den Status eines Objekts nicht ändern können, besteht kein Risiko, dass seine Konsistenz beeinträchtigt wird.

Eine andere Methode nutztlocal memory, wodurch jeder Thread eine eigene Kopie desselben Objekts erhält. Auf diese Weise können andere Threads die im aktuellen Thread vorgenommenen Änderungen nicht sehen.

Aber das löst das Problem nicht, oder? Sie möchten häufig, dass Ihre Threads zusammenarbeiten, indem Sie eine gemeinsam genutzte Ressource mutieren können. Die häufigste Methode zum Synchronisieren des gleichzeitigen Zugriffs auf eine solche Ressource istlocking it. Dies ermöglicht exklusiven Schreibzugriff auf einen oder manchmal mehrere Threads gleichzeitig.

Das Sperren ist jedoch teuer und reduziert den gleichzeitigen Durchsatz. Daher wurden andere Mittel zur Steuerung des Zugriffs erfunden, wie beispielsweiseatomic variables oder dercompare-and-swap-Algorithmus.

Das Drucken ist in Python nicht threadsicher. Die Funktionprint() enthält einen Verweis auf die Standardausgabe, bei der es sich um eine gemeinsam genutzte globale Variable handelt. Da es keine Sperre gibt, kann theoretisch während eines Aufrufs vonsys.stdout.write() ein Kontextwechsel erfolgen, bei dem Textbits aus mehrerenprint()-Anrufen miteinander verflochten werden.

Note: Ein Kontextwechsel bedeutet, dass ein Thread seine Ausführung entweder freiwillig oder nicht freiwillig anhält, damit ein anderer übernehmen kann. Dies kann jederzeit passieren, auch mitten in einem Funktionsaufruf.

In der Praxis ist dies jedoch nicht der Fall. Unabhängig davon, wie sehr Sie sich bemühen, scheint das Schreiben in die Standardausgabe atomar zu sein. Das einzige Problem, das Sie manchmal beobachten können, sind fehlerhafte Zeilenumbrüche:

[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]

Um dies zu simulieren, können Sie die Wahrscheinlichkeit eines Kontextwechsels erhöhen, indem Sie die zugrunde liegende.write()-Methode für eine zufällige Zeitspanne in den Ruhezustand versetzen. How? Indem Sie es verspotten, was Sie bereits aus einem früheren Abschnitt wissen:

import sys

from time import sleep
from random import random
from threading import current_thread, Thread
from unittest.mock import patch

write = sys.stdout.write

def slow_write(text):
    sleep(random())
    write(text)

def task():
    thread_name = current_thread().name
    for letter in 'ABC':
        print(f'[{thread_name} {letter}]')

with patch('sys.stdout') as mock_stdout:
    mock_stdout.write = slow_write
    for _ in range(3):
        Thread(target=task).start()

Zunächst müssen Sie die ursprüngliche.write()-Methode in einer Variablen speichern, an die Sie später delegieren werden. Anschließend stellen Sie Ihre gefälschte Implementierung bereit, deren Ausführung bis zu einer Sekunde dauert. Jeder Thread ruft einigeprint() mit seinem Namen und einem Buchstaben auf: A, B und C.

Wenn Sie den Spottabschnitt zuvor gelesen haben, haben Sie möglicherweise bereits eine Vorstellung davon, warum sich das Drucken so schlecht verhält. Um dies kristallklar zu machen, können Sie jedoch Werte erfassen, die in die Funktionslow_write()eingegeben wurden. Sie werden feststellen, dass Sie jedes Mal eine etwas andere Sequenz erhalten:

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

Obwohlsys.stdout.write() selbst eine atomare Operation ist, kann ein einzelner Aufruf der Funktionprint() mehr als einen Schreibvorgang ergeben. Beispielsweise werden Zeilenumbrüche getrennt vom Rest des Textes geschrieben, und zwischen diesen Schreibvorgängen findet eine Kontextumschaltung statt.

Note: Die atomare Natur der Standardausgabe in Python ist ein Nebenprodukt vonGlobal Interpreter Lock, das das Sperren von Bytecode-Anweisungen anwendet. Beachten Sie jedoch, dass viele Interpreter-Varianten nicht über die GIL verfügen, bei der das Multithread-Drucken eine explizite Sperrung erfordert.

Sie können das Zeilenumbruchzeichen zu einem integralen Bestandteil der Nachricht machen, indem Sie es manuell behandeln:

print(f'[{thread_name} {letter}]\n', end='')

Dadurch wird die Ausgabe korrigiert:

[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]

Beachten Sie jedoch, dass die Funktionprint()immer noch einen separaten Aufruf für das leere Suffix ausführt, was zu einer nutzlosen Anweisung vonsys.stdout.write('')führt:

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

Eine wirklich threadsichere Version der Funktionprint()könnte folgendermaßen aussehen:

import threading

lock = threading.Lock()

def thread_safe_print(*args, **kwargs):
    with lock:
        print(*args, **kwargs)

Sie können diese Funktion in ein Modul einfügen und an eine andere Stelle importieren:

from thread_safe_print import thread_safe_print

def task():
    thread_name = current_thread().name
    for letter in 'ABC':
        thread_safe_print(f'[{thread_name} {letter}]')

Jetzt kann trotz zweier Schreibvorgänge proprint()-Anforderung nur ein Thread mit dem Stream interagieren, während der Rest warten muss:

[
    # 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
    (...)
]

Ich habe Kommentare hinzugefügt, um anzuzeigen, wie die Sperre den Zugriff auf die freigegebene Ressource einschränkt.

Note: Selbst in Single-Threaded-Code können Sie in eine ähnliche Situation geraten. Insbesondere, wenn Sie gleichzeitig auf die Standardausgabe und die Standardfehlerströme drucken. Sofern Sie eine oder beide nicht in separate Dateien umleiten, teilen sich beide ein einziges Terminalfenster.

Umgekehrt ist das Modulloggingvom Design her threadsicher, was sich in seiner Fähigkeit widerspiegelt, Threadnamen in der formatierten Nachricht anzuzeigen:

>>>

>>> import logging
>>> logging.basicConfig(format='%(threadName)s %(message)s')
>>> logging.error('hello')
MainThread hello

Dies ist ein weiterer Grund, warum Sie die Funktionprint()möglicherweise nicht immer verwenden möchten.

Python Print-Gegenstücke

Inzwischen wissen Sie viel überprint()! Das Thema wäre jedoch nicht vollständig, ohne ein wenig über seine Gegenstücke zu sprechen. Während es beiprint() um die Ausgabe geht, gibt es Funktionen und Bibliotheken für die Eingabe.

Eingebaut

Python verfügt über eine integrierte Funktion zum Akzeptieren von Benutzereingaben, die vorhersehbar alsinput() bezeichnet wird. Es akzeptiert Daten aus dem Standardeingabestream, bei dem es sich normalerweise um die Tastatur handelt:

>>>

>>> name = input('Enter your name: ')
Enter your name: jdoe
>>> print(name)
jdoe

Die Funktion gibt immer einen String zurück, daher müssen Sie ihn möglicherweise entsprechend analysieren:

try:
    age = int(input('How old are you? '))
except ValueError:
    pass

Der Eingabeaufforderungsparameter ist vollständig optional, sodass beim Überspringen nichts angezeigt wird, die Funktion jedoch weiterhin funktioniert:

>>>

>>> x = input()
hello world
>>> print(x)
hello world

Trotzdem macht das Einfügen eines beschreibenden Aufrufs zum Handeln die Benutzererfahrung so viel besser.

Note: Um von der Standardeingabe in Python 2 zu lesen, müssen Sie stattdessenraw_input() aufrufen, was ebenfalls integriert ist. Leider gibt es auch eine irreführend benannteinput()-Funktion, die etwas anderes macht.

Tatsächlich nimmt es auch die Eingabe aus dem Standard-Stream, versucht dann aber, sie so auszuwerten, als wäre es Python-Code. Da dies ein potenziellessecurity vulnerability ist, wurde diese Funktion vollständig aus Python 3 entfernt, währendraw_input() ininput() umbenannt wurde.

Hier ist ein kurzer Vergleich der verfügbaren Funktionen und ihrer Funktionen:

Python 2 Python 3

raw_input()

input()

input()

eval(input())

Wie Sie sehen, ist es weiterhin möglich, das alte Verhalten in Python 3 zu simulieren.

Es ist eine schlechte Idee, den Benutzer nach einem Kennwort mitinput() zu fragen, da es während der Eingabe im Klartext angezeigt wird. In diesem Fall sollten Sie stattdessen die Funktiongetpass()verwenden, mit der eingegebene Zeichen maskiert werden. Diese Funktion ist in einem gleichnamigen Modul definiert, das auch in der Standardbibliothek verfügbar ist:

>>>

>>> from getpass import getpass
>>> password = getpass()
Password:
>>> print(password)
s3cret

Dasgetpass-Modul verfügt über eine weitere Funktion zum Abrufen des Benutzernamens aus einer Umgebungsvariablen:

>>>

>>> from getpass import getuser
>>> getuser()
'jdoe'

Die in Python integrierten Funktionen zur Verarbeitung der Standardeingabe sind recht begrenzt. Gleichzeitig gibt es zahlreiche Pakete von Drittanbietern, die viel ausgefeiltere Tools bieten.

Dritte Seite

Es gibt externe Python-Pakete, mit denen komplexe grafische Oberflächen erstellt werden können, um Daten vom Benutzer zu erfassen. Einige ihrer Funktionen umfassen:

  • Erweiterte Formatierung und Gestaltung

  • Automatisches Parsen, Validieren und Bereinigen von Benutzerdaten

  • Ein deklarativer Stil zum Definieren von Layouts

  • Interaktive automatische Vervollständigung

  • Mausunterstützung

  • Vordefinierte Widgets wie Checklisten oder Menüs

  • Durchsuchbarer Verlauf eingegebener Befehle

  • Satzstellung markieren

Die Demonstration solcher Tools liegt außerhalb des Geltungsbereichs dieses Artikels, Sie können sie jedoch ausprobieren. Einige davon habe ich persönlich durch diePython Bytes Podcast kennengelernt. Hier sind sie:

Erwähnenswert ist jedoch ein Befehlszeilentool namensrlwrap, mit dem Sie Ihren Python-Skripten kostenlos leistungsstarke Funktionen zur Zeilenbearbeitung hinzufügen können. Sie müssen nichts tun, damit es funktioniert!

Nehmen wir an, Sie haben eine Befehlszeilenschnittstelle geschrieben, die drei Anweisungen versteht, darunter eine zum Hinzufügen von Zahlen:

print('Type "help", "exit", "add a [b [c ...]]"')
while True:
    command, *arguments = input('~ ').split(' ')
    if len(command) > 0:
        if command.lower() == 'exit':
            break
        elif command.lower() == 'help':
            print('This is help.')
        elif command.lower() == 'add':
            print(sum(map(int, arguments)))
        else:
            print('Unknown command')

Auf den ersten Blick scheint es eine typische Eingabeaufforderung zu sein, wenn Sie es ausführen:

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

Sobald Sie jedoch einen Fehler machen und ihn beheben möchten, werden Sie feststellen, dass keine der Funktionstasten wie erwartet funktioniert. Wenn Sie beispielsweise auf den Pfeil[.kbd .key-arrow-left]#Left # drücken, wird dies ausgeführt, anstatt den Cursor zurück zu bewegen:

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

Jetzt können Sie dasselbe Skript mit dem Befehlrlwrap umbrechen. Sie werden nicht nur die Pfeiltasten zum Laufen bringen, sondern auch den dauerhaften Verlauf Ihrer benutzerdefinierten Befehle durchsuchen, die automatische Vervollständigung verwenden und die Zeile mit Verknüpfungen bearbeiten:

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

Ist das nicht toll?

Fazit

Sie verfügen jetzt über umfassende Kenntnisse über dieprint()-Funktion in Python sowie über viele umliegende Themen. Sie haben ein tiefes Verständnis dafür, was es ist und wie es funktioniert, wobei alle wichtigen Elemente berücksichtigt werden. Zahlreiche Beispiele gaben Ihnen einen Einblick in die Entwicklung von Python 2.

Abgesehen davon haben Sie gelernt, wie man:

  • Vermeiden Sie häufige Fehler mitprint() in Python

  • Beschäftige dich mit Zeilenumbrüchen, Zeichenkodierungen und Pufferung

  • Schreiben Sie Text in Dateien

  • Verspotten Sie dieprint()-Funktion in Unit-Tests

  • Erstellen Sie erweiterte Benutzeroberflächen im Terminal

Nachdem Sie dies alles wissen, können Sie interaktive Programme erstellen, die mit Benutzern kommunizieren oder Daten in gängigen Dateiformaten erstellen. Sie können Probleme in Ihrem Code schnell diagnostizieren und sich vor ihnen schützen. Zu guter Letzt wissen Sie, wie Sie das klassische Schlangenspiel implementieren.

Wenn Sie immer noch nach weiteren Informationen dürsten, Fragen haben oder einfach nur Ihre Gedanken teilen möchten, wenden Sie sich bitte an den Kommentarbereich unten.