Differences between revisions 1 and 27 (spanning 26 versions)
Revision 1 as of 2005-09-03 01:32:31
Size: 281
Editor: NickCoghlan
Comment:
Revision 27 as of 2005-09-03 18:45:09
Size: 9933
Editor: IGLD-83-130-72-115
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
This page discusses the benefits of replacing the current print statement with an equivalent builtin. The output function presented below does everything the print statement does without requiring an hacking of the grammar, and also makes a number of things significantly easier. 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 will probably be turned into a PEP at some point.

=== 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

=== 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.htm 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

=== Sample implementation ===
This is a Python 2.4 compatible sample implementation. This version of `writeln` doesn't provide a `linesep` keyword argument in order to keep things simple.

{{{#!python
def write(*args, **kwds):
    """Functional replacement for the print statement

    This function does NOT automatically append a line separator (use writeln for that)
    """

    def parse_kwds(sep=" ", stream=sys.stdout):
        """ Helper function to parse keyword arguments """
        return sep, stream
        
    # Nothing to do if no positional arguments
    if not args:
        return
    sep, stream = parse_kwds(**kwds)
    # Perform the print operation without building the whole string
    stream.write(str(args[0]))
    for arg in args[1:]:
        stream.write(sep)
        stream.write(str(arg))
        
def writeln(*args, **kwds):
    """Functional replacement for the print statement

    >>> writeln(1, 2, 3)
    1 2 3
    >>> writeln(1, 2, 3, sep='')
    123
    >>> writeln(1, 2, 3, sep=', ')
    1, 2, 3
    >>> import sys
    >>> writeln(1, 2, 3, stream=sys.stderr)
    1 2 3
    >>> writeln(*range(10))
    0 1 2 3 4 5 6 7 8 9
    >>> writeln(*(x*x for x in range(10)))
    0 1 4 9 16 25 36 49 64 81
    """
    # Perform the print operation without building the whole string
    write(*args, **kwds)
    write("\n", **kwds)
}}}

=== Code comparisons ===
These are some comparisons of current `print` statements with the equivalent code using the builtins `write` and `writeln`.

{{{#!python
# Standard printing
print 1, 2, 3
writeln(1, 2, 3)

# Printing without any spaces
print "%d%d%d" % (1, 2, 3)
writeln(1, 2, 3, sep='')

# Print as comma separated list
print "%d, %d, %d" % (1, 2, 3)
writeln(1, 2, 3, sep=', ')

# Print without a trailing newline
print 1, 2, 3,
write(1, 2, 3)

# Print to a different stream
print >> sys.stderr, 1, 2, 3
writeln(1, 2, 3, stream=sys.stderr)

# Print a simple sequence
print " ".join(map(str, range(10)))
writeln(*range(10))

# Print a generator expression
print " ".join(str(x*x) for x in range(10))
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

{{{#!python
# Print without a trailing newline
print 1, 2, 3,
writeln(1, 2, 3, end='')
# or (shorthand)
writeln(1, 2, 3, '')
}}}

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''

=== 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:

{{{#!python
# Print a generator expression
print " ".join(str(x*x) for x in range(10))
writeln(x*x for x in range(10))
# Or optionally
writeln((x*x for x in range(10)), iter=1)
}}}

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''

=== 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''

{{{#!python
import sys
from string import Template

class Separator:
    def __init__(self, sep):
        self.sep = sep

SPACE = Separator(' ')
EMPTY = Separator('')


def writef(fmt, *args, **kws):
    if 'to' in kws:
        to = kws.get('to')
        del kws['to']
    else:
        to = sys.stdout
    if 'nl' in kws:
        nl = kws.get('nl')
        del kws['nl']
        if nl is True:
            nl = '\n'
        elif nl is False:
            nl = ''
    else:
        nl = '\n'
    if isinstance(fmt, Template):
        if args:
            raise TypeError('invalid positional arguments')
        s = fmt.substitute(kws)
    else:
        if kws:
            raise TypeError('invalid keyword arguments')
        s = fmt % args
    to.write(s)
    to.write(nl)


def write(*args, **kws):
    if 'to' in kws:
        to = kws.get('to')
        del kws['to']
    else:
        to = sys.stdout
    if 'nl' in kws:
        nl = kws.get('nl')
        del kws['nl']
        if nl is True:
            nl = '\n'
        elif nl is False:
            nl = ''
    else:
        nl = '\n'
    if 'sep' in kws:
        sep = kws.get('sep')
        del kws['sep']
    else:
        sep = ' '
    if kws:
        raise TypeError('invalid keyword arguments')
    it = iter(args)
    # Suppress leading separator, but consume all Separator instances
    for s in it:
        if isinstance(s, Separator):
            sep = args[0].sep
        else:
            # Don't write a leading separator
            to.write(str(s))
            break
    for s in it:
        if isinstance(s, Separator):
            sep = s.sep
        else:
            to.write(sep)
            to.write(str(s))
    to.write(nl)


obj = object()
refs = sys.getrefcount(obj)

write('obj:', obj, 'refs:', refs)
write(Separator(': '), 'obj', obj,
      Separator(', '), 'refs',
      Separator(': '), refs,
      nl=False)
write()

writef('obj: %s, refs: %s', obj, refs)
writef(Template('obj: $obj, refs: $refs, obj: $obj'),
       obj=obj, refs=refs,
       to=sys.stderr,
       nl=False)
write()}}}

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 will probably be turned into a PEP at some point.

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

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.htm 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

Sample implementation

This is a Python 2.4 compatible sample implementation. This version of writeln doesn't provide a linesep keyword argument in order to keep things simple.

   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     def parse_kwds(sep=" ", stream=sys.stdout):
   8         """ Helper function to parse keyword arguments """
   9         return sep, stream
  10         
  11     # Nothing to do if no positional arguments
  12     if not args:
  13         return
  14     sep, stream = parse_kwds(**kwds)
  15     # Perform the print operation without building the whole string
  16     stream.write(str(args[0]))
  17     for arg in args[1:]:
  18         stream.write(sep)
  19         stream.write(str(arg))
  20         
  21 def writeln(*args, **kwds):
  22     """Functional replacement for the print statement
  23 
  24     >>> writeln(1, 2, 3)
  25     1 2 3
  26     >>> writeln(1, 2, 3, sep='')
  27     123
  28     >>> writeln(1, 2, 3, sep=', ')
  29     1, 2, 3
  30     >>> import sys
  31     >>> writeln(1, 2, 3, stream=sys.stderr)
  32     1 2 3
  33     >>> writeln(*range(10))
  34     0 1 2 3 4 5 6 7 8 9
  35     >>> writeln(*(x*x for x in range(10)))
  36     0 1 4 9 16 25 36 49 64 81
  37     """
  38     # Perform the print operation without building the whole string
  39     write(*args, **kwds)
  40     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

   1 # Print without a trailing newline
   2 print 1, 2, 3,
   3 writeln(1, 2, 3, end='')
   4 # or (shorthand)
   5 writeln(1, 2, 3, '')

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

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:

   1 # Print a generator expression
   2 print " ".join(str(x*x) for x in range(10))
   3 writeln(x*x for x in range(10))
   4 # Or optionally
   5 writeln((x*x for x in range(10)), iter=1)

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

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
  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()

PrintAsFunction (last edited 2011-08-14 09:25:04 by eth595)

Unable to edit the page? See the FrontPage for instructions.