Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 214 – Erweiterte Print-Anweisung

Autor:
Barry Warsaw <barry at python.org>
Status:
Final
Typ:
Standards Track
Erstellt:
24. Juli 2000
Python-Version:
2.0
Post-History:
16. August 2000

Inhaltsverzeichnis

Einleitung

Dieses PEP beschreibt eine Syntax zur Erweiterung der Standard-`print`-Anweisung, damit diese in jedes dateiähnliche Objekt schreiben kann, anstatt standardmäßig in `sys.stdout`. Dieses PEP verfolgt den Status und die Zuständigkeit dieser Funktion. Es enthält eine Beschreibung der Funktion und umreißt die notwendigen Änderungen zur Unterstützung der Funktion. Dieses PEP fasst Diskussionen in Mailinglisten zusammen und bietet nach Bedarf URLs für weitere Informationen. Die CVS-Revisionshistorie dieser Datei enthält die maßgebliche historische Aufzeichnung.

Vorschlag

Dieser Vorschlag führt eine Syntaxerweiterung für die print-Anweisung ein, die es dem Programmierer erlaubt, optional das Ausgabeziel anzugeben. Ein Beispiel für die Verwendung ist wie folgt:

print >> mylogfile, 'this message goes to my log file'

Formell ist die Syntax der erweiterten print-Anweisung:

print_stmt: ... | '>>' test [ (',' test)+ [','] ] )

wobei die Ellipse die ursprüngliche Syntax für `print_stmt` unverändert anzeigt. In der erweiterten Form muss der Ausdruck direkt nach `>>` ein Objekt mit einer `write()`-Methode ergeben (d.h. ein dateiähnliches Objekt). Somit sind diese beiden Anweisungen äquivalent:

print 'hello world'
print >> sys.stdout, 'hello world'

Ebenso sind diese beiden Anweisungen:

print
print >> sys.stdout

Diese beiden Anweisungen sind Syntaxfehler:

print ,
print >> sys.stdout,

Begründung

`print` ist ein Python-Schlüsselwort und leitet die print-Anweisung ein, wie in Abschnitt 6.6 des Sprachreferenzhandbuchs beschrieben [1]. Die print-Anweisung hat eine Reihe von Funktionen:

  • sie konvertiert die Elemente automatisch in Strings
  • sie fügt automatisch Leerzeichen zwischen den Elementen ein
  • sie hängt einen Zeilenumbruch an, es sei denn, die Anweisung endet mit einem Komma

Die Formatierung, die die print-Anweisung durchführt, ist begrenzt; für mehr Kontrolle über die Ausgabe kann eine Kombination aus `sys.stdout.write()` und String-Interpolation verwendet werden.

Die print-Anweisung gibt per Definition nach `sys.stdout` aus. Genauer gesagt, muss `sys.stdout` ein dateiähnliches Objekt mit einer `write()`-Methode sein, aber es kann neu zugewiesen werden, um die Ausgabe in andere Dateien als nur die Standardausgabe umzuleiten. Ein typisches Idiom ist:

save_stdout = sys.stdout
try:
    sys.stdout = mylogfile
    print 'this message goes to my log file'
finally:
    sys.stdout = save_stdout

Das Problem bei diesem Ansatz ist, dass die Zuweisung global ist und somit jede Anweisung innerhalb der `try:`-Klausel beeinflusst. Wenn wir zum Beispiel einen Aufruf einer Funktion hinzufügen, die tatsächlich in stdout drucken möchte, würde diese Ausgabe ebenfalls in die Logdatei umgeleitet.

Dieser Ansatz ist auch sehr unpraktisch für das Verschachteln von Ausgaben in verschiedene Ausgabeströme und erschwert die Programmierung angesichts legitimer `try/except`- oder `try/finally`-Klauseln.

Referenzimplementierung

Eine Referenzimplementierung in Form eines Patches gegen den Quellcode von Python 2.0 ist im Patch-Manager von SourceForge verfügbar [2]. Dieser Ansatz fügt zwei neue Opcodes hinzu: `PRINT_ITEM_TO` und `PRINT_NEWLINE_TO`, die einfach das dateiähnliche Objekt vom Stack entfernen und es anstelle von `sys.stdout` als Ausgabestrom verwenden.

(Diese Referenzimplementierung wurde in Python 2.0 übernommen.)

Alternative Ansätze

Eine Alternative zu dieser Syntaxänderung wurde (ursprünglich von Moshe Zadka) vorgeschlagen, die keine Syntaxänderungen an Python erfordert. Eine `writeln()`-Funktion könnte (möglicherweise als Built-in) bereitgestellt werden, die ähnlich wie die erweiterte print-Anweisung funktioniert, mit einigen zusätzlichen Funktionen:

def writeln(*args, **kws):
    import sys
    file = sys.stdout
    sep = ' '
    end = '\n'
    if kws.has_key('file'):
        file = kws['file']
        del kws['file']
    if kws.has_key('nl'):
        if not kws['nl']:
            end = ' '
        del kws['nl']
    if kws.has_key('sep'):
        sep = kws['sep']
        del kws['sep']
    if kws:
        raise TypeError('unexpected keywords')
    file.write(sep.join(map(str, args)) + end)

`writeln()` nimmt drei optionale Schlüsselwortargumente entgegen. Im Kontext dieses Vorschlags ist das relevante Argument 'file', das auf ein dateiähnliches Objekt mit einer `write()`-Methode gesetzt werden kann. Somit

print >> mylogfile, 'this goes to my log file'

würde geschrieben werden als

writeln('this goes to my log file', file=mylogfile)

`writeln()` hat die zusätzliche Funktionalität, dass das Schlüsselwortargument 'nl' ein Flag ist, das angibt, ob ein Zeilenumbruch angehängt werden soll oder nicht, und ein Argument 'sep', das den Trenner angibt, der zwischen jedem Element ausgegeben werden soll.

Weitere Begründung durch den BDFL

Der Vorschlag wurde in der Newsgroup angefochten. Eine Reihe von Anfechtungen mag das '>>' nicht und würde stattdessen ein anderes Symbol bevorzugen.

  • Anfechtung: Warum nicht eines dieser Symbole?
    print in stderr items,....
    print + stderr items,.......
    print[stderr] items,.....
    print to stderr items,.....
    

    Antwort: Wenn wir ein spezielles Symbol verwenden wollen (`print` `` Ausdruck), erfordert der Python-Parser, dass es sich nicht bereits um ein Symbol handelt, das einen Ausdruck beginnen kann – sonst kann er nicht entscheiden, welche Form der print-Anweisung verwendet wird. (Der Python-Parser ist ein einfacher LL(1)- oder rekursiver Abstiegs-Parser.)

    Das bedeutet, dass wir den Trick "Keyword nur im Kontext" nicht verwenden können, der für "import as" verwendet wurde, da ein Bezeichner einen Ausdruck beginnen kann. Dies schließt `+stderr`, `[sterr]` und `to stderr` aus. Übrig bleiben binäre Operatorsymbole und andere nicht näher spezifizierte Symbole, die hier derzeit illegal sind, wie z.B. 'import'.

    Wenn ich zwischen 'print in file' und 'print >> file' wählen müsste, würde ich definitiv '>>' wählen. Zum Teil, weil 'in' eine Neuerung wäre (ich kenne keine andere Sprache, die es verwendet, während '>>' in sh, awk, Perl und C++ verwendet wird), und zum Teil, weil '>>' nicht-alphabetisch ist und stärker auffällt, sodass es eher die Aufmerksamkeit des Lesers auf sich zieht.

  • Anfechtung: Warum muss ein Komma zwischen der Datei und dem Rest stehen?

    Antwort: Das Komma, das die Datei vom folgenden Ausdruck trennt, ist notwendig! Natürlich möchten Sie, dass die Datei ein beliebiger Ausdruck ist, nicht nur ein einzelnes Wort. (Sie möchten definitiv in der Lage sein, `print >>sys.stderr` zu schreiben.) Ohne den Ausdruck könnte der Parser nicht unterscheiden, wo dieser Ausdruck endet und wo der nächste beginnt, z.B.

    print >>i +1, 2
    print >>a [1], 2
    print >>f (1), 2
    
  • Anfechtung: Warum benötigen Sie eine Syntaxerweiterung? Warum nicht writeln(file, item, ...)?

    Antwort: Erstens fehlt hier eine Funktion der print-Anweisung: das abschließende Komma zum Drucken, das den letzten Zeilenumbruch unterdrückt. Beachten Sie, dass 'print a,' immer noch nicht äquivalent zu 'sys.stdout.write(a)' ist – print fügt ein Leerzeichen zwischen Elementen ein und akzeptiert beliebige Objekte als Argumente; `write()` fügt kein Leerzeichen ein und benötigt einen einzelnen String.

    Wenn man eine Erweiterung für die print-Anweisung in Betracht zieht, ist es nicht richtig, eine Funktion oder Methode hinzuzufügen, die eine neue Funktion in einer Dimension (wohin die Ausgabe geht) hinzufügt, aber in einer anderen Dimension (Leerzeichen zwischen Elementen und die Wahl des abschließenden Zeilenumbruchs oder nicht) wegnimmt. Wir könnten eine ganze Reihe von Methoden oder Funktionen hinzufügen, um die verschiedenen Fälle zu behandeln, aber das scheint mehr Verwirrung als notwendig zu stiften und wäre nur sinnvoll, wenn wir die print-Anweisung vollständig abschaffen würden.

    Ich glaube, diese Debatte dreht sich wirklich darum, ob print eine Funktion oder Methode und keine Anweisung hätte sein sollen. Wenn Sie im Lager der Funktionen sind, ist das Hinzufügen von spezieller Syntax zur bestehenden print-Anweisung natürlich nichts, was Ihnen gefällt. Ich vermute, dass die Einwände gegen die neue Syntax hauptsächlich von Leuten kommen, die bereits der Meinung sind, dass die print-Anweisung eine schlechte Idee war. Habe ich Recht?

    Vor etwa 10 Jahren habe ich mit mir selbst debattiert, ob die grundlegendste Form der Ausgabe eine Funktion oder eine Anweisung sein sollte; im Grunde entschied ich zwischen "print(item, ...)" und "print item, ...". Ich entschied mich für eine Anweisung, da das Drucken sehr früh gelehrt werden muss und in den Programmen, die Anfänger schreiben, sehr wichtig ist. Außerdem hat ABC, das bei vielen Dingen den Weg wies, es zu einer Anweisung gemacht. In einer Bewegung, die typisch für die Interaktion zwischen ABC und Python ist, änderte ich den Namen von WRITE in print und kehrte die Konvention für das Hinzufügen von Zeilenumbrüchen von der Notwendigkeit einer zusätzlichen Syntax zum Hinzufügen eines Zeilenumbruchs (ABC verwendete abschließende Schrägstriche zur Anzeige von Zeilenumbrüchen) zur Notwendigkeit einer zusätzlichen Syntax (dem abschließenden Komma) zur Unterdrückung des Zeilenumbruchs um. Ich behielt die Funktion bei, dass Elemente bei der Ausgabe durch Leerzeichen getrennt werden.

    Vollständiges Beispiel: in ABC,

    WRITE 1
    WRITE 2/
    

    hat die gleiche Wirkung wie

    print 1,
    print 2
    

    in Python, was im Wesentlichen "1 2n" ausgibt.

    Ich bin mir nicht zu 100% sicher, ob die Wahl für eine Anweisung richtig war (ABC hatte den zwingenden Grund, dass es Anweisungssyntax für alles mit Seiteneffekten verwendete, aber Python hat diese Konvention nicht), aber ich bin auch nicht überzeugt, dass es falsch ist. Ich mag definitiv die Sparsamkeit der print-Anweisung. (Ich bin ein fanatischer Lisp-Hasser – syntaktisch, nicht semantisch! – und übermäßige Klammern in der Syntax nerven mich. Schreiben Sie niemals `return(i)` `or` `if(x==y):` in Ihrem Python-Code! :-)

    Wie auch immer, ich bin nicht bereit, die print-Anweisung abzuschaffen, und im Laufe der Jahre gab es viele Anfragen für eine Option zur Angabe der Datei.

  • Anfechtung: Warum nicht `>` statt `>>`?

    Antwort: Für DOS- und Unix-Benutzer suggeriert `>>` "anhängen", während `>` "überschreiben" suggeriert; die Semantik ist am ehesten mit "anhängen" vergleichbar. Außerdem sind für C++-Programmierer `>>` und `<<` E/A-Operatoren.

  • Anfechtung: Aber in C++ ist `>>` Eingabe und `<<` Ausgabe!

    Antwort: spielt keine Rolle; C++ hat es eindeutig aus Unix übernommen und die Pfeile umgekehrt. Wichtig ist, dass bei der Ausgabe der Pfeil auf die Datei zeigt.

  • Anfechtung: Sicherlich können Sie eine `println()`-Funktion entwerfen, die alles kann, was `print>>file` kann; warum reicht das nicht?

    Antwort: Ich betrachte dies im Sinne einer einfachen Programmieraufgabe. Angenommen, einem Anfänger wird aufgetragen, eine Funktion zu schreiben, die Einmaleins-Tabellen ausgibt. Eine vernünftige Lösung ist:

    def tables(n):
        for j in range(1, n+1):
            for i in range(1, n+1):
                print i, 'x', j, '=', i*j
            print
    

    Nehmen wir nun an, die zweite Aufgabe besteht darin, die Ausgabe in eine andere Datei hinzuzufügen. Mit der neuen Syntax muss der Programmierer nur eine neue Sache lernen: `print >> file`, und die Antwort kann so aussehen:

    def tables(n, file=sys.stdout):
        for j in range(1, n+1):
            for i in range(1, n+1):
                print >> file, i, 'x', j, '=', i*j
            print >> file
    

    Mit nur einer print-Anweisung und einer `println()`-Funktion muss der Programmierer zuerst etwas über `println()` lernen und das ursprüngliche Programm in die Verwendung von `println()` umwandeln:

    def tables(n):
        for j in range(1, n+1):
            for i in range(1, n+1):
                println(i, 'x', j, '=', i*j)
            println()
    

    und **dann** über das Schlüsselwortargument 'file':

    def tables(n, file=sys.stdout):
        for j in range(1, n+1):
            for i in range(1, n+1):
                println(i, 'x', j, '=', i*j, file=sys.stdout)
            println(file=sys.stdout)
    

    Somit ist der Transformationspfad länger:

    (1) print
    (2) print >> file
    

    vs.

    (1) print
    (2) println()
    (3) println(file=...)
    

    Hinweis: Das Standardisieren des Dateiarguments auf `sys.stdout` zur Kompilierzeit ist falsch, da es nicht richtig funktioniert, wenn der Aufrufer `sys.stdout` zuweist und dann `tables()` verwendet, ohne die Datei anzugeben. Dies ist ein häufiges Problem (und würde auch bei einer `println()`-Funktion auftreten). Die bisherige Standardlösung war:

    def tables(n, file=None):
        if file is None:
            file = sys.stdout
        for j in range(1, n+1):
            for i in range(1, n+1):
                print >> file, i, 'x', j, '=', i*j
            print >> file
    

    Ich habe der Implementierung eine Funktion hinzugefügt (die ich auch für `println()` empfehlen würde), bei der, wenn das Dateiargument `None` ist, automatisch `sys.stdout` verwendet wird. Somit:

    print >> None, foo bar
    

    (oder natürlich `print >> x`, wobei x eine Variable ist, deren Wert None ist) bedeutet dasselbe wie

    print foo, bar
    

    und die Funktion `tables()` kann wie folgt geschrieben werden:

    def tables(n, file=None):
        for j in range(1, n+1):
            for i in range(1, n+1):
                print >> file, i, 'x', j, '=', i*j
            print >> file
    

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0214.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT