PEP: 214 Title: Extended Print Statement Author: Barry Warsaw
<barry@python.org> Status: Final Type: Standards Track Content-Type:
text/x-rst Created: 24-Jul-2000 Python-Version: 2.0 Post-History:
16-Aug-2000

Introduction

This PEP describes a syntax to extend the standard 'print' statement so
that it can be used to print to any file-like object, instead of the
default sys.stdout. This PEP tracks the status and ownership of this
feature. It contains a description of the feature and outlines changes
necessary to support the feature. This PEP summarizes discussions held
in mailing list forums, and provides URLs for further information, where
appropriate. The CVS revision history of this file contains the
definitive historical record.

Proposal

This proposal introduces a syntax extension to the print statement,
which allows the programmer to optionally specify the output file
target. An example usage is as follows:

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

Formally, the syntax of the extended print statement is:

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

where the ellipsis indicates the original print_stmt syntax unchanged.
In the extended form, the expression just after >> must yield an object
with a write() method (i.e. a file-like object). Thus these two
statements are equivalent:

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

As are these two statements:

    print
    print >> sys.stdout

These two statements are syntax errors:

    print ,
    print >> sys.stdout,

Justification

'print' is a Python keyword and introduces the print statement as
described in section 6.6 of the language reference manual[1]. The print
statement has a number of features:

-   it auto-converts the items to strings
-   it inserts spaces between items automatically
-   it appends a newline unless the statement ends in a comma

The formatting that the print statement performs is limited; for more
control over the output, a combination of sys.stdout.write(), and string
interpolation can be used.

The print statement by definition outputs to sys.stdout. More
specifically, sys.stdout must be a file-like object with a write()
method, but it can be rebound to redirect output to files other than
specifically standard output. A typical idiom is:

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

The problem with this approach is that the binding is global, and so
affects every statement inside the try: clause. For example, if we added
a call to a function that actually did want to print to stdout, this
output too would get redirected to the logfile.

This approach is also very inconvenient for interleaving prints to
various output streams, and complicates coding in the face of legitimate
try/except or try/finally clauses.

Reference Implementation

A reference implementation, in the form of a patch against the Python
2.0 source tree, is available on SourceForge's patch manager[2]. This
approach adds two new opcodes, PRINT_ITEM_TO and PRINT_NEWLINE_TO, which
simply pop the file like object off the top of the stack and use it
instead of sys.stdout as the output stream.

(This reference implementation has been adopted in Python 2.0.)

Alternative Approaches

An alternative to this syntax change has been proposed (originally by
Moshe Zadka) which requires no syntax changes to Python. A writeln()
function could be provided (possibly as a builtin), that would act much
like extended print, with a few additional features:

    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() takes a three optional keyword arguments. In the context of
this proposal, the relevant argument is 'file' which can be set to a
file-like object with a write() method. Thus:

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

would be written as:

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

writeln() has the additional functionality that the keyword argument
'nl' is a flag specifying whether to append a newline or not, and an
argument 'sep' which specifies the separator to output in between each
item.

More Justification by the BDFL

The proposal has been challenged on the newsgroup. One series of
challenges doesn't like '>>' and would rather see some other symbol.

-   Challenge: Why not one of these?

        print in stderr items,....
        print + stderr items,.......
        print[stderr] items,.....
        print to stderr items,.....

    Response: If we want to use a special symbol (print <symbol>
    expression), the Python parser requires that it is not already a
    symbol that can start an expression -- otherwise it can't decide
    which form of print statement is used. (The Python parser is a
    simple LL(1) or recursive descent parser.)

    This means that we can't use the "keyword only in context trick"
    that was used for "import as", because an identifier can start an
    expression. This rules out +stderr, [sterr], and to stderr. It
    leaves us with binary operator symbols and other miscellaneous
    symbols that are currently illegal here, such as 'import'.

    If I had to choose between 'print in file' and 'print >> file' I
    would definitely choose '>>'. In part because 'in' would be a new
    invention (I know of no other language that uses it, while '>>' is
    used in sh, awk, Perl, and C++), in part because '>>', being
    non-alphabetic, stands out more so is more likely to catch the
    reader's attention.

-   Challenge: Why does there have to be a comma between the file and
    the rest?

    Response: The comma separating the file from the following
    expression is necessary! Of course you want the file to be an
    arbitrary expression, not just a single word. (You definitely want
    to be able to write print >>sys.stderr.) Without the expression the
    parser wouldn't be able to distinguish where that expression ends
    and where the next one begins, e.g.

        print >>i +1, 2
        print >>a [1], 2
        print >>f (1), 2

-   Challenge: Why do you need a syntax extension? Why not writeln(file,
    item, ...)?

    Response: First of all, this is lacking a feature of the print
    statement: the trailing comma to print which suppresses the final
    newline. Note that 'print a,' still isn't equivalent to
    'sys.stdout.write(a)' -- print inserts a space between items, and
    takes arbitrary objects as arguments; write() doesn't insert a space
    and requires a single string.

    When you are considering an extension for the print statement, it's
    not right to add a function or method that adds a new feature in one
    dimension (where the output goes) but takes away in another
    dimension (spaces between items, and the choice of trailing newline
    or not). We could add a whole slew of methods or functions to deal
    with the various cases but that seems to add more confusion than
    necessary, and would only make sense if we were to deprecate the
    print statement altogether.

    I feel that this debate is really about whether print should have
    been a function or method rather than a statement. If you are in the
    function camp, of course adding special syntax to the existing print
    statement is not something you like. I suspect the objection to the
    new syntax comes mostly from people who already think that the print
    statement was a bad idea. Am I right?

    About 10 years ago I debated with myself whether to make the most
    basic from of output a function or a statement; basically I was
    trying to decide between "print(item, ...)" and "print item, ...". I
    chose to make it a statement because printing needs to be taught
    very early on, and is very important in the programs that beginners
    write. Also, because ABC, which lead the way for so many things,
    made it a statement. In a move that's typical for the interaction
    between ABC and Python, I changed the name from WRITE to print, and
    reversed the convention for adding newlines from requiring extra
    syntax to add a newline (ABC used trailing slashes to indicate
    newlines) to requiring extra syntax (the trailing comma) to suppress
    the newline. I kept the feature that items are separated by
    whitespace on output.

    Full example: in ABC,

        WRITE 1
        WRITE 2/

    has the same effect as:

        print 1,
        print 2

    has in Python, outputting in effect "1 2n".

    I'm not 100% sure that the choice for a statement was right (ABC had
    the compelling reason that it used statement syntax for anything
    with side effects, but Python doesn't have this convention), but I'm
    also not convinced that it's wrong. I certainly like the economy of
    the print statement. (I'm a rabid Lisp-hater -- syntax-wise, not
    semantics-wise! -- and excessive parentheses in syntax annoy me.
    Don't ever write return(i) or if(x==y): in your Python code! :-)

    Anyway, I'm not ready to deprecate the print statement, and over the
    years we've had many requests for an option to specify the file.

-   Challenge: Why not > instead of >>?

    Response: To DOS and Unix users, >> suggests "append", while >
    suggests "overwrite"; the semantics are closest to append. Also, for
    C++ programmers, >> and << are I/O operators.

-   Challenge: But in C++, >> is input and << is output!

    Response: doesn't matter; C++ clearly took it from Unix and reversed
    the arrows. The important thing is that for output, the arrow points
    to the file.

-   Challenge: Surely you can design a println() function can do all
    what print>>file can do; why isn't that enough?

    Response: I think of this in terms of a simple programming exercise.
    Suppose a beginning programmer is asked to write a function that
    prints the tables of multiplication. A reasonable solution is:

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

    Now suppose the second exercise is to add printing to a different
    file. With the new syntax, the programmer only needs to learn one
    new thing: print >> file, and the answer can be like this:

        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

    With only a print statement and a println() function, the programmer
    first has to learn about println(), transforming the original
    program to using println():

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

    and then about the file keyword argument:

        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)

    Thus, the transformation path is longer:

        (1) print
        (2) print >> file

    vs.

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

    Note: defaulting the file argument to sys.stdout at compile time is
    wrong, because it doesn't work right when the caller assigns to
    sys.stdout and then uses tables() without specifying the file. This
    is a common problem (and would occur with a println() function too).
    The standard solution so far has been:

        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

    I've added a feature to the implementation (which I would also
    recommend to println()) whereby if the file argument is None,
    sys.stdout is automatically used. Thus,

        print >> None, foo bar

    (or, of course, print >> x where x is a variable whose value is
    None) means the same as

        print foo, bar

    and the tables() function can be written as follows:

        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

References

[1] http://docs.python.org/reference/simple_stmts.html#print

[2] http://sourceforge.net/patch/download.php?id=100970