32 ms·
PEP 572: Python assignment expressions has been accepted
- oooooof 8y agoWhat is it? The link points to a discussion more deep than I’m willing to read.
- deleted 8y ago[deleted]
- sslnx 8y agoHere is the PEP-572 https://www.python.org/dev/peps/pep-0572/ https://www.python.org/dev/peps/pep-0572/
- deleted 8y ago[deleted]
- est 8y agoIt's a controversial PEP https://www.python.org/dev/peps/pep-0572/ 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]
- ForHackernews 8y agoIck.
- kibibu 8y agoI've come around to it purely based on the application in list comprehensions.
- aviraldg 8y agoIt 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.
- icebraining 8y agoI think that's a rejected alternative proposal, not part of this PEP.
- s3m4j 8y agohttps://www.python.org/dev/peps/pep-0572/#alternative-spellings https://www.python.org/dev/peps/pep-0572/#alternative-spelli...
- chombier 8y agoCan somebody comment on why is this PEP controversial?
- detaro 8y agoIt 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.
- est 8y agoFor 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.
- ATsch 8y agoI 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.
- 8y ago
- sluukkonen 8y agoWould've making regular assignment an expression broken too much existing code?
- icebraining 8y agoProbably 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.
- sametmax 8y agoIt'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.
- bluecalm 8y agoI 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.
- stfwn 8y agoThis 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.
- kelnos 8y agoFor stuff like that I'd just use `defaultdict`. That if/else tree then reduces to 2 lines total.
- stfwn 8y agoThat’s a good tip, thanks!
- antoinealb 8y agoAs pointed, you can use either a default dict or just simply, and [more pythonic](https://blogs.msdn.microsoft.com/pythonengineering/2016/06/29/idiomatic-python-eafp-versus-lbyl/ 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.
- stfwn 8y agoThe 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.
- bb88 8y agoIt'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.
- 8y ago
- i_do_not_agree 8y agoThis 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
- mFixman 8y agoReminds me of the kind of hacks you would find in an old-school K&R book.
- deleted 8y ago[deleted]
- deleted 8y ago[deleted]
- hultner 8y agoYou'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/ https://www.python.org/dev/peps/pep-0572/
- majewsky 8y agoI'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):
- icebraining 8y ago:= overrules everything except a comma, so it's the latter. Still, I agree it's potentially confusing.
- sametmax 8y agoThat's why parenthesis are mandatory.
- kibibu 8y agoHigh-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.
- arketyp 8y agoI feel the colon is unnecessary, especially considering how C deals with this. A plain '=' inside a conditional is already invalid syntax in Python.
- heavenlyblue 8y agoNo, it's necessary.
- arketyp 8y agoHow so? Syntactically, or from a pragmatic point of view?
- detaro 8y agoAnd 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.
- arketyp 8y agoSure. But if fidelity to C style was not a concern then I don't see why the '==' syntax was adopted in the first place.
- detaro 8y ago== 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)
- OskarS 8y agoBasically 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/ https://www.python.org/dev/peps/pep-0572/
- oblio 8y agoHehe. More chances for C-style bugs like: if (a = b) /* Oooops, meant a == b! */
- toxik 8y agoDifference is bigger, C is `if (a = b)` vs `if (a == b)`. Python is `if (a := b)` vs `if a == b`
- afraca 8y agoExcept it's more likely you're accidentally inserting a character twice than inserting another extra character (':')
- MaxBarraclough 8y agoPresumably 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.
- systoll 8y agoThe proposal: https://www.python.org/dev/peps/pep-0572/ 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.
- _Codemonkeyism 8y agoThe PEP is here: https://www.python.org/dev/peps/pep-0572/ https://www.python.org/dev/peps/pep-0572/ "This is a proposal for creating a way to assign to variables within an expression using the notation NAME := expr."
- Flimm 8y agoPrevious discussion on Hacker News: https://news.ycombinator.com/item?id=16949248 https://news.ycombinator.com/item?id=16949248
- hiisukun 8y agoAn in case it sees more recent updates, the github copy (where Guido proposes people can submit changes) is here: https://github.com/python/peps/blob/master/pep-0572.rst https://github.com/python/peps/blob/master/pep-0572.rst
- mci 8y ago> x = y = z = 0 # Equivalent: (x := (y := (z := 0))) This comment is false. It should say x = y = z = 0 # Equivalent: (z := (y := (x := 0))) "...assigns the single resulting object to each of the target lists, from left to right." https://docs.python.org/3/reference/simple_stmts.html#assignment-statements https://docs.python.org/3/reference/simple_stmts.html#assign... Here is a demonstration of the difference: >>> class Node: pass ... >>> node = blue_node = Node() >>> red_node = Node() >>> node = node.next = red_node >>> blue_node.next is red_node Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: Node instance has no attribute 'next'
- jessaustin 8y agoI don't see how that demonstrates anything about the order of the assignment. It seems to show that node and blue_node are no longer the same object? (I'm not sure I understand how that came to be either, but perhaps it's due to the PEP...) I'm not saying your general point is wrong, just that the REPL listing doesn't demonstrate your general point.
- gbfowler 8y agoIt is not syntactic sugar, "x := 10" is an assignment expression in contrast with "x = 10", which is a statement. Hence the former can be used in contexts like "if x := 10: pass", which is the whole point of the PEP.
- Aardwolf 8y agoWhy is it not syntactic sugar? It looks like convenience. You could argue everything above machine language is syntactic sugar.
- akvadrako 8y agoNot so. Many things change the semantics, otherwise high level languages would just be glorified macros. For example, import mod is NOT defined as mod = eval(open("mod.py").read()) but involves abstract load module operation, which is dependant on the environment. That's why := is just syntactic sugar; there are no new semantics.
- junke 8y agoHonestly curious, how do you expand a "x := y" expression into an expression that returns y and affects x as a side-effect?
- akvadrako 8y agoI'm not sure exactly, but my point is that a program transformation can do it. However, no transformation can change an import to other operations while perserving semantics. Just for fun, this seems to work: (locals().pop('x', None), locals().setdefault('x', y))[1]
- yorwba 8y agoThat only works at top-level scope, where locals() == globals(). Inside a function, changing the dict returned by locals() doesn't actually change the variable.
- deleted 8y ago[deleted]
- roel_v 8y agoHaving just spend the last few weeks writing Python, this comment will come off as bitter, but - really? Out of all the shitty syntax things, this sort of thing is what they're willing to fix?
- isoprophlex 8y agoCan you elaborate what other syntax decisions you find shitty? In general I find the syntax of python to be very clean and easy to understand
- vanderZwan 8y agoIt would be more constructive if you added explanations of what other (in your opinion) "shitty syntax" things you prefer to see addressed and why. EDIT: also, I'm mentioning "in your opinion" because adding that to your own statements indicates that you're open to discussion. It's also good to use it as a reminder to yourself (speaking as a former physics student, and most people who know physics students will agree how "absolute" and unintentionally arrogant they tend to be in their claims until they learn otherwise). I'm sure your coding experience was frustrating, and I'm sorry to hear that, and I understand that we all need to vent sometimes, but trying to staying open to other viewpoints is better for your own sanity, wisdom, and social connections in the long run.
- mehrdadn 8y agoNot the OP, but the inability to write sorted(enumerate([('a', 1), ('b', 2)]), key=lambda (i, (k, v)): (k, i, v)) in Python 3 drives me nuts. They could fix this. It worked in Python 2.
- fredley 8y agoI love writing python, but the lack of decent map/reduce/etc. (that are really good in Javascript - the other language I mainly write) hurts. Stuff like your example really feels burdensome and inelegant in comparison.
- mlvljr 8y agoomg
- sametmax 8y agoI will be happy to be able to do: while (bytes := io.get(x)): and: [bar(x) for z in stuff if (x := foo(z))] Every time Python adds an expression counterpart to an existing statement (lambdas, intensions, ternary...) there is a (legit) fear it will be abused. But experience tells that the slow and gradual pace of the language evolution combined with the readability culture of the community don't lead that way. While we will see code review breaking materials in the wild, I believe that the syntax will mostly be used sparingly, as other features, when the specific needs arise for it. After all, it's been, as usual, designed with this in mind: "=" and ":=" are mutually exclusive. You don't use them in the same context. The grammar makes sure of it most of the time, and for the rare ambiguities like: a = b vs (a := b) The parenthesis will discourage pointless usage. My bet is that we will see essentially rare but useful and expressive use cases in productions, which is exactly the goal. Given the month of debates around this, I think it's a fine compromise. Like many, I would have preferred the use of the "as" keyword instead of a new operator, since it's already used to bind things to names in imports, context managers and try/except. However, the new syntax has 2 advantages: it reads the same way than the original operator, and it supports type hints out of the box.
- deleted 8y ago[deleted]
- smcl 8y ago> Every time Python adds an expression counterpart to an existing statement (lambdas, intensions, ternary...) What do you mean by "intension"? I haven't seen this referred to in python and googling isn't turning out anything useful (also spelling it as "intention").
- sametmax 8y agoSorry, french word for "comprehension". It pops up automatically. I can't edit my post anymore though. The worst for me is OOP. In french it's POO, which, for obvious reasons, does not inspire confidence in an english reader.
- 8y ago
- logicallee 8y agoThis is unpythonic, breaking about half of these design rules: https://en.m.wikipedia.org/wiki/Zen_of_Python https://en.m.wikipedia.org/wiki/Zen_of_Python For anyone who hasn't read the original PEP link, what do you suppose this does? Guess its meaning: 1. if (row := cursor.fetchone()) is None: raise NotFound return row 2. Next guess the meaning of this - what does it look like it does? row = cursor.fetchone() if row is None: raise NotFound return row Answers: The first does the second. The second doesn't need explanation. I literally don't need to tell you what it does. (The two examples are from the link.) Now you might think that this means I'm against this PEP. But actually [EDIT: because I know there is a VERY high bar to being accepted into Python] to me it means this is going to save an incredible amount of time - it must be very good indeed to be accepted. So if they decided to adopt it - sure, I'll use it. And I bet it's great in practice. It's also super explicit. If you don't know what := does you will go look it up. If Python is still driven by Python design philosophy and all that discussion, then this will be very helpful. It surely had a very high threshold to meet.
- marcus_holmes 8y agoI'm confused. Are you saying it's unpythonic but you like it anyway?
- logicallee 8y agoI've added an edit to my comment to help you through the logical jump, and in the rest of this comment I write it out explicitly. So, my reasoning is that I surely must end up liking it, if it ended up accepted despite breaking every rule in the book. They wouldn't accept it for something that wasn't incredibly useful. My first impression is that it's incredibly ugly, unpythonic, and unreadable (what does it even do?), and this is pretty "obvious". So arguing based on what I know about Python design philosophy, and the fact that this has been so deeply discussed, it must be absolutely fantastic. I wouldn't think it's great (if you gave me the syntax), I'd recite all the Python Values it breaks - https://en.m.wikipedia.org/wiki/Zen_of_Python https://en.m.wikipedia.org/wiki/Zen_of_Python : - Beautiful is better than ugly. Nope, this is very ugly. - Simple is better than complex. Nope, this isn't simple. - Flat is better than nested. Nope, this explicitly exists to let you nest something. - Sparse is better than dense. Nope, this explicitly exists to make code denser. - Readability counts. Nope: this is literally not readable. You don't know what the first code example I gave does. Normally with Python you just "write pseudocode and make sure you indent it properly." - Special cases aren't special enough to break the rules. This is a special case. but here we get to the next rule, which trumps ALL of the above: Although practicality beats purity. - There should be one—and preferably only one—obvious way to do it. This is broken, as now you can do it on two lines or one line. - If the implementation is hard to explain, it's a bad idea. It's kind of hard to explain, look at all our comments. - If the implementation is easy to explain, it may be a good idea. This isn't the case here. I will grant that this is extremely explicit syntax. In this sense it is MUCH better than overloading = based on different contexts or something. As I mentioned above, anyone who sees := knows that it's a "known unknown" -- WTF is this? So we are left with a single solitary Pythonic value: - Practicality beats purity. Based on this alone, I reason that it must be great to have been accepted. It must be extremely practical. It must save a lot of time. I am quite disposed to thinking (honestly!) that it must be pretty darn great. It will save a lot of time. There's no way that it made it into the language if it didn't, since it's so obviously ugly and unpythonic. But practicality beats purity. I'm sure I'll love it!
- blindseer 8y agoMy biggest problem with this change (and dataclasses) is that it is not backward compatible (I know that a backport of dataclass is available, but it is only for Python 3.6). Can people tell me how they deal with this? Are you using features that are only available in the version of Python all your colleagues / users have access to? Are you using the latest version of Python and hoping your colleagues and users can upgrade to that version of Python? One main reason in my opinion as to why Python 2.7 stuck around for so long was that everyone agreed that it was the last stable version of Python 2, and the devs could make Python 3 changes without worrying significantly about backward compatibility. I've been using Python3 since Python 3.3 but have had to write code that was Python 2 compatible for large code bases for about 5 years because I knew colleagues / users of my packages would not upgrade. This meant using a select subset of features in Python 3 that had been backported to a version of Python 2.7 that I knew I could get my colleagues / users to upgrade to. It has been great to watch the language evolve and Python 3 really gets a lot of things right, but adding breaking syntax features every minor release is extremely annoying. I have to have a mental checklist of all the different possible subsets of features I have access to given a minimum version of Python I want to support. I've spent the last couple of years in my professional career converting large Python 2 only code bases to Python 3.3+. But if someone wants to use async, I need to bump the minor version. If someone wants to use type hinting I have to bump the minor version. If someone wants to use f-strings I have to bump the minor version. If someone wants to use data classes I have to bump the minor version. It's nuts (to me anyway)! This sounds rant-y but I genuinely want to know what other people are doing to mitigate this. I love Python! I have spent years advocating its merits. But thinking about large codebases in Python just worries me and the future doesn't look promising here. Are Python developers hoping that once 2020 arrives they'll have a stable Python release and work on Python 4. We'll have the Python 2/3 split all over again! My personal opinion is that Python needs an officially maintained translator for the latest Python features back to older versions. My current favorite package is py-backwards [0] but it is rather unmaintained at the moment. [0] - https://github.com/nvbn/py-backwards https://github.com/nvbn/py-backwards
- marcus_holmes 8y agoA bit off-topic, but I'm really curious. Why do people not upgrade Python and stick with 2.7?
- amelius 8y agoIf they add anything to Python, it should be the ability to do functional-style programming without hassle. Right now it's almost impossible to compose functions in an inline style such as used in functional programming languages. Yes, there's lambda, but it doesn't extend into multiple lines, and applying a function to a bunch of lambdas directly leads to line-width overflow. Even JavaScript has better support for functional-style programming. Perhaps Guido should spend a year writing Haskell :)
- icebraining 8y agoPython has explicitly discouraged functional-style for a long time, I don't think that's going to change. And frankly, the whole language is quite, I'd even say extremely, mutable; I'm not sure a functional syntax works well with that.
- aib 8y agoThe problem is, python list comprehensions are very... specific. Add to that the brokenness of if...else and you get a very quirky and specific sublanguage that is hard for outsiders to read. And this is before we take into account the locals/scope/clojure discussion in the link and the PEP (which I frankly did not bother to read.) On the other hand, you have a concept which translates easily to any language with first-class functions and lambdas. Even the syntax stays the same among languages which use "f(x,y)" for function evaluation and parameter passing. /* This post is for those occasions when a list comprehension style is advocated over a functional style, which I know was not necessarily what you were doing in your comment. But I think the two points are valid enough on their own. */
- kstrauser 8y ago> Add to that the brokenness of if...else Could you elaborate on that?
- aib 8y ago> > Add to that the brokenness of if...else > Could you elaborate on that? It's a very opinionated statement on my part. `if COND then TRUE-CASE else FALSE-CASE` is the correct form to use, in my opinion. Python uses `TRUE-CASE if COND else FALSE-CASE`.
- ckastner 8y agoLink to the email on the Python-Dev mailing list: https://mail.python.org/pipermail/python-dev/2018-July/154231.html https://mail.python.org/pipermail/python-dev/2018-July/15423...
- BerislavLopac 8y agoI would like to see adding a comprehension-like filtering clause to for-statements: for n in range(100) if n%2: print(f'{n} is odd number') Does anyone know if there is a PEP covering that?
- netheril96 8y agoDoes this save anything? The canonical way to do this is for n in range(100): if n%2: print(f'{n} is odd number') Only two more indents. What is the point of your proposed syntax?
- theSage 8y agoAgreed these are a little verbose but they get the job done no? for n in filter(is_even, range(100)): print(f'{n} is odd number') for n in (i for i in range(100) if i % 2 == 0): print(f'{n} is odd number') Are there any points against these solutions other than verbosity?
- BerislavLopac 8y agoYes, that's what I've been using so far, especially filter, which works quite well with lambda. But if you have a separate function anyway it's better to make it into a generator: def odd_range(count): return (x for x in range(count) if x%2) for n in odd_range(100): ... As for the second one, I'm just not too happy with the implied two loops (even if it amounts to only one in practice).
- eesmith 8y agoHistorically there has been much resistance to proposals like this which only save a line. The existing code is, after all: for n in range(100): if n%2: print(f'{n} is odd number') You proposal also leads to a more ambiguous grammar because the following is currently allowed: for n in range(100) if n%2 else range(n): The ambiguity can be extended with multiple if's, compare: for x in range(10) if n%2 if n else range(n): for x in range(10) if n%2 if n else range(n) else n**2: A work-around would be to raise something akin to the "SyntaxError: Generator expression must be parenthesized if not sole argument" that occurs with expressions like "f(b, a for a in range(3))", but that's a lot of work just to save a newline, two indents, and ":", isn't it?
- korijn 8y agoIt feels like we are repeating a mistake the PHP community made years ago. PHP supports assignment in expressions, and the community seems to avoid its use. There are plenty of examples of stackoverflow questions and answers where the problem is caused by coders overlooking an assignment in an expression. You just don't expect assignment to occur in an expression, and its visually similar enough to gloss over it.
- Ar-Curunir 8y agoOn the other hand you can assign within expressions in Rust, and it's generally not been an issue that I've seen anyone complain about. However, that might be because the type checker usually catches you if you're making a mistake; assignment/variable binding return the type `()`, which doesn't work inside ifs or whiles
- sametmax 8y agoIn PHP, the "$a = 1" and "$a == 1" look a lot like each others. In rust, you would use "let a = " which removes the ambiguity.
- marcosdumay 8y agoThat's equivalent to the Python's (=) operator, that return None. What Python got now is a different (:=) operator, that behaves like the assignment in C.
- johannes1234321 8y agoBefore there were iterators in PHP `while ($row = fetch_row())` was the only sensible iteration pattern. Before there were exceptions `if ($result = query())` was the cleanest error-checking approach. Also PHP didn't invent that syntax in any way, but followed C (in the end early PHP was a simplified C created by a C programmer) it only later git higher level constructs making this less needed.
- sametmax 8y agoIn PHP, the "if ($a = 1)" and "if ($a == 1)" look a lot like each others. In the Python version, the new "(a := 1)" will stand out compare to the canonical "a == 1" as the ':' and '()' are unexpected here, and scream that it's not regular if test.
- julienfr112 8y agoI miss that feature in multiple occasion list comprehension. I hacked through that with for x in [expr] but it never fell right.
- jkabrg 8y agoIt would be nice if in Python you could define new operators instead of overloading existing ones. It would make matrix multiplication look nicer. I'm thinking it could look like this: import numpy as np def M1 %*% M2 with same precedence as *: return M1.matmul(M2) foo_matrix = np.matrix([[1,1],[1,1]]) bar_matrix = np.matrix([[2,2],[2,2]]) print(foo_matrix %*% bar_matrix) Also, it would be nice to have a pipe operator `%>%` such that foo %>% f() is equivalent to f(foo) The alternative is to make f a method of foo, and then you can write foo.f() But what happens if I don't want f to be a method? I just want the style of writing the f after the foo, but I don't want the baggage of OOP. Is that anti-Pythonic?
- misnome 8y agoI know your question isn't specifically about the exact example, but are you aware that they added the @ operator for matmul in 3.5? I think anything general would probably be considered as cluttering up the grammar or non-pythonic. Completely custom unicode operators was something I loved about Swift (open to abuse, but really useful in moderation).
- Franciscouzo 8y agoMaybe you already know, but the @ operator already exists for matrix multiplication.
- ben509 8y agoI think the problem with custom operators is they have custom associativity and precedence. Not saying this is a deal breaker, just the biggest issue you'll run into. In toy examples, you can see all that because they'll show the declarations. Here's a case[1] trying to explain monads by showing a simpler example: parseP5_take2 s = matchHeader (L8.pack "P5") s >>? \s -> skipSpace ((), s) >>? (getNat . snd) >>? skipSpace >>? \(width, s) -> getNat s >>? skipSpace >>? \(height, s) -> getNat s >>? \(maxGrey, s) -> getBytes 1 s >>? (getBytes (width * height) . snd) >>? \(bitmap, s) -> Just (Greymap width height maxGrey bitmap, s) Is all that left or right associative? Generally, to read an expression when you have custom operators, you have to dig up the associativity and precedence. That's why I see many newer libraries avoiding custom operators, [2] vs [3]. I think you can do custom operators in a language, you just need to require that the associativity and precedence be declared in import. I'd also have named levels of associativity, so maybe: import MyModule.Foo.Bar (op(!$!, additive, right), ...) Now, that's some boilerplate in your imports, but for readability, it'd be huge. And automated tools can tweak stuff like this. [1] http://book.realworldhaskell.org/read/monads.html http://book.realworldhaskell.org/read/monads.html [2] http://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-PrettyPrint.html#g:6 http://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-... [3] https://hackage.haskell.org/package/prettyprinter-1.2.1/docs/Data-Text-Prettyprint-Doc.html#g:17 https://hackage.haskell.org/package/prettyprinter-1.2.1/docs...
- Derbasti 8y agoDoes this mean that I can write with file := open('filename'): data = file.read() instead of with open('filename') as file: data = file.read() I don't know how I feel about this.
- glifchits 8y agoInteresting! I figured the `as` in a `with` statement was handled uniquely, but I learned something new about Python today: x = open('filename') x.closed # False with x: print(x.readline()) x.closed # True I think you're right. I prefer the `as` variant for readability.
- zb 8y agoIt's the same for classes that define it like: def __enter__(self): return self which is a common pattern (used by open()), but there's no requirement that __enter__() return the same object. In cases where __enter__() does something different, the assignment expression and the 'as' variable would have different values (the object of the 'with' statement, x, and the result of calling x.__enter__(), respectively).
- thomasahle 8y agoExactly. Using "as" everywhere might require some small backward incompatible changes to make sure semantics line up, but at least then we wouldn't have redundant syntax.
- Myrmornis 8y agoFrom the PEP # Share a subexpression between a comprehension filter clause and its output filtered_data = [y for x in data if (y := f(x)) is not None] What about filtered_data = [(y := f(x)) for x in data if y is not None] will that work also? Attempted guess at answer: no, because the "gathering" part of the comprehension is downstream of the "sequence construction" part of the comprehension in the language implementation. But if that's so, I'm a bit concerned that this will be confusing to users.
- carapace 8y agoI'm a huge Python fanboy and I've been so happy to be able to use it professionally for the last decade or so, but I think the BDFL may have lost his touch. Or he just doesn't care anymore. If I hadn't already decided to stick to Python 2 (likely in the form of the Tauthon project, but there are lots of Python 2 runtimes out there. Lots. (Everybody always forgets about Stackless, for example)), I say, if I hadn't already decided to stick to Python 2, this would be the camel-back-breaking straw. We're trucking in footguns from C now? These examples in the PEP, they all seem bad to me, written to be too clever by someone who doesn't think well. E.g.: filtered_data = [y for x in data if (y := f(x)) is not None] How about: filtered_data = [y for y in (f(x) for x in data) if y is not None] Or just? filtered_data = filter(None, map(f, data)) (If f() can return non-None "Falsey" values then this would require a predicate function. But maybe f() is badly designed?) Or this: if any(len(longline := line) >= 100 for line in lines): print("Extremely long line:", longline) What's wrong with the "old-fashioned" way? for line in lines: if len(line) >= 100: print("Extremely long line:", line) break Of course, in the bad old days when loop variables leaked I think you could just write: if any(len(line) >= 100 for line in lines): print("Extremely long line:", line) But I'm not sure, and in any event leaking loop vars was fixed at some point. (Good. It was useful but bug-prone. Just like this PEP!) To me this is also a mess: results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0] It should be: results = [(x, y, x / y) for x, y in zip(input_data, map(f, input_data)) if y > 0] Maybe all the examples are just poorly chosen, but I don't believe that. I think this PEP is just a bad idea. Badly bad.
- UncleEntity 8y ago> But I'm not sure, and in any event leaking loop vars was fixed at some point. (Good. It was useful but bug-prone. Just like this PEP!) So, basically, they have a proposal for an expression that explicitly says "I want to leak this variable into the enclosing scope" and it is less useful than the implicit old way? Python's variable scoping rules are a mess anyway -- some are function level, some are block level and I usually can't tell which is which until the compiler complains...
- 8y ago
- passive 8y agoThis is nice. I very frequently use something like the following: [node.find(SOME_XPATH).get("value") for node in tree if node.find(SOME_XPATH) is not None] Which I can soon rewrite as: [found_node.get("value") for node in tree if (found_node := node.find(SOME_XPATH)) is not None] There's a certain amount of complexity introduced, but I think removing the duplication makes up for it. This is one of the few remaining cases in Python where I feel like there's not a simple way to avoid repeating myself.
- stared 8y agoStill fail to see why instead of that there isn't more functional list comprehension, with maps and filters. That way we wouldn't get such problems in the first place. For more advanced list compherensions, even JavaScript (ES6+) is more readable.
- joeframbach 8y ago[found_node.get("value") for node in tree if (found_node := node.find(SOME_XPATH)) is not None] tree.map(node => node.find(SOME_XPATH)).filter(Boolean).map(node => node.get("value")) I can deal with either language at this level of complexity. Anything more complicated needs more LoC in either language.
- williamdclt 8y agoI vastly prefer the second one, as I would expect to see it written something like this: tree .map(node => node.find(SOME_XPATH)) .filter(Boolean) .map(node => node.get("value")) I don't use Python on a daily basis, but I find that infinitely more readable than the comprehension version (plus, it's a syntax used in several major languages)
- lukeschlather 8y agoIn fairness, the Python also should be broken up. It should be: [ found_node.get("value") for node in tree if (found_node := node.find(SOME_XPATH)) is not None ] I think I also prefer Ruby/Javascript style comprehensions, but it's worth comparing well formatted code.
- esaym 8y agoWith every PEP it seems Python gets closer to Perl...
- Animats 8y agoMust...have...functional...features. Python's indentation system was so nice for imperative programs. But it gets in the way when functional features are retrofitted. The "everything must be one big expression" style is a total mismatch to Python. The beauty is gone.
- pgorczak 8y agoYet using functional staples like map and reduce is discouraged by GvR himself. I guess it's alright as long as the language can adapt to your needs (even though this might contradict the "one obvious way" idea).
- dmytrish 8y agoHaskell has a similar indentation system, it fits the language very well.
- KirinDave 8y agoI hope I'm not the only person who reads this and really, really dislikes this. If Python's gonna have breaking syntax, why not work on bringing it more in line with other modern languages that don't require special breakout syntax for expressions and rely more on functional features? Are we still maintaining that lambdas are hard but suggesting expression-scoped variables are easy?
- viraptor 8y agoThe difference here is that this pattern of get-value-check-it is in pretty much every program longer than a few lines. And possibly in every one using regexes. Missing lambda opportunities are not nearly as easy to point out.
- _diyu 8y agoSpeaking as someone whose first dynamic language was python, it’s full of weird inconsistencies and I have no idea why anyone voluntarily chooses it when teaching programming. Granted all languages have warts, but people say python is easier to learn programming concepts in than other languages and I just don’t see it. I think they just mean the indentation syntax discourages confusingly indented code?
- icebraining 8y agoWhat would you choose?
- bluecalm 8y agoThis is badly needed equivalent of where keyword in Haskell very useful (among other things) in list comprehensions to avoid duplicating function calls (now you assign the result of the call and use it again in the beginning part of the comprehension). That being said it's another example of how after many years it turns out that C got it right all along. Assignment being an expression (not a statement) makes many useful idioms possible. Sure, there was a problem with typos concerning '=' and '==' being similar but this was solved by convention of requiring parenthesis around assignment if its value is used as boolean. If you turn on warning in modern C compiler you will never make this error again. Interestingly it's also what Python has chosen even though they went with := operator. Personally I prefer having one assignment operator and assignment being an expression. If it's := or = really doesn't matter in my view.
- p3llin0r3 8y agoThat's nice. Give me the `|>` operator plz
- meken 8y agoDon't anaphoric macros [1] provide an alternative solution to this problem? Though they seem cool I can see how introducing a magic variable can result in unreadable code... - [1] https://en.wikipedia.org/wiki/Anaphoric_macro https://en.wikipedia.org/wiki/Anaphoric_macro