364 lines
13 KiB
Plaintext
364 lines
13 KiB
Plaintext
PEP: 214
|
||
Title: Extended Print Statement
|
||
Version: $Revision$
|
||
Author: bwarsaw@beopen.com (Barry A. Warsaw)
|
||
Python-Version: 2.0
|
||
Status: Final
|
||
Created: 24-Jul-2000
|
||
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
|
||
|
||
sys.stdout = mylogfile
|
||
try:
|
||
print 'this message goes to my log file'
|
||
finally:
|
||
sys.stdout = sys.__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.
|
||
|
||
|
||
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 would'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 2\n".
|
||
|
||
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://www.python.org/doc/current/ref/print.html
|
||
[2] http://sourceforge.net/patch/download.php?id=100970
|
||
|
||
|
||
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
End:
|