Size: 14686
Comment: Added BDFL comments.
|
Size: 14928
Comment: One more.
|
Deletions are marked like this. | Additions are marked like this. |
Line 133: | Line 133: |
Oh, and you will never get away with using the final empty string to mean "no newline". This would be very confusing for someone who printed a variable like so: {{{print("The value is:", x)}}} when the variable happens to be empty. |
This page discusses the benefits of replacing the current print statement with an equivalent builtin. The write and writeln functions presented below do everything the print statement does without requiring any hacking of the grammar, and also make a number of things significantly easier.
Guido has made it clear he wants to get rid of the print statement in ["Python3.0"]. This page considers why we would want to go that way, and how we can actually get there. It should be turned into a PEP eventually.
Benefits of using a function instead of a statement
- Extended call syntax provides better interaction with sequences
Keyword argument sep allows item separator to be changed easily and obviously
Keyword argument linesep could optionally allow line separator to be changed easily and obviously
Keyword argument stream allows easy and obvious redirection
- The builtin can be replaced for application wide customisation (e.g. per-thread logging)
- Interacts well with PEP 309's partial function application, and the rest of Python's ability to handle functions
BDFL comments:
- don't waste your time on sequence printing
- I'm not excited about sep and linesep keyword args
- add to benefits: easier transition to other function/method calls
- if it were me, I'd use 'to=' or 'file=' rather than 'stream=' (too long)
Getting there from here
The example implementation below shows that creating a function with the desired behaviour is quite straightforward. However, calling the builtin print is a problem due to the fact that print is a reserved word in Python 2.x. Since the print statement will be around until Py3K allows us to break backwards compatibility, devising a transition plan that lets programmers 'get ready early' for the Py3K transition becomes a significant challenge.
If, on the other hand, the builtin has a different name, it is quite feasible to introduce it during the 2.x series. In [http://www.python.org/peps/pep-3000.html PEP 3000], it is suggested that the print statement be replaced by two builtins: write and writeln. These names are used in the example below. By using alternative names, and providing the builtins in the 2.x series, it is possible to 'future-proof' code against the removal of the print statement in Py3k.
This technique of having two printing operations is not uncommon - Java has both print and println methods, and C# has Write and WriteLine. The main problem with the approach is that the writeln form will actually be more commonly used, but has the longer, less obvious name of the two proposed functions. This perception of relative use is based on a comparison of relative usage levels of the two current forms of the print statement (i.e., with and without the trailing comma) by some of the developers on python-dev.
Some other names for the builtins which have been suggested are:
print - excellent name, but causes transition problems as described above
println - avoids the transition problems, reflects default behaviour of adding a line, matches Java method name
printline - alternative to println, that avoids the somewhat cryptic abbreviation
writeline - alternative to writeln that avoids the somewhat cryptic abbreviation
out - not a verb, and converting to it may be problematic due to shadowing by variable names
output - nice symmetry with input, but using the term as a verb is not typical
prnt - easily edited into print later on
write - decent name, but confusing when compared to write() method
Sample implementation
This is a Python 2.4 compatible sample implementation of the approach currently in [http://www.python.org/peps/pep-3000.html PEP 3000]. This version of writeln doesn't provide a linesep keyword argument in order to keep things simple. Some other variations are covered further down this Wiki page.
1 def write(*args, **kwds):
2 """Functional replacement for the print statement
3
4 This function does NOT automatically append a line separator (use writeln for that)
5 """
6
7 # Nothing to do if no positional arguments
8 if not args:
9 return
10
11 def parse_kwds(sep=" ", stream=sys.stdout):
12 """ Helper function to parse keyword arguments """
13 return sep, stream
14 sep, stream = parse_kwds(**kwds)
15
16 # Perform the print operation without building the whole string
17 stream.write(str(args[0]))
18 for arg in args[1:]:
19 stream.write(sep)
20 stream.write(str(arg))
21
22 def writeln(*args, **kwds):
23 """Functional replacement for the print statement
24
25 >>> writeln(1, 2, 3)
26 1 2 3
27 >>> writeln(1, 2, 3, sep='')
28 123
29 >>> writeln(1, 2, 3, sep=', ')
30 1, 2, 3
31 >>> import sys
32 >>> writeln(1, 2, 3, stream=sys.stderr)
33 1 2 3
34 >>> writeln(*range(10))
35 0 1 2 3 4 5 6 7 8 9
36 >>> writeln(*(x*x for x in range(10)))
37 0 1 4 9 16 25 36 49 64 81
38 """
39 # Perform the print operation without building the whole string
40 write(*args, **kwds)
41 write("\n", **kwds)
Code comparisons
These are some comparisons of current print statements with the equivalent code using the builtins write and writeln.
1 # Standard printing
2 print 1, 2, 3
3 writeln(1, 2, 3)
4
5 # Printing without any spaces
6 print "%d%d%d" % (1, 2, 3)
7 writeln(1, 2, 3, sep='')
8
9 # Print as comma separated list
10 print "%d, %d, %d" % (1, 2, 3)
11 writeln(1, 2, 3, sep=', ')
12
13 # Print without a trailing newline
14 print 1, 2, 3,
15 write(1, 2, 3)
16
17 # Print to a different stream
18 print >> sys.stderr, 1, 2, 3
19 writeln(1, 2, 3, stream=sys.stderr)
20
21 # Print a simple sequence
22 print " ".join(map(str, range(10)))
23 writeln(*range(10))
24
25 # Print a generator expression
26 print " ".join(str(x*x) for x in range(10))
27 writeln(*(x*x for x in range(10)))
Newline / No-newline
Another possibility to deal with the newline / no-newline cases would be to have a single function which would take an extra keyword argument "linesep" or "end" (or perhaps some slight magic: an empty string as the last argument), so to print without newline, you would do
The default case should be to insert a newline.
I quite like the single function idea (early versions of this Wiki page used only a single function), but giving it a good name is challenging. The version without the keyword argument is a definite non-starter, though, as there is far too much risk of quirky behaviour when printing a string variable which just happens to contain the empty string. - Nick Coghlan
BDFL comments: I definitely am not keen on the single function with keyword args. IMO all you need is a companion function that inserts no separator and no newline; the desired separators are then easily given explicitly. Oh, and you will never get away with using the final empty string to mean "no newline". This would be very confusing for someone who printed a variable like so: print("The value is:", x) when the variable happens to be empty.
Iterating Iterables
Another potentially interesting improvement could be for the function to iterate all iterables, in order to be able to use generator expressions without having to use the star syntax and to avoid the creation of a temporary sequence. This would allow:
This behaviour could be optionally triggered by a keyword argument "iter". Another possibility would be to always do the iteration and to force the caller to str() the generator if he wants to print it without iteration (happens rarely).
Nailing down this kind of behaviour is trickier than one might think. The python-dev discussion of the Python 2.5 candidate library function [http://mail.python.org/pipermail/python-dev/2005-March/052215.html itertools.walk] goes over some of the potential problems. We've survived without fancy iterator handling in the print statement - let's avoid adding anything we don't have a demonstrated need for (the extended call syntax stuff comes 'for free' with the conversion to using a function). - Nick Coghlan
BDFL comments: bah. implicitly exhausting iterables has side effects, which is a bad idea for a print function. It would not be a good idea if commenting out a print() call changes the behavior of the program.
Another Strawman
Here's my own strawman implementation of write() and writef() using semantics I think are pretty useful. I'll post to python-dev about the details. - Barry Warsaw
1 import sys
2 from string import Template
3
4 class Separator:
5 def __init__(self, sep):
6 self.sep = sep
7
8 SPACE = Separator(' ')
9 EMPTY = Separator('')
10
11
12 def writef(fmt, *args, **kws):
13 if 'to' in kws:
14 to = kws.get('to')
15 del kws['to']
16 else:
17 to = sys.stdout
18 if 'nl' in kws:
19 nl = kws.get('nl')
20 del kws['nl']
21 if nl is True:
22 nl = '\n'
23 elif nl is False:
24 nl = ''
25 else:
26 nl = '\n'
27 if isinstance(fmt, Template):
28 if args:
29 raise TypeError('invalid positional arguments')
30 s = fmt.substitute(kws)
31 else:
32 if kws:
33 raise TypeError('invalid keyword arguments')
34 s = fmt % args
35 to.write(s)
36 to.write(nl)
37
38
39 def write(*args, **kws):
40 if 'to' in kws:
41 to = kws.get('to')
42 del kws['to']
43 else:
44 to = sys.stdout
45 if 'nl' in kws:
46 nl = kws.get('nl')
47 del kws['nl']
48 if nl is True:
49 nl = '\n'
50 elif nl is False:
51 nl = ''
52 else:
53 nl = '\n'
54 if 'sep' in kws:
55 sep = kws.get('sep')
56 del kws['sep']
57 else:
58 sep = ' '
59 if kws:
60 raise TypeError('invalid keyword arguments')
61 it = iter(args)
62 # Suppress leading separator, but consume all Separator instances
63 for s in it:
64 if isinstance(s, Separator):
65 sep = args[0].sep # Should this be s.sep?
66 else:
67 # Don't write a leading separator
68 to.write(str(s))
69 break
70 for s in it:
71 if isinstance(s, Separator):
72 sep = s.sep
73 else:
74 to.write(sep)
75 to.write(str(s))
76 to.write(nl)
77
78
79 obj = object()
80 refs = sys.getrefcount(obj)
81
82 write('obj:', obj, 'refs:', refs)
83 write(Separator(': '), 'obj', obj,
84 Separator(', '), 'refs',
85 Separator(': '), refs,
86 nl=False)
87 write()
88
89 writef('obj: %s, refs: %s', obj, refs)
90 writef(Template('obj: $obj, refs: $refs, obj: $obj'),
91 obj=obj, refs=refs,
92 to=sys.stderr,
93 nl=False)
94 write()
For the code comparisons shown earlier, simply put write where writeln is currently used, and add the keyword argument nl=False for the no trailing newline case. I quite like this approach. - Nick Coghlan
BDFL comments: I like the write/writef parallel; would like it even more if it was print/printf. But please drop the Separator thing. The use case isn't common enough to burden people with the possibility. Also, we need to spend more time researching the formatting language. (See a post in python-dev by Steven Bethard: "string formatting options and removing basestring.mod".
Another variant - `format` builtin
Barry's writef builtin cuts down a little on the typing, but is somewhat inflexible in that it only supports string % or string.Template formatting when printing directly to a stream. It also causes problems by preventing the use of to or nl as keywords in the format string. A separate format builtin would deal with both of those problems, at the expense of some extra typing when using it. Such a builtin would also help with avoiding some of the tuple related quirks of the string mod operator, as well as making it easy to write code that supports both types of string formatting. The version below is based on Barry's, but eliminates the Separator concept, and replaces writef with format - Nick Coghlan
1 import sys
2 from string import Template
3
4 # Real implementation would avoid blocking use of 'fmt'
5 # as an element of the formatting string
6 def format(fmt, *args, **kws):
7 if isinstance(fmt, Template):
8 if args:
9 raise TypeError('invalid positional arguments')
10 s = fmt.substitute(kws)
11 else:
12 if kws:
13 s = fmt % kws
14 else:
15 s = fmt % args
16 return s
17
18
19 def write(*args, **kws):
20 if 'to' in kws:
21 to = kws.get('to')
22 del kws['to']
23 else:
24 to = sys.stdout
25 if 'nl' in kws:
26 nl = kws.get('nl')
27 del kws['nl']
28 if nl is True:
29 nl = '\n'
30 elif nl is False:
31 nl = ''
32 else:
33 nl = '\n'
34 if 'sep' in kws:
35 sep = kws.get('sep')
36 del kws['sep']
37 else:
38 sep = ' '
39 if kws:
40 raise TypeError('invalid keyword arguments')
41 for s in args[:1]:
42 to.write(str(s))
43 for s in args[1:]:
44 to.write(sep)
45 to.write(str(s))
46 to.write(nl)
47
48
49 obj = object()
50 refs = sys.getrefcount(obj)
51
52 write('obj:', obj, 'refs:', refs)
53 write('obj:', obj, 'refs:', refs, sep=', ', nl=False)
54 write()
55
56 write(format('obj: %s, refs: %s', obj, refs))
57 write(format('obj: %(obj)s, refs: %(refs)s', obj=obj, refs=refs))
58 write(format(Template('obj: $obj, refs: $refs, obj: $obj'),
59 obj=obj, refs=refs),
60 to=sys.stderr,
61 nl=False)
62 write()
Displaying iterators
I'm looking into an approach which adds explicit support for displaying iterators into the string mod operator. The intent is that "%''j" % (my_seq,) will become roughly equivalent to ''.join(map(str, my_seq)). - Nick Coghlan
[http://sourceforge.net/tracker/?func=detail&aid=1281573&group_id=5470&atid=305470 SF Patch #1281573] for anyone who wants to play with it. Only strings are supported so far (no Unicode), but it illustrates the concept quite well.
BDFL comments: again, please don't do this.