Permalink
Cannot retrieve contributors at this time
390 lines (293 sloc)
13 KB
| PEP: 214 | |
| Title: Extended Print Statement | |
| Version: $Revision$ | |
| 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. | |
| 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 >> 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 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 | |
| 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 | |
| .. 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 | |
| .. | |
| Local Variables: | |
| mode: indented-text | |
| indent-tabs-mode: nil | |
| End: |