Bringing the print statement back
lwn.net
lwn.net
What I do not like is TMTOWTDI. And I do not like Python violating its own principles (namely, "explicit is better than implicit"). As far as I am concerned, in Python, which does not have partial application, function application is handled by a special `(...)` operator that explicitly applies whatever function comes before the parens to whatever is inside the parens. Making the parens optional gives you two different ways to apply a function - and, by extension, one more place for people to argue about style - one of which is just an implicit version of the other.
You've got to have a way better reason to do that to a language than just, "Hey, isn't this cool? And also, I miss the Python 2 vs Python 3 wars, so wouldn't it be fun to give that pot another stir for old time's sake?"
I get it, the change from a print statement to a print function caused some pain. But it did bring some practical benefits, and the transition is in the past now, and, to quote the Zen of Python again, "special cases aren't special enough to break the rules."
I'm also going to throw another reference to PEP-20 in the mix here: "There should be one-- and preferably only one --obvious way to do it." This implies either forcing parens everywhere or not using them at all. Paired with "explicit is better than implicit", I'd say that points pretty clearly at using parens everywhere.
print(1, 2, 3)
supposed to print 1 2 3
or (1, 2, 3)
?What about
print (1, 2, 3)
?Are they the same? Should they be different? I know what the answer is in Python 3.8. A few weeks or months of programming in a Python where parens are optional, though, and I wouldn't be so sure anymore.
To solve the conflict the first parameter cannot start with a paren.
That was my first thought. Why would we turn a simple, binary choice -- "< 3: statement, >= 3: function" into a big old... maybe?
The binary choice hurts, because it dictates changes. Sure. People dislike change, but it's easy to support.
However, there's one thing worse than change, and that's inconsistency and uncertainty. like, in this case, going "zoinks, your change wasn't actually necessary!". Except, now we have both choices. So it'd be even more confusing.
There are several languages that people like for this sort of flexibility, but I think Python's relative rigidity has always been one of its strengths.
edit: And just to be clear, I'm fine with making print a function for all new code. There were ways that the python community could have accomplished that [and the unicode switch] without breaking the entire language.
The problem with that is that the entire python ecosystem had to deal with that instead...
I'm going way past rant territory at this point, but there's a reason that Microsoft and Amazon are worth a trillion dollars a piece, and it's not because of beautiful and elegant APIs.
The following still compiles and runs, on the compiler that comes with the latest OpenBSD:
main()
{
printf("hello, world\n");
}
Somehow the C language has managed to survive and thrive without breaking the canonical example program.Imagine we'd drop HTTPS for a new protocol because it fixes the 'Referrer' spelling error.
Python 3 is the version with breaking changes from 2.x and while stuff is being broken, they decided to fix the print statement.
Python 3 wasn't invented to fix the print statement.
f(a)
f((a))
(f)(a)
(f)((a))
etc. and those all mean the same thing, and no-one finds this confusing.There is no reason to not require parenthesis. No reason.
This is what I hate about Ruby, not having parenthesis gets a bit confusing and adds a couple of gotchas
Print statement is no more. It is gone. It went to meet its maker. Forget about it
Doing away with parenthesis is not syntactic sugar, it's the watermelon seeds that get in the way
----
> Why is this being proposed? > > I think we would need a very strong reason to consider this, > and so far I haven't seen any justification other than "because > we can". >
(Guidos response below):
There was definitely something of that... I was looking at the new PEG parser and realized that if people wanted it this would be easy to do. So I spent a pleasant hour or two coding it up to my satisfaction.
But I was also trying to satisfy some demand. When Python 3 was young, print becoming a function was one of the most frequent complaints, and it's still occasionally seen on Twitter. I found at least two StackOverflow issues about it, but the combined upvote count was less than 100.
An early post in this thread reminded me that IPython has a feature called "autocall" that allows exactly this syntax. I don't know how popular it is. However, apparently there the form `f x+1` ends up calling `f("x+1")` (i.e. stringifying the argument), so introducing similar syntax in Python with different semantics would hardly be helpful. (If someone wants to start a debate on argument quoting, please start a new thread, so we can lay this one to rest.)
All in all, it's clear that there's no future for this idea, and I will happily withdraw it.
----
A = len
But you can in python.
It seems python is adding more and more implicit stuff in every new release.
I think the day we can import braces from __future__ might not be far away.
Behold, bython: https://github.com/mathialo/bython
Benefits: You can use spaces instead of parenthesis. It's the same number of characters and function arguments won't work with spaces.
Costs: Python has to support this forever. Increased complexity. More difficult to read code, because you could be doing the same thing in multiple ways. More style guidelines to enforce. Confusion among new programmers about why they can't use function arguments without parenthesis. Confusion among new programmers about why they would use spaces instead of parenthesis (or the reverse).
Seriously, I do not see any reason why they would ever want to add this in light of the seemingly obvious costs.
It's an Easter egg.
Python has had multiple ways of doing many things for a very long time, and the longer you program in any language, the more you realize there are often many solutions to a problem and the "best" approach either depends on context or you realize there is no one "best" approach and go on preference.
I think the idea should be judged on its own merits without having to consult the Zen. That said, I seriously don't see the point of it. Python 3 already forced everybody to convert all their print statements to look like function calls, and now GVR wants to bring the old style back as an option? It's just parenthesis, what's the point?
If he wants to put that new parser to work, how about taking another look at multi-line lambdas?
But "solution" and "ways of doing things" from a language perspective are different. You can one way of doing something in a language, yet provide different implementations of the same solution. The difference is how different a "way" is at some level of abstract (vs "syntactic sugar).
Inventing new sentences is needed everyday, inventing new words much rarely so. "programmable program languages" or those highly dependant on custom frameworks fall prey to this: where code becomes a personal space that is hard to break into. You have to "terminate-thought" at some level, if else everyone "creatively" reinvents the low level (e.g. built-ins) then no collaboration is practical; accept the standard building block, and implement your solution with those.
> there is no one "best" approach
but there are "better" approaches
This principle is often repeated for Python, but does it have any bearing on reality today? With every release of Python (and indeed for many other languages), new features give you even more ways of doing things.
Python is 30 years old - it's no longer the small language it may once have been, but is now chock-full of features. Maybe once there was an "obvious way to do it", but I don't thing this is true today.
Just as representing a vector of numbers as a tuple, a list, a dictionary, a numpy matrix or a numpy array. And don't get me started on multidimensional arrays.
But `print 'hi world'` This would really spoil one of the nice consistency things python 3 brought.
Callables without parenthesis to distinguish the arguments. Can you imagine what this is going to do to readability on open source projects?
This was back in June, and probably for the sake of conversation. It got my (and a lot of others) attention, though. If that was the case, mission accomplished.
I'm all for the new parser enabled in Python 3.9 (and switched on by default in 3.10): https://www.python.org/dev/peps/pep-0617/
same with `class Foo(object):`
That's still legal, and actually I prefer it even though it's not required in Python 3, because explicit is better than implicit.
But then again, throwing and catching exceptions through arbitrary depths along the call stack is perfectly clear and neat.
That's why I hate them. A programming language is primarily meant to be read by people. If I see something like
thing noun object
What's going on there? Is thing a function? Is noun? Am I passing two arguments to thing, or am I passing the result of invoking the function noun with the argument object?Hell, if I have something like
verb action
it certainly looks like I'm calling a function called verb with the argument action, but is the argument I'm giving to verb the actual function action, or the result of evaluating action?Yes, in any given language there's probably a single answer to all of these, but I bet it varies from language to language, and I'd really, really, really rather not waste calories by having my brain try to figure it out.
Software developers are often stubborn. The main result of this change will be a mess of code which follows a mix of all the styles. That removes one of the greatest beauties of the Python ecosystem, that there is a "right way" to do things.
The Pythonic way to do it is (very) often not my favorite way to do it. But, if I'm working in Python, I'm going to do it that way, anyway. Because social factors matter, and choosing your own convention rarely yields enough benefit to justify choosing not to follow the convention.
Seems like the sort of faddish syntactic sugar that they love in Swift. Please, not in Python.
(I fucking hope)
foo (boo, hiss)
should immediately close the issue. What the heck is happening?
echo $foo
# is parsed as
echo($foo)
[..] echo(1, 2) # pass 1 and 2 to echo
echo (1, 2) # pass the tuple (1, 2) to echo
[..] proc optarg(x: int, y: int = 0): int = x + y
proc singlearg(x: int): int = 20*x
echo optarg 1, " ", singlearg 2 # prints "1 40"
let fail = optarg 1, optarg 8 # Wrong. Too many arguments for a command call
let x = optarg(1, optarg 8) # traditional procedure call with 2 arguments
let y = 1.optarg optarg 8 # same thing as above, w/o the parenthesis
assert x == y
[0]: https://nim-lang.org/docs/manual.html#syntax-precedence[1]: https://nim-lang.org/docs/manual.html#procedures-command-inv...
This makes it quite hard to "sight-read" code with reasonably-sized function names and arg lists:
do_some_thing_descriptive (some_arg_2, some_longer_arg)
[ ... other code ] do_some_very_descriptive_thing(some_arg_2, some_longer_arg)
I imagine that each file would try to remain internally-consistent with preferring one syntax over the other.IMO, in scenarios where a module must remain internally-consistent on its one-of-many-ways to do it, for the sake of legibility, there should be only one way to do it. Although, I'm open to hearing any good-faith arguments to the contrary!
Yes, Python, I meant exactly that! I've never seen this error message in error. Now you know what I mean, please fix it automatically, I know you can do that. Heck, throw a single warning if you really want to enforce this. I can ignore warnings that I don't care about.
> Python! I learned it last night! Everything is so simple! Hello world is just: print "Hello, world!"
> SyntaxError: Missing parentheses in call to 'print'. Did you mean print("Hello, world!")?
> I dunno... dynamic typing? Whitespace?
> Come join us! Programming is fun again! It's a whole new world up here!
> But how are you flying?
> I just typed: imports antigravity as ag
> TypeError: imports() takes 2 positional arguments but 3 were given
I'm not sure whether to take this as an in-joke (knowing the reference is part of the fun) or poor attribution (credits should be given where they're due).
def bob(cb, *args):
do some stuff...
Then the call: bob somefn, 2, 3
Works. But I remember having hellish times tracking down issues when you missed a single comma. bob somefn 2, 3
Now you have bob being passed the result of evaluating the callback (early) with 2, 3 - and this is discovered at runtime. Ugh!Boo, hiss.
The parentheses-less function call is the second biggest obstacle to ruby readability.
123 | add10 | str | print
UPD: Some of my workarounds (dont try this at home lol): [twitter thread] https://twitter.com/tandavaya/status/1155925017848242176
: add10 10 +
123 add10 . for file, size in ls('/home/jao') | map(lambda f: (f, f.size)):
print(f'{file.name}: {size}') range(10) | @ + 2 | @ % 2 == 0 | print(@) def thread_first(current_value, args):
for funcargs in args:
if isinstance(funcargs, list):
func, *restargs = funcargs
else:
func = funcargs
restargs = []
current_value = func(current_value, *restargs)
if current_value is None:
break
return current_value
It’s modelled after clojure’s ‘->’. Your example would be thread_first(123, [add10, str, print])Sincerely,
a programmer from a language that allows this.
(To elaborate: Perl and Raku allow this. It's not a bad idea on its own, but it comes with lots of tradeoffs that, IMHO, don't fit with the design of Python).
Is that supposed to be a function call with a tuple or function call with 3 integers?
So, boo. Hiss.
>>> class At:
__slots__= 'fun',
def __init__(self, fun):
self.fun = fun
def __matmul__(self, arg):
return self.fun(arg)
def __call__(self, *args, **kwargs):
return self.fun(*args, **kwargs)
>>> @At
def square(x):
return x*x
>>> square(2)
4
>>> square@2
4If it's the latter, does that mean that the execution environment is part of the language itself, instead of, for example, C where environmental stuff is only accesible through the standard library and the language per se is little more than a context-free grammar?
Yes, that has always be the case with Python in my understanding - there are lots of built-ins...
It's just a syntax change; "print 1 2 3" is still an expression just like "print(1, 2, 3)".
I personally like the aesthetic of it, but the non-composability makes it a UX mess in my opinion. Languages are a toolset for combining things, and when those tools just arbitrarily refuse to be combined (or combine irregularly), you end up bending over backwards to make the language get out of your way rather than getting a job done.
One thing I love about Python is that it's clear when I'm passing a function/method as argument, or aliasing a class, or just generally knowing when I'm calling something vs referencing it.
This is a no-go from the start.
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(1, 2, 3)?
Then why not just have it DWIM?
How long until this is a library on pypi?
There is something mature and responsible about a language that tends to refuse a command if the meaning isn't obvious, forcing you to make it obvious. If instead, you assign meanings to every ambiguous statement, and to the inevitable exceptions and exceptions to exceptions, you end up with JavaScript: "Oh, well, too late now"-oriented design.
Poor effort tbh but I’ve been excited about new possibilities before too.
Can anyone say which style of parser python used before and why The bee PEG parser is more powerful. It’s my understanding that PEG parser are equivalent to RDPs.
For debugging comfort the print statement is half measures anyway: Give us Julia's @show macro!
I'm amazed that Guido is not aware of Nim which does precisely this. It works brilliantly too. It would be incredible if Python becomes more like Nim (Nim itself having been inspired a fair bit by Python).
The idea of making parens optional for all function calls seems like just way too much though
Many think think that python already lost almost everything that it was loved for initially. So why not change it more drastically
Parenthesis makes very clear what context you are in.
So rather than saying that I don't like it, I think what I'd like is some (semi-)objective way of figuring out whether the advantages are worth it. I'm inclined to say it's not worth it, but I don't know.
The funny thing is that in Standard ML and F# it's less common to pass arguments solely through currying anyway. The standard libraries in both use tuples.
I have always felt as a reader that this and camel-case makes code easier to read.
Yes you did. And you want to take the good things from ruby. You want no parentheses calls, block syntax instead of lambdas, chainable map/filter/sort/reduce, accessor syntax, you want to extend basic types so you can call methods on literals. You want ruby all over you. Just admit it.
It makes python less consistent as you can not use this in a chain and a view other positions.