reSTify PEP 214 (#319)

This commit is contained in:
Huang Huang 2017-08-11 23:35:05 +08:00 committed by Berker Peksag
parent 4dbfaec329
commit e889fa82a5
1 changed files with 282 additions and 262 deletions

View File

@ -5,366 +5,386 @@ Last-Modified: $Date$
Author: barry@python.org (Barry Warsaw)
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.
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:
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'
print >> mylogfile, 'this message goes to my log file'
Formally, the syntax of the extended print statement is
Formally, the syntax of the extended print statement is::
print_stmt: ... | '>>' test [ (',' test)+ [','] ] )
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:
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'
print 'hello world'
print >> sys.stdout, 'hello world'
As are these two statements:
As are these two statements::
print
print >> sys.stdout
print
print >> sys.stdout
These two statements are syntax errors:
These two statements are syntax errors::
print ,
print >> sys.stdout,
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:
'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
- 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 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
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
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.
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.
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.
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.)
(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.
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)
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
``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'
print >> mylogfile, 'this goes to my log file'
would be written as
would be written as::
writeln('this goes to my log file', file=mylogfile)
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.
``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.
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?
* 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.)
print in stderr items,....
print + stderr items,.......
print[stderr] items,.....
print to stderr items,.....
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'.
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.)
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.
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'.
Challenge: Why does there have to be a comma between the file and
the rest?
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.
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.
* Challenge: Why does there have to be a comma between the file and
the rest?
print >>i +1, 2
print >>a [1], 2
print >>f (1), 2
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.
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.
print >>i +1, 2
print >>a [1], 2
print >>f (1), 2
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.
* Challenge: Why do you need a syntax extension? Why not
writeln(file, item, ...)?
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?
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.
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.
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.
Full example: in ABC,
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?
WRITE 1
WRITE 2/
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.
has the same effect as
Full example: in ABC,
print 1,
print 2
::
has in Python, outputting in effect "1 2\n".
WRITE 1
WRITE 2/
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! :-)
has the same effect as::
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.
print 1,
print 2
Challenge: Why not > instead of >>?
has in Python, outputting in effect "1 2\n".
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.
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! :-)
Challenge: But in C++, >> is input and << is output!
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.
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: Why not > instead of >>?
Challenge: Surely you can design a println() function can do all
what print>>file can do; why isn't that enough?
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.
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:
* Challenge: But in C++, >> is input and << is output!
def tables(n):
for j in range(1, n+1):
for i in range(1, n+1):
print i, 'x', j, '=', i*j
print
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.
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:
* Challenge: Surely you can design a ``println()`` function can do all
what ``print>>file`` can do; why isn't that enough?
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
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::
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):
print i, 'x', j, '=', i*j
print
def tables(n):
for j in range(1, n+1):
for i in range(1, n+1):
println(i, 'x', j, '=', i*j)
println()
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::
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):
print >> file, i, 'x', j, '=', i*j
print >> 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)
With only a print statement and a ``println()`` function, the
programmer first has to learn about ``println()``, transforming the
original program to using ``println()``::
Thus, the transformation path is longer:
def tables(n):
for j in range(1, n+1):
for i in range(1, n+1):
println(i, 'x', j, '=', i*j)
println()
(1) print
(2) print >> file
and **then** about the file keyword argument::
vs.
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)
(1) print
(2) println()
(3) println(file=...)
Thus, the transformation path is longer::
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:
(1) print
(2) print >> file
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
vs.
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
(1) print
(2) println()
(3) println(file=...)
(or, of course, print >> x where x is a variable whose value is
None) means the same as
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::
print foo, bar
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
and the tables() function can be written as follows:
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,
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
::
[XXX this needs more justification, and a section of its own]
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
.. XXX this needs more justification, and a section of its own
References
==========
[1] http://docs.python.org/reference/simple_stmts.html#print
[2] http://sourceforge.net/patch/download.php?id=100970
.. [1] http://docs.python.org/reference/simple_stmts.html#print
.. [2] http://sourceforge.net/patch/download.php?id=100970
Local Variables:
mode: indented-text
indent-tabs-mode: nil
End:
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
End: