|
|
Subscribe / Log in / New account

The (non-)return of the Python print statement

LWN.net needs you!

Without subscribers, LWN would simply not exist. Please consider signing up for a subscription and helping to keep LWN publishing

By Jake Edge
July 1, 2020

In what may have seemed like an April Fool's Day joke to some, Python creator Guido van Rossum recently floated the idea of bringing back the print statement—several months after Python 2, which had such a statement, reached its end of life. In fact, Van Rossum acknowledged that readers of his message to the python-ideas mailing list might be checking the date: "No, it's not April 1st." He was serious about the idea—at least if others were interested in having the feature—but he withdrew it fairly quickly when it became clear that there were few takers. The main reason he brought it up is interesting, though: the new parser for CPython makes it easy to bring back print from Python 2 (and before).

Prior to Python 3, the print statement was the usual way to print output to the screen:

    >>> print '1 + 2 = ', 1+2
    1 + 2 = 3
But Python 3 changed print from a statement to the print() function. Of the changes for Python 3, switching to print() was perhaps one of the easiest, but it still led to a fair number of complaints. It was rather straightforward for Python 2 code to adopt the new behavior using:
    from __future__ import print_function
But the change did break a lot of working code, so perhaps it makes sense to bring back the print statement, Van Rossum said.

The new parser is based on a parsing expression grammar (PEG) and it was a fairly simple matter to make the change: "One thing that the PEG parser makes possible in about 20 lines of code is something not entirely different from the old print statement." He has a prototype working, but it enables far more than just print statements:

But wait, there's more! The same syntax will make it possible to call *any* function:
>>> len "abc"
3
Or any method:
>>> import sys
>>> sys.getrefcount "abc"
24
Really, *any* method:
>>> class C:
...   def foo(self, arg): print arg
... 
>>> C().foo 2+2
4

He noted that there are some downsides too, including a "bare" print statement being interpreted as the print() function and not a call to it. Potentially more problematic is the behavior when the first argument to the print statement uses parentheses.

>>> print (1, 2, 3)
1 2 3
[...]
>>> print (2+2), 42
4
(None, 42)
Those examples may be puzzling to some readers. In the first, it might seem that a tuple is being passed to the print statement, but the space is not significant; the parser sees a call to the print() function with three arguments. In the second, the same behavior can be seen in Python 3 with "print (2+2), 42". The Python read-eval-print loop (REPL) evaluates print first (either as a statement or a function), which results in the output of "4"; it then prints the result of the whole statement, which is a tuple containing the return value of print (i.e. None) and 42.

Currently a bunch of effort is made in the parser to recognize code that is trying to use the print statement, so that it results in a SyntaxError that suggests adding parentheses ("Did you mean print('hello world')"). That code could be removed if print was resurrected. Van Rossum said that it was not an "all or nothing" proposal, it could be dialed back somewhat by restricting what kinds of function calls it would work for or restricting it only to "print". He also noted that he would withdraw the idea "if the response is a resounding 'boo, hiss'".

It may not have been resounding, but the response was definitely mostly of the "boo, hiss" variety (including some that used that phrase directly, of course). Ethan Furman said that while too many parentheses made code hard to read for him, too few is also problematic, so he was not in favor of any change. Naomi Ceder had mixed feelings; she has struggled to switch to the print() function over the last five years (after 15 years of print without parentheses). "As someone who teaches Python and groans at explaining exceptions, I'm -0 on print without parens and -1 on other calls without parens."

Gregory P. Smith agreed with Ceder, but took it further ("-1 overall for me..."), even though Smith liked the print statement for Python 2 and earlier. Beyond just print, though, he is concerned that calling functions without requiring parentheses will just lead to "a whole new world of typos and misunderstandings". Other languages allow that kind of thing, but Python is not those languages. "I love that the new parser allows us to even explore these possibilities. We should be very cautious about what syntax changes we actually adopt."

Overall, Smith's sentiment seemed popular; there were some who viewed parts of the idea favorably, but the full-blown proposal, including making any kind of call without parentheses, was not well-liked. For his part, Van Rossum said there was an element of "because we can" to the idea; he was pleasantly surprised at how easy it was to make it work with the new parser. He described why the PEG parser made things so much easier in his initial message:

[...] The PEG parser makes this much simpler, because it can simply backtrack -- by placing the grammar rule for this syntax (tentatively called "call statement") last in the list of alternatives for "small statement" we ensure that everything that's a valid expression statement (including print() calls) is still an expression statement with exactly the same meaning, while still allowing parameter-less function calls, without lexical hacks. (There is no code in my prototype that checks for a space after 'print' -- it just checks that there's a name, number or string following a name, which is never legal syntax.)

But Van Rossum said that he would "happily withdraw" the idea since it did not seem to be gaining any real traction. It seems likely that the idea came out of the blue for many—there were multiple good reasons to switch to a print() function as part of the Python 3 transition, after all. But the proposal does serve another purpose: it allows the CPython core developers to see the scope of the types of changes the PEG parser is bringing to the table. That may well open up some interesting features moving forward.


Index entries for this article
PythonEnhancements


(Log in to post comments)

The (non-)return of the Python print statement

Posted Jul 1, 2020 22:18 UTC (Wed) by benhoyt (subscriber, #138463) [Link]

I'm very glad this change didn't get any traction. Print being a statement (well, really being special) was an annoyance in Python 2, and makes much more sense as a plain old function in Python 3. It'd be weird to have two different ways to print stuff (with parens and without), and avoids the whole "is it a tuple or are these function args?". I so much prefer "print('foo', file=sys.stderr)" over "print >>sys.stderr, 'foo'". And "len 'abc'" ... that's just crazy talk. :-)

The (non-)return of the Python print statement

Posted Jul 1, 2020 22:57 UTC (Wed) by jafd (subscriber, #129642) [Link]

Oh, with those non-parenthetical calls they almost pulled a Perl.

If the proposal got accepted, though, I'm wondering if there wouldn't be an ambiguity if a function call was without arguments: is this a reference to the function itself or to its result? Or should we specify &foo for the former, like, again, Perl?

The (non-)return of the Python print statement

Posted Jul 2, 2020 13:57 UTC (Thu) by smitty_one_each (subscriber, #28989) [Link]

See your Perl; raise you Visual Basic #ForTheWin

The (non-)return of the Python print statement

Posted Jul 3, 2020 1:34 UTC (Fri) by joey (guest, #328) [Link]

More like a Haskell really, but the non-tuples gave it away.

The (non-)return of the Python print statement

Posted Jul 3, 2020 3:45 UTC (Fri) by ibukanov (subscriber, #3942) [Link]

Haskell does not have that problem as the language is lazy and functional. So there is no difference between a reference to a function or its result.

The (non-)return of the Python print statement

Posted Jul 3, 2020 10:04 UTC (Fri) by marcH (subscriber, #57642) [Link]

> Oh, with those non-parenthetical calls they almost pulled a Perl.

Exactly https://en.wikipedia.org/wiki/There%27s_more_than_one_way...

The (non-)return of the Python print statement

Posted Jul 1, 2020 23:34 UTC (Wed) by kjp (guest, #39639) [Link]

> Van Rossum said there was an element of "because we can" to the idea;

There is a whole lot of venting I could post in response to this right now, but I'm done. I'm just done.

The (non-)return of the Python print statement

Posted Jul 2, 2020 9:02 UTC (Thu) by dw (guest, #12017) [Link]

Eventually the kindest thing you can do for any old horse is to take it out back and shoot it.

The (non-)return of the Python print statement

Posted Jul 3, 2020 5:52 UTC (Fri) by NYKevin (subscriber, #129325) [Link]

To be fair, GvR means "because we can now." In earlier Python 3.x, this was not possible. See for example this code, which is trying to decide whether or not the syntax error we are about to raise was caused by a 2.x-style print statement.

TL;DR: It's processing the code as a raw string, by hand. No AST, no tokenizer, just raw Unicode. It's mostly equivalent to grep -E '^[[:space:]]*print '. It should be entirely unsurprising that you can't "simply" convert this custom syntax error into a valid parse.

The (non-)return of the Python print statement

Posted Jul 2, 2020 0:02 UTC (Thu) by roc (subscriber, #30627) [Link]

Reintroducing print statements just after everyone had to finish converting their print statements to print functions is just rubbing salt in the wound.

The (non-)return of the Python print statement

Posted Jul 2, 2020 0:48 UTC (Thu) by maney (subscriber, #12630) [Link]

My first thought was along the lines of another comment here: we just got done with that conversion! But there is in fact one place that my fingers persist in wishing print statements were still a thing: when doing discovery/experimentation in the REPL. The things that were real warts (yeah >>filething, yuck) are non-issues there.

Or maybe my fingers are just lazy - they keep trying to leave the parens off of dir, too. :-)

The (non-)return of the Python print statement

Posted Jul 3, 2020 14:23 UTC (Fri) by raoni (guest, #137137) [Link]

I too have this instinct. Bu I think it is a typing mindset that is context dependent, when on shell or REPL you are on a "bash" mindset, that is different than when on an text editor.

The (non-)return of the Python print statement

Posted Jul 2, 2020 1:03 UTC (Thu) by kokada (guest, #92849) [Link]

Glad this proposal didn't gain track. Most languages that I know that allow calling functions without parenthesis (Ruby, Elixir) are either ambiguous causing unnecessary confusion (Ruby) or this is discouraged by the language users even if possible (Elixir).

One case that I think having function without parenthesis helps is when you're calling multiple functions, something like:

print fn1(fn2(foo))

But in this case, I really prefer thread macros instead:

(-> foo fn2 fn1 print)

I would love if Python introduced something like above instead.

The (non-)return of the Python print statement

Posted Jul 2, 2020 9:25 UTC (Thu) by ragnar (guest, #139237) [Link]

You could do something like this:
def apply(first_arg, *args):
    res = first_arg
    for item in args:
        res = item(res)
    return res

def partial(fn, *args):
    def _inner(*inner_args):
        return fn(*(args + inner_args))
    return _inner

apply('foo', len, partial(operator.mul, 7), print)

The (non-)return of the Python print statement

Posted Jul 3, 2020 16:22 UTC (Fri) by xi0n (subscriber, #138144) [Link]

You don’t even need to define partial(), as it’s already present in functools.

The (non-)return of the Python print statement

Posted Jul 9, 2020 1:13 UTC (Thu) by kokada (guest, #92849) [Link]

Yeah, I know, but having this in the language itself would be great.

The (non-)return of the Python print statement

Posted Jul 5, 2020 19:58 UTC (Sun) by NAR (subscriber, #1313) [Link]

Elixir came to my mind as well when I read this article. It's really confusing when parenthesis are omitted, but I think that's what makes some Ecto code look like SQL, e.g. instead of this:

query = from(c in City, select: {c.name, c.population})

one can write

query = from c in City, select: {c.name, c.population}

The (non-)return of the Python print statement

Posted Jul 6, 2020 19:24 UTC (Mon) by niner (subscriber, #26151) [Link]

In Raku you could use the compose function operator:
my &composed = &print ∘ &fn2 ∘ &fn1;
composed(foo);

or directly:
(&print ∘ &fn2 ∘ &fn1)(foo)

Particularily handy in combination with the reduction meta operator [] when you have a list of functions:
my &composed = [∘] @function-list; # i.e. @function-list[0] ∘ @function-list[1] ∘ @function-list[2] ...
composed foo; # because parentheses aren't really needed anyway

The (non-)return of the Python print statement

Posted Jul 2, 2020 1:04 UTC (Thu) by ncm (guest, #165) [Link]

If the print function were to return itself, then we could have (had)

>>> print (2+2) 42
4 42

but no one would ever have wanted that.

The (non-)return of the Python print statement

Posted Jul 2, 2020 2:24 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

Haskell does that, but Haskell also automatically curries everything. Since Python has always had a strong difference between foo(bar, baz) and foo(bar)(baz), I doubt it would be a Good Idea.

The (non-)return of the Python print statement

Posted Jul 2, 2020 10:11 UTC (Thu) by mina86 (guest, #68442) [Link]

First formatted strings, now attempt to allow function calls without parenthesises. It’s like they’re trying to tell us Perl was right all along. ;)

The (non-)return of the Python print statement

Posted Jul 2, 2020 11:10 UTC (Thu) by dskoll (subscriber, #1630) [Link]

What's that about Perl?

The (non-)return of the Python print statement

Posted Jul 2, 2020 13:34 UTC (Thu) by jafd (subscriber, #129642) [Link]

In Perl, parentheses in function calls are optional.

To pass an actual subroutine reference somewhere, you need to specify it as \&foo.

The (non-)return of the Python print statement

Posted Jul 2, 2020 13:49 UTC (Thu) by dskoll (subscriber, #1630) [Link]

I know. I posted a link to a funny XKCD cartoon.

The (non-)return of the Python print statement

Posted Jul 2, 2020 13:51 UTC (Thu) by jafd (subscriber, #129642) [Link]

Oh. I have a blind spot wherever those four letters are invoked.

The (non-)return of the Python print statement

Posted Jul 2, 2020 10:55 UTC (Thu) by rschroev (subscriber, #4164) [Link]

Please no. Explicit is better than implicit. Python now makes a very clear distinction between just referring to a function (no parentheses), and calling a function (parentheses), unlike some other languages. I would hate to loose that.

Just because you can do something, doesn't mean you should..

The (non-)return of the Python print statement

Posted Jul 2, 2020 13:43 UTC (Thu) by jafd (subscriber, #129642) [Link]

I'd rather have a demonstration of how one could, for example, create a new type of string — say I want to embed SQL which will get validated all over my codebase before my program even runs.

the_sql = sql"SELECT * FROM foobar WHERE (x = :placeholder)"

And if my little prerun hook is not present, the string is being treated as just string.

Or make it HTML, or whatever else code or markup that you'd like to throw over the fence at another system.

But for all gods' sake, let the print statement burn already. You Python people were putting it down for 12 years already, and it saddens me to no end that some people still can't let go of it.

The (non-)return of the Python print statement

Posted Jul 5, 2020 12:58 UTC (Sun) by quotemstr (subscriber, #45331) [Link]

Language designers sometimes, late in the evolution of their languages, propose the weirdest mechanisms. Here we have a function call syntax in Python that I couldn't be more against. In C++-land, we have Bjarne Stroustrup's weird idea [1] for a "unified call syntax". Sometimes things are done and should be left alone.

As for Python's PEG parser: I really dislike this trend towards complicated and context-sensitive language grammars. I don't want to have to backtrack across half a megabyte of crap to figure out what a token means. LALR is powerful enough to express anything you might reasonably want in a programming language. For God's sake, let's keep language grammars simple.

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n...

The (non-)return of the Python print statement

Posted Jul 5, 2020 19:46 UTC (Sun) by NAR (subscriber, #1313) [Link]

Actually that weird C++ idea makes some sense, it's too bad that C-compatibility (existence of free functions) causes problems decades later.

The (non-)return of the Python print statement

Posted Jul 5, 2020 19:48 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

This makes no sense at all. C++ is not Java and free functions are an integral feature. Especially since object methods can behave drastically differently from functions.

The (non-)return of the Python print statement

Posted Jul 11, 2020 14:21 UTC (Sat) by nix (subscriber, #2304) [Link]

Quite. As the proposal notes, people have been moving *towards* free functions for the cases considered, not away from them, so there's no way they could be considered anything like deprecated.

The (non-)return of the Python print statement

Posted Jul 14, 2020 5:01 UTC (Tue) by jezuch (subscriber, #52988) [Link]

(To me that proposal sounded like "we've created this problem for ourselves so let's wade deeper into the weeds!" Because language design is fun or something?)

The (non-)return of the Python print statement

Posted Jul 18, 2020 11:17 UTC (Sat) by flussence (subscriber, #85566) [Link]

Python's solution to the waterbed theory of language design seems to be to nail down the lumps.

The (non-)return of the Python print statement

Posted Jul 14, 2020 4:48 UTC (Tue) by kenshoen (subscriber, #121595) [Link]

Is it another deviation from PEP3099?
  • The parser won't be more complex than LL(1).


Copyright © 2020, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds