Most active commenters
  • (4)
  • sametmax(4)
  • est(3)
  • detaro(3)
  • icebraining(3)
  • arketyp(3)
  • stfwn(3)
  • bluecalm(3)

←back to thread

317 points est | 53 comments | | HN request time: 0.812s | source | bottom
1. oooooof ◴[] No.17448560[source]
What is it? The link points to a discussion more deep than I’m willing to read.
replies(10): >>17448567 #>>17448570 #>>17448571 #>>17448572 #>>17448575 #>>17448579 #>>17448584 #>>17448591 #>>17448617 #>>17448638 #
2. ◴[] No.17448567[source]
3. sslnx ◴[] No.17448570[source]
Here is the PEP-572

https://www.python.org/dev/peps/pep-0572/

4. ◴[] No.17448571[source]
5. est ◴[] No.17448572[source]
It's a controversial PEP https://www.python.org/dev/peps/pep-0572/ which allows you to write Python like this:

    def foo():
        if n := randint(0, 3):
            return n ** 2
        return 1337


    [(x, y, x/y) for x in input_data if (y := f(x)) > 0]
replies(7): >>17448580 #>>17448633 #>>17448694 #>>17448731 #>>17448946 #>>17449000 #>>17449023 #
6. ◴[] No.17448575[source]
7. ◴[] No.17448579[source]
8. ForHackernews ◴[] No.17448580[source]
Ick.
replies(1): >>17448601 #
9. hultner ◴[] No.17448584[source]
You're allowed to do assignments inside of expressions

E.g.

    if(x:=f() is not None):
        print(x)
You can read more about it here: https://www.python.org/dev/peps/pep-0572/
replies(1): >>17448672 #
10. kibibu ◴[] No.17448591[source]
High-level overview: it's an assignment operator that returns its value, similar to C's assignment operator.

The choice of := is to avoid accidentally using assignment where comparison is expected.

replies(2): >>17448770 #>>17453569 #
11. kibibu ◴[] No.17448601{3}[source]
I've come around to it purely based on the application in list comprehensions.
12. OskarS ◴[] No.17448617[source]
Basically it's about adding := as an "assignment expression operator", that does assignment and returns the value as an expression. That is, take this regex example:

    match1 = re1.match(text)

    if match1 is not None:
        do_stuff()
    else:
        match2 = re2.match(text)

        if match2 is not None:
            do_other_stuff()
Which is a bit clunky. you only want to evaluate match2 in case match1 fails, but that means a new level of nesting. Instead, with this proposal, you could do this:

    if (match1 := re1.match(text)) is not None:
        do_stuff();
    elif (match2 := re2.match(text)) is not None:
        do_other_stuff()
Evaluate and assign in the if-statement itself. This is not dissimilar to the equals operator in C. In C, you would frequently find loops like `while ((c = read()) != EOF) { ... }`. This would presumably allow a similar pattern in python as well.

More information can be found in PEP-572: https://www.python.org/dev/peps/pep-0572/

replies(1): >>17448750 #
13. aviraldg ◴[] No.17448633[source]
It also seems include a special case for if/while that lets you do:

    def foo():
        if randint(0, 3) as n:
            return n ** 2
        return 1337
which looks a bit better to me.
replies(2): >>17448751 #>>17449041 #
14. systoll ◴[] No.17448638[source]
The proposal: https://www.python.org/dev/peps/pep-0572/

Short version.

(x =: y) is an expression that:

1. assigns the value y to the variable x

2. has the value y.

So `print((x := 1) + 1)` prints '2', and sets x=1.

A ton of languages [eg: c, js] have '=' work this way. And a ton of style guides for those languages tell you to avoid using it like that, because it's confusing. So this is a bit controversial.

15. majewsky ◴[] No.17448672[source]
I'm immediately skeptical after seeing this example because I'm not sure if the first line parses as:

  if (x := f()) is not None:
or as:

  if x := (f() is not None):
replies(2): >>17448788 #>>17449070 #
16. chombier ◴[] No.17448694[source]
Can somebody comment on why is this PEP controversial?
replies(3): >>17448727 #>>17448733 #>>17449044 #
17. detaro ◴[] No.17448727{3}[source]
It makes list expressions and some other things more powerful, but some feel the potential to create difficult-to-understand constructs with it is too high and the current ways of writing such code are clear enough.
18. sluukkonen ◴[] No.17448731[source]
Would've making regular assignment an expression broken too much existing code?
replies(2): >>17448916 #>>17449059 #
19. est ◴[] No.17448733{3}[source]
For many people (including me) who learned Python the way that, in languages like C, the `if x=2` assignment combined with condition is an anti-pattern and prone to errors.

This PEP solves very little problem, saves a few characters of code, but adds complexity to readability.

20. oblio ◴[] No.17448750[source]
Hehe. More chances for C-style bugs like:

if (a = b) /* Oooops, meant a == b! */

replies(4): >>17448889 #>>17448893 #>>17448905 #>>17453446 #
21. icebraining ◴[] No.17448751{3}[source]
I think that's a rejected alternative proposal, not part of this PEP.
22. arketyp ◴[] No.17448770[source]
I feel the colon is unnecessary, especially considering how C deals with this. A plain '=' inside a conditional is already invalid syntax in Python.
replies(2): >>17448792 #>>17448811 #
23. icebraining ◴[] No.17448788{3}[source]
:= overrules everything except a comma, so it's the latter. Still, I agree it's potentially confusing.
24. heavenlyblue ◴[] No.17448792{3}[source]
No, it's necessary.
replies(1): >>17448898 #
25. detaro ◴[] No.17448811{3}[source]
And it's a very well-known source of bugs in C, since it's to close to "==". I don't think new languages adopting that is a good idea.
replies(1): >>17448913 #
26. toxik ◴[] No.17448889{3}[source]
Difference is bigger, C is `if (a = b)` vs `if (a == b)`. Python is `if (a := b)` vs `if a == b`
27. afraca ◴[] No.17448893{3}[source]
Except it's more likely you're accidentally inserting a character twice than inserting another extra character (':')
28. arketyp ◴[] No.17448898{4}[source]
How so? Syntactically, or from a pragmatic point of view?
29. MaxBarraclough ◴[] No.17448905{3}[source]
Presumably that's why they've gone with the far more sensible ":=" syntax.

The use of "=" for assignment has long been a pet peeve of mine. It was a mistake when C did it, and it's been a mistake for so many subsequent languages to copy it.

"=" shouldn't be an operator at all, it makes a lot more sense to use ":=" and "==".

Pascal's use of ":=" for assignment and "=" for equality, strikes me as almost as clear.

Still, at least C makes consistent use of '=' for assignment, unlike that god-forsaken trainwreck of a language, VB.Net, which uses it for both assignment and for equality depending on context.

30. arketyp ◴[] No.17448913{4}[source]
Sure. But if fidelity to C style was not a concern then I don't see why the '==' syntax was adopted in the first place.
replies(1): >>17450177 #
31. icebraining ◴[] No.17448916{3}[source]
Probably not, since expressions can already be statements. But that would allow dangerous code like "if a = 3", which I don't think the Python devs would want to allow.
32. stfwn ◴[] No.17448946[source]
This immediately looks useful for things like:

    if foo := bar[baz]:
        bar[baz] += 1
        return foo
    else:
        bar[baz] = 1
        return 0
Where foo is a dict keeping track of multiple things, and a non-existing key (baz) is never an error but rather the start of a new count. Faster and more readable than

    if baz in list(bar.keys()):
    ....
Similar to Swift’s ‘if let’, it seems.
replies(4): >>17448968 #>>17449008 #>>17449051 #>>17449074 #
33. kelnos ◴[] No.17448968{3}[source]
For stuff like that I'd just use `defaultdict`. That if/else tree then reduces to 2 lines total.
replies(1): >>17448988 #
34. stfwn ◴[] No.17448988{4}[source]
That’s a good tip, thanks!
35. i_do_not_agree ◴[] No.17449000[source]
This is horrible. It looks like ":=" is a comparison operator. The last line is dangerously close to Erlang list comprehensions:

[ {X, Y, X/Y} || X <- Some_Function (), Y <- Some_Other_Function () ]

And people bitch about Erlang syntax.

Edit: "/" is the division operator

36. antoinealb ◴[] No.17449008{3}[source]
As pointed, you can use either a default dict or just simply, and [more pythonic](https://blogs.msdn.microsoft.com/pythonengineering/2016/06/2...):

    try:
      bar[baz] += 1
    except KeyError:
      bar[baz] = 1
Also you can check if a key is in a dict simply by doing "if baz in bar" no need for "list(bar.keys())", which will be slow (temp object + linear scan) vs O(1) hashmap lookup.
replies(3): >>17449075 #>>17449181 #>>17449409 #
37. mFixman ◴[] No.17449023[source]
Reminds me of the kind of hacks you would find in an old-school K&R book.
38. s3m4j ◴[] No.17449041{3}[source]
https://www.python.org/dev/peps/pep-0572/#alternative-spelli...
39. ATsch ◴[] No.17449044{3}[source]
I don't think the controversy here is with the feature itself, more with the implementation. Many, me included, would have preferred to seen a different implementation of solutions to the same problems.

Code starts becoming a lot harder to reason about when more than one state is mutated on the same line. The good design of Python makes this harder than in say C and I think this is a step in the wrong direction in that regard.

The two real things this solves are checking for truthyness in an if and reusing values in a filterting comprehension. Instead of the syntax we have now that can be used anywhere, adds a whole new concept and feels kind of out-of-place, I would have much preferred a solution that can only be used in vetted places, doesn't add a new thing people need to learn and follows the style of the language

For example, my preferred solution for `if` would have been:

    if thing() as t:
        print(t)
Usage of `as` is already established by the `with` block

    [value for x in y
     if value
     where value = x * 2]
The order is unfortunately a bit weird here, but there is no need to add the whole concept of a different type of assignment and this syntax will feel instantly recognizable to people familiar mathematical notation, which is where the existing list comprehension syntax comes from and so has been established as well.
replies(1): >>17449064 #
40. sametmax ◴[] No.17449051{3}[source]
Don't wait for 3.8, and don't bother with defaultdict.

collections.Counter is what you want for the counting case.

dict.get() + dict.setdefault() for the general case.

defaultdict is only useful if the factory is expensive to call.

41. sametmax ◴[] No.17449059{3}[source]
It's a voluntary design choice since the beginning of Python to avoid the very common mistake of doing:

    while continue = "yes":
instead of:

    while continue == "yes":
Those mistakes introduce bugs that are hard to spot because they don't cause an immediate error, linters can hardly help with them and even a senior can make them while being tired.
replies(1): >>17453480 #
42. sametmax ◴[] No.17449064{4}[source]
I wanted "as" too. But the accepted operator has the benefit of integrating perfectly with type hints.
43. sametmax ◴[] No.17449070{3}[source]
That's why parenthesis are mandatory.
44. eesmith ◴[] No.17449074{3}[source]
The place I see using it is in (quoting Python's "python.exe-gdb.py"):

        m = re.match(r'\s*(\d+)\s*', args)
        if m:
            start = int(m.group(0))
            end = start + 10

        m = re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args)
        if m:
            start, end = map(int, m.groups())
With the new syntax this becomes:

        if m := re.match(r'\s*(\d+)\s*', args):
            start = int(m.group(0))
            end = start + 10

        if m := re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args)
            start, end = map(int, m.groups())
This pattern occurs just often enough to be a nuisance. For another example drawn from the standard library, here's modified code from "platform.py"

    # Parse the first line
    if (m := _lsb_release_version.match(firstline)) is not None:
        # LSB format: "distro release x.x (codename)"
        return tuple(m.groups())

    # Pre-LSB format: "distro x.x (codename)"
    if (m := _release_version.match(firstline)) is not None:
        return tuple(m.groups())

    # Unknown format... take the first two words
    if l := firstline.strip().split():
        version = l[0]
        if len(l) > 1:
            id = l[1]
replies(1): >>17454726 #
45. stfwn ◴[] No.17449075{4}[source]
The error-catching method seemed too drastic to me before, but the article explains the LBYL vs. EAFP arugument quite well. Thanks!

I should find a way to get more code reviews, I really enjoy learning these small nuggets of info.

46. bb88 ◴[] No.17449181{4}[source]
It's also time saving since the hash lookup needs to be done at most 1, as well. GP has two lookups in the hash list.
47. bocklund ◴[] No.17449409{4}[source]
Alternatively

`bar[baz] = bar.get(baz, 0) + 1`

One line and no error checking.

But the OP was probably just illustrating a basic example where you might have some more intense logic

48. detaro ◴[] No.17450177{5}[source]
== is an incredibly common syntax for equality and stand-alone not a problem. only if you introduce = to expressions too it becomes a risk. (well, you could theoretically accidentally write == for a normal assignment, but that kind of error is caught more easily)
49. bluecalm ◴[] No.17453446{3}[source]
It's not a problem in C anymore as modern compilers warn about that so you had to put additional parenthesis to make it clearer.

I like C way of assignment being an expression. I think having separate statement and then assignment expresdion is a mess. It's still useful though as Python was missing where keyword like feature from Haskell which is necessary to avoid duplicating computation in list comprehension.

50. bluecalm ◴[] No.17453480{4}[source]
I don't know about linters but GCC warns me about that every time I make that typo. They could just require parenthesis when assignment value is used as boolean.
51. bluecalm ◴[] No.17453569[source]
Yeah but there is already solution for that in C: put parenthesis around assignment when using its value as bool. The compilers warn if you don't so making this error in C can only happen if you don't use warnings.
52. est ◴[] No.17454726{4}[source]
It' a problem with re module really.

re.match should return a match object no matter what, and .group() should return strings, empty string if non were matched.

replies(1): >>17455316 #
53. eesmith ◴[] No.17455316{5}[source]
I don't see how that would improve things. Could you sketch a solution based around your ideas?