if expensive_computation() as x:
Hi everybody, Please excuse the recent torrent of crazy ideas :) I was reading code of the Shpaml project, trying to make a patch, while I saw code that looked inelegant to me. I considered how to improve it, but then saw I don't know how to. The code paraphrased is this: if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x... The problem here is that we're doing the expensive computation twice. That's because we first need to check its truth value, and if it's true we need to actually use the value and do stuff with it. One solution would be to calculate it and put it in a variable before checking truth value. The problem with that is, besides being quite verbose, it doesn't work with the `elif` lines. You want to calculate the values only if the `elif` branch is being taken. (i.e. you can't do it before the `if` because then you might be calculating it needlessly since maybe the first condition would be true.) Obviously this can be "solved", i.e. rewritten in a way that wouldn't call the expensive operation twice, but the solution would probably be quite verbose, which is something we don't want. My suggestion: if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x... If you'd like to bind to a variable only a part of the condition, this would work too: if x<5 with expensive_computation_0() as x: # Do something with x What do you think? Ram.
Responding to your post in different order to the original. On Fri, Feb 14, 2014 at 8:59 AM, Ram Rachum <ram.rachum@gmail.com> wrote:
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
Definitely don't like this syntax - while it might be useful to snapshot part of a condition (I've done it in C plenty of times), this notation feels clumsy. However...
My suggestion:
if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x...
... this simpler form does look reasonable. The "as" part will *only* come at the end of the expression, and it *always* applies to the whole expression, so it's fairly clear. There is another cheat you can do, though, and that's to use break or return instead of an elif chain. Going back to your original:
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
the alternative would be something like this: while "allow_break": x = expensive_computation_0(): if x: # Do something with x... break x = expensive_computation_1(): if x: # Do something with x... break x = expensive_computation_2(): if x: # Do something with x... break # whatever your 'else' clause would be, if any break Or, using a function instead: def allow_return(): nonlocal everything, you, need x = expensive_computation_0(): if x: # Do something with x... return x = expensive_computation_1(): if x: # Do something with x... return x = expensive_computation_2(): if x: # Do something with x... return allow_return() Both of them are abusing their keywords into gotos, but ultimately, that's what if/elif/elif is anyway - as soon as you finish one elif, you goto the end of the block. It's not perfect by any means, but it works. ChrisA
On Thu, Feb 13, 2014 at 9:58 PM, Chris Angelico <rosuav@gmail.com> wrote:
Responding to your post in different order to the original.
On Fri, Feb 14, 2014 at 8:59 AM, Ram Rachum <ram.rachum@gmail.com> wrote:
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
Definitely don't like this syntax - while it might be useful to snapshot part of a condition (I've done it in C plenty of times), this notation feels clumsy. However...
I agree that a non-clunky way to extract variables from conditions with an operator would be nice. Maybe a better syntax would be: if (expensive_computation_0() as x)<5: # Do something with x And likewise for `while` loops, while (expensive_computation_0() as x)<5: # Do something with x
My suggestion:
if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x...
... this simpler form does look reasonable. The "as" part will *only* come at the end of the expression, and it *always* applies to the whole expression, so it's fairly clear.
Agreed, this looks reasonable to me. These are special cases of PEP 379, "Adding an Assignment Expression" ( http://www.python.org/dev/peps/pep-0379/) from 2009, which has been withdrawn. Perhaps it would be better received if restricted to if/while conditions. Nathan
Worth noting that Go has a similar syntax: if x := expensive_computation(); x < 5 { fmt.Println("x is smaller than 5: %d", x) } else { fmt.Println("also have access to x here: %d", x) } 2014-02-13 19:19 GMT-08:00 Nathan Schneider <nathan@cmu.edu>:
On Thu, Feb 13, 2014 at 9:58 PM, Chris Angelico <rosuav@gmail.com> wrote:
Responding to your post in different order to the original.
On Fri, Feb 14, 2014 at 8:59 AM, Ram Rachum <ram.rachum@gmail.com> wrote:
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
Definitely don't like this syntax - while it might be useful to snapshot part of a condition (I've done it in C plenty of times), this notation feels clumsy. However...
I agree that a non-clunky way to extract variables from conditions with an operator would be nice. Maybe a better syntax would be:
if (expensive_computation_0() as x)<5:
# Do something with x
And likewise for `while` loops,
while (expensive_computation_0() as x)<5:
# Do something with x
My suggestion:
if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x...
... this simpler form does look reasonable. The "as" part will *only* come at the end of the expression, and it *always* applies to the whole expression, so it's fairly clear.
Agreed, this looks reasonable to me.
These are special cases of PEP 379, "Adding an Assignment Expression" (http://www.python.org/dev/peps/pep-0379/) from 2009, which has been withdrawn. Perhaps it would be better received if restricted to if/while conditions.
Nathan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 02/14/2014 04:19 AM, Nathan Schneider wrote:
On Thu, Feb 13, 2014 at 9:58 PM, Chris Angelico <rosuav@gmail.com> wrote:
Responding to your post in different order to the original.
On Fri, Feb 14, 2014 at 8:59 AM, Ram Rachum <ram.rachum@gmail.com> wrote:
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
Definitely don't like this syntax - while it might be useful to snapshot part of a condition (I've done it in C plenty of times), this notation feels clumsy. However...
I agree that a non-clunky way to extract variables from conditions with an operator would be nice. Maybe a better syntax would be:
if (expensive_computation_0() as x)<5: # Do something with x
And likewise for `while` loops,
while (expensive_computation_0() as x)<5: # Do something with x
My suggestion:
if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x...
... this simpler form does look reasonable. The "as" part will *only* come at the end of the expression, and it *always* applies to the whole expression, so it's fairly clear.
Agreed, this looks reasonable to me.
These are special cases of PEP 379, "Adding an Assignment Expression" ( http://www.python.org/dev/peps/pep-0379/) from 2009, which has been withdrawn. Perhaps it would be better received if restricted to if/while conditions.
Nathan
Isn't this asking for a python variant of C's X x; if (x = f()) {...} for (x = f()) {...} ? Remember all the critics around such constructs? (IIRC partly, but not only, due to the misuse of '=' to mean assignment; the other part is that it is hard to think right, intuitively the mix of a computation [f()] and an action [assignment] is a single construct; this is also partly why people never check the fake error-results of "action-functions") d
Am 2014-02-14 03:58, schrieb Chris Angelico:
Responding to your post in different order to the original.
On Fri, Feb 14, 2014 at 8:59 AM, Ram Rachum <ram.rachum@gmail.com> wrote:
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
Definitely don't like this syntax - while it might be useful to snapshot part of a condition (I've done it in C plenty of times), this notation feels clumsy. However...
My suggestion:
if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x...
... this simpler form does look reasonable. The "as" part will *only* come at the end of the expression, and it *always* applies to the whole expression, so it's fairly clear.
There is another cheat you can do, though, and that's to use break or return instead of an elif chain. Going back to your original:
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
the alternative would be something like this:
while "allow_break": x = expensive_computation_0(): if x: # Do something with x... break x = expensive_computation_1(): if x: # Do something with x... break x = expensive_computation_2(): if x: # Do something with x... break # whatever your 'else' clause would be, if any break
Or, using a function instead:
def allow_return(): nonlocal everything, you, need x = expensive_computation_0(): if x: # Do something with x... return x = expensive_computation_1(): if x: # Do something with x... return x = expensive_computation_2(): if x: # Do something with x... return allow_return()
Both of them are abusing their keywords into gotos, but ultimately, that's what if/elif/elif is anyway - as soon as you finish one elif, you goto the end of the block. It's not perfect by any means, but it works.
Or if "Do something with x" is always the same: x = expensive_computation_0() or expensive_computation_1() or expensive_computation_2() if x: # Do something with x...
On Fri, Feb 14, 2014 at 2:29 PM, Mathias Panzenböck <grosser.meister.morti@gmx.net> wrote:
Or if "Do something with x" is always the same:
x = expensive_computation_0() or expensive_computation_1() or expensive_computation_2()
if x: # Do something with x...
I would assume that it's not the same... otherwise this method doesn't just obviate the need for comparison-assignment, it also deduplicates a block of code. I think most programmers are smart enough to figure out that that's important :) ChrisA
On 02/13/2014 01:59 PM, Ram Rachum wrote:
Hi everybody,
Please excuse the recent torrent of crazy ideas :)
I was reading code of the Shpaml project, trying to make a patch, while I saw code that looked inelegant to me. I considered how to improve it, but then saw I don't know how to.
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
The problem here is that we're doing the expensive computation twice. That's because we first need to check its truth value, and if it's true we need to actually use the value and do stuff with it.
Use a pocket function: def pocket(value=None, _storage=[]): if value is not None: _storage[:] = [value] return _storage[0] and then: if pocket(expensive_computation_0()): x = pocket() # Do something with x... elif pocket(expensive_computation_1()): x = pocket() # Do something with x... elif pocket(expensive_computation_2()): x = pocket() # Do something with x... -- ~Ethan~
You could gather all the functions in a list and then iterate and break when needed: for func in [f1, f2, f3, f4]: x = func() if x: # bla bla break Sorry for the indentation if it's not right, I'm writing from my phone. Il 14/feb/2014 03:40 "Ram Rachum" <ram.rachum@gmail.com> ha scritto:
Hi everybody,
Please excuse the recent torrent of crazy ideas :)
I was reading code of the Shpaml project, trying to make a patch, while I saw code that looked inelegant to me. I considered how to improve it, but then saw I don't know how to.
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
The problem here is that we're doing the expensive computation twice. That's because we first need to check its truth value, and if it's true we need to actually use the value and do stuff with it.
One solution would be to calculate it and put it in a variable before checking truth value. The problem with that is, besides being quite verbose, it doesn't work with the `elif` lines. You want to calculate the values only if the `elif` branch is being taken. (i.e. you can't do it before the `if` because then you might be calculating it needlessly since maybe the first condition would be true.)
Obviously this can be "solved", i.e. rewritten in a way that wouldn't call the expensive operation twice, but the solution would probably be quite verbose, which is something we don't want.
My suggestion:
if expensive_computation_0() as x: # Do something with x... elif expensive_computation_1() as x: # Do something with x... elif expensive_computation_2() as x: # Do something with x...
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
What do you think?
Ram.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, Feb 14, 2014 at 5:49 PM, Michele Lacchia <michelelacchia@gmail.com> wrote:
You could gather all the functions in a list and then iterate and break when needed:
for func in [f1, f2, f3, f4]: x = func() if x: # bla bla break
Sorry for the indentation if it's not right, I'm writing from my phone.
The indentation's fine, but this still assumes that the 'if x' block in each case is the same. ChrisA
On Fri, Feb 14, 2014 at 07:49:02AM +0100, Michele Lacchia wrote:
You could gather all the functions in a list and then iterate and break when needed:
for func in [f1, f2, f3, f4]: x = func() if x: # bla bla break
To me, that is the cleanest, most obvious solution to avoid repeating yourself. Must nicer than the suggestion to add name binding to arbitrary expressions. The only downside is that it assumes the "bla bla" part is identical for each function. Coming back to Ram's suggestion:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
The problem here is that we're doing the expensive computation twice.
It's not really a language problem. The problem is not the lack of syntax, but the poor way the code is written, avoiding taking advantage of Python's already existing features. Put the code inside a function, assign the expensive computations once each time, and return as needed: def perform_expensive_calculation(): x = expensive_computation_0() if x: # Do something with x... return x = expensive_computation_1() if x: # Do something with x... return This requires no new syntax, and it is easy to understand. If the "do something" parts include returning a value, you can drop the bare returns as well.
Obviously this can be "solved", i.e. rewritten in a way that wouldn't call the expensive operation twice, but the solution would probably be quite verbose, which is something we don't want.
It's not at all verbose. Apart from the extra returns, which are only needed if you're not otherwise returning from the "do something" parts, it is no more verbose than what you already have.
My suggestion:
if expensive_computation_0() as x: # Do something with x...
If we must have an alternative name binding of expressions, I don't mind this too much. But I'm not sold that this is needed.
If you'd like to bind to a variable only a part of the condition, this would work too:
if x<5 with expensive_computation_0() as x: # Do something with x
I think that's hard to understand, unnecessary and clashes with the existing use of "with". It also leads to the possibility of abuse: x < 5 with (y + 1)/y with z*(z+1) with function(arg) as z as y as x -- Steven
On 02/13/2014 10:59 PM, Ram Rachum wrote:
Hi everybody,
Please excuse the recent torrent of crazy ideas :)
I was reading code of the Shpaml project, trying to make a patch, while I saw code that looked inelegant to me. I considered how to improve it, but then saw I don't know how to.
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
Such a pattern is rather common, eg in some kind of hand-baked parsing. (Say you have match funcs returning False if no match, True if match but you don't want/need the result, some "form" having a truth value of True if you need the form; then you are matching a pattern choice). Solution 0: for a little and particular choice: x = expensive_computation_0() if not x: x = expensive_computation_1() if not x: x = expensive_computation_2() # assert(x) # or whatever check Alternative: x = expensive_computation_0() \ or expensive_computation_1() \ or expensive_computation_2() # assert(x) Works due to lazy evaluation of logical operations. Solution 1: for a big choice, or the general case: for comp in computations: x = comp() if x: break # assert(x) However, one must notice that in some way we are here abusing the logical idea of truth value (of a result, specifically) to make it indcate the success of a computation. In other words, we are carrying two pieces of information (result+success) in the same single piece of data. And the loop version only works if all comp's take no param, or the same one(s), or you have a parallel array of param sets (in parsing, all match funcs take source & index). d
On 02/14/2014 09:05 AM, spir wrote:
On 02/13/2014 10:59 PM, Ram Rachum wrote:
Hi everybody,
Please excuse the recent torrent of crazy ideas :)
I was reading code of the Shpaml project, trying to make a patch, while I saw code that looked inelegant to me. I considered how to improve it, but then saw I don't know how to.
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
Such a pattern is rather common, eg in some kind of hand-baked parsing. (Say you have match funcs returning False if no match, True if match but you don't want/need the result, some "form" having a truth value of True if you need the form; then you are matching a pattern choice).
Solution 0: for a little and particular choice:
x = expensive_computation_0() if not x: x = expensive_computation_1() if not x: x = expensive_computation_2() # assert(x) # or whatever check
Alternative: x = expensive_computation_0() \ or expensive_computation_1() \ or expensive_computation_2() # assert(x)
Works due to lazy evaluation of logical operations.
Solution 1: for a big choice, or the general case:
for comp in computations: x = comp() if x: break # assert(x)
Oh, I did not get the action performed is supposed to be different in each case. (Note: you should have used indices 1, 2, 3... for actions as well.) Then, we need a // array of actions. Something like: for i in range(len(comps)): # or zip x = comps[i]() if x: actions[i]() break # assert(x) But it still assumes all computations and all actions take the same input. Else, we're left with unrolling the loop: but how to break outside no loop? The solution may be to export the whole series comps/actions into a func and return from it. def f (): ... g() ... def g (): x = comp1() if x: action1(x) return x = comp2() if x: action2(x) return ... d
On Fri, Feb 14, 2014 at 7:24 PM, spir <denis.spir@gmail.com> wrote:
But it still assumes all computations and all actions take the same input. Else, we're left with unrolling the loop: but how to break outside no loop? The solution may be to export the whole series comps/actions into a func and return from it.
You can break out of a loop that's guaranteed to execute only once anyway. In C, that's sometimes spelled "do {......} while (0);", but in Python you just put a hard break at the end of it. That or 'return' from a function, either way works. ChrisA
On 2/13/2014 4:59 PM, Ram Rachum wrote:
Hi everybody,
Please excuse the recent torrent of crazy ideas :)
I was reading code of the Shpaml project, trying to make a patch, while I saw code that looked inelegant to me. I considered how to improve it, but then saw I don't know how to.
The code paraphrased is this:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x... elif expensive_computation_1(): x = expensive_computation_1() # Do something with x... elif expensive_computation_2(): x = expensive_computation_2() # Do something with x...
I do not really understand the fear of indents that would cause one to repeat calculations rather than write the actual logic. x = expensive_computation_0(): if x: # Do something with x... else: x = expensive_computation_1() if x: # Do something with x... else: x = expensive_computation_2() # Do something with x... If the code is already indented so much that 8 more spaces is a burden, then temporarily use 1 space indents. If do_something is the same, I would use 'or' as already posted. Python 'or' expressions are flow-control expression, not just logic expresssion. If there are more nested clauses, a separate function or "while 'fake loop': ... break" is fine. -- Terry Jan Reedy
On Fri, Feb 14, 2014 at 05:18:55AM -0500, Terry Reedy wrote:
I do not really understand the fear of indents that would cause one to repeat calculations rather than write the actual logic.
x = expensive_computation_0(): if x: # Do something with x... else: x = expensive_computation_1() if x: # Do something with x...
That's really not very nice looking. It's okay with one or two levels, three at the most, but avoiding that sort of thing is why we have elif in the first place. So I wouldn't call it a *fear* of indents, more an dislike of excessive indentation. -- Steven
On Sat, Feb 15, 2014 at 7:48 AM, Steven D'Aprano <steve@pearwood.info> wrote:
On Fri, Feb 14, 2014 at 05:18:55AM -0500, Terry Reedy wrote:
I do not really understand the fear of indents that would cause one to repeat calculations rather than write the actual logic.
x = expensive_computation_0(): if x: # Do something with x... else: x = expensive_computation_1() if x: # Do something with x...
That's really not very nice looking. It's okay with one or two levels, three at the most, but avoiding that sort of thing is why we have elif in the first place. So I wouldn't call it a *fear* of indents, more an dislike of excessive indentation.
More to the point, excessive _and inappropriate_ indentation. An if/elif/elif/elif/else chain puts all the conditions at the same level, and all the bodies at the same level (one further in than the conditions). The intention is that they're all peers, not that they're nested inside each other. Maybe the computer physically executes it as a series of nested conditions (more likely, it's a series of conditions with big fat GOTOs to get out of them), but logically and conceptually, it's not that, so it shouldn't be written that way. Since Python, unlike C, doesn't let you assign inside a condition (and C doesn't let you declare inside a condition - nor does C++, though at least there you can declare inside a for loop, which is the most common case), you need to warp your code somewhat around what the language supports; in my opinion, the less warping, the better, which means either a dummy while loop or a nested function. (The nested function might be clearer in a lot of situations, but having to declare too many nonlocals may damage that, which would be a point in favour of while/break. It'd be nice to just say "nonlocal *" meaning "everything that looks local isn't", but that's probably a nightmare for the interpreter.) ChrisA
On 2/14/2014 4:06 PM, Chris Angelico wrote:
On Sat, Feb 15, 2014 at 7:48 AM, Steven D'Aprano <steve@pearwood.info> wrote:
On Fri, Feb 14, 2014 at 05:18:55AM -0500, Terry Reedy wrote:
I do not really understand the fear of indents that would cause one to repeat calculations rather than write the actual logic.
In C, where (syntactically insignificant) 8-space tabs are standard, I can, but not in Python where 4 spaces are standard and one can use less.
x = expensive_computation_0(): if x: # Do something with x... else: x = expensive_computation_1() if x: # Do something with x...
That's really not very nice looking.
It is an accurate representation of the logic, so you are saying that the logic is not nice looking. Ok.
It's okay with one or two levels, three at the most,
As I said, but you clipped.
More to the point, excessive _and inappropriate_ indentation. An if/elif/elif/elif/else chain puts all the conditions at the same level, and all the bodies at the same level (one further in than the conditions). The intention is that they're all peers,
But they are *not* peers.
not that they're nested inside each other.
But logically, they are.
Since Python, unlike C, doesn't let you assign inside a condition
The C construction with a side-effect expression as a condition is a bit of a contortion. Its main virtue is avoidance of indents. -- Terry Jan Reedy
On 02/14/2014 02:02 PM, Terry Reedy wrote:
On 2/14/2014 4:06 PM, Chris Angelico wrote:
More to the point, excessive _and inappropriate_ indentation. An if/elif/elif/elif/else chain puts all the conditions at the same level, and all the bodies at the same level (one further in than the conditions). The intention is that they're all peers,
But they are *not* peers.
not that they're nested inside each other.
But logically, they are.
Maybe `logic`ally, but not `human`ly. ;) For me at least, nested implies that to get to B, A has to be true: therefore B is inside A. Otherwise it's simply a matter of prioritizing - LIFO, FIFO, random(), or whatever, and the only requirement to try the next is that the last did not meet our criteria. -- ~Ethan~
On Sat, Feb 15, 2014 at 9:02 AM, Terry Reedy <tjreedy@udel.edu> wrote:
More to the point, excessive _and inappropriate_ indentation. An if/elif/elif/elif/else chain puts all the conditions at the same level, and all the bodies at the same level (one further in than the conditions). The intention is that they're all peers,
But they are *not* peers.
not that they're nested inside each other.
But logically, they are.
C has a switch statement that looks like this: switch (expr) { case value1: code1; break; case value2: code2; break; case value3: code3; break; case value4: code4; break; default: code5; break; } Are the case statements peers? The intention is usually that they are. One does not indent each case statement equally to its preceding code block, ever-increasing down the page. What if we rewrite this with nothing but if and else? x = expr; if (x == value1) code1; else if (x == value2) code2; else if (x == value3) code3; else if (x == value4) code4; else code5; Tell me, please, why code4 and code5 are at equal indentation, but all the other code blocks are different. In Python, we could use a dictionary: switch = { value1: lambda: code1, value2: lambda: code2, value3: lambda: code3, value4: lambda: code4, None: lambda: code5 } switch.get(expr, switch[None])() Once again, they're all peers. And if you define all the functions out of line, they'll still be peers, both in definition and in execution. Not one of them depends on any other. There is no sane and logical reason to indent each one further than the others, because *to the programmer* they are peers. And that's my point. No matter how the *computer* sees them, the *programmer* sees them as peers. Adding an additional option to the list should not change the indentation level of any other, because the logical structure of the code hasn't changed. ChrisA
On 15 February 2014 10:49, Chris Angelico <rosuav@gmail.com> wrote:
And that's my point. No matter how the *computer* sees them, the *programmer* sees them as peers. Adding an additional option to the list should not change the indentation level of any other, because the logical structure of the code hasn't changed.
You snipped the salient point in all this: the relevant case is one where the programmer wants to check *different* things, and *only* wants to calculate them if the earlier things are false. If they were true peers, this approach would work: x = alternative_1() y = alternative_2() z = alternative_3() if x: # Do something with x elif y: # Do something with y elif z: # Do something with z Ram's concern is that the calculation of y and z isn't needed if x is true, and these calculations are considered to expensive to execute unconditionally, so calculating all three up front isn't wanted. The natural shortcircuiting translation of that is nested if statements, not an elif chain, because these *aren't* peers, the programmer actually wants execution of the later calculations to be conditional on the results of the earlier ones: x = alternative_1() if x: # Do something with x else: y = alternative_2() if y: # Do something with y else: z = alternative_3() if z: # Do something with z If the response to this recommendation is "but I need them all to be bound to x, because x gets reused later in the function and these are just different ways of calculating x", then we have *definitely* reached the point where the correct answer is not "make this kind of spaghetti code easier to write", but instead "refactor the function so that calculating x is a clearly distinct named operation": x = calculate_x() And if the objection to *that* is "but I don't like factoring out functions that are only used once", then the appropriate response is to keep looking for a better way to handle one-shot function definitions (along the lines of PEPs 403 and 3150), not attempting to reduce the number of cases where factoring out a one shot function is appropriate by encouraging more spaghetti code. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Sat, Feb 15, 2014 at 12:12 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 15 February 2014 10:49, Chris Angelico <rosuav@gmail.com> wrote:
And that's my point. No matter how the *computer* sees them, the *programmer* sees them as peers. Adding an additional option to the list should not change the indentation level of any other, because the logical structure of the code hasn't changed.
You snipped the salient point in all this: the relevant case is one where the programmer wants to check *different* things, and *only* wants to calculate them if the earlier things are false. If they were true peers, this approach would work:
x = alternative_1() y = alternative_2() z = alternative_3()
if x: # Do something with x elif y: # Do something with y elif z: # Do something with z
I believe they're still peers; they just happen to be unable to fit into the physical structure of Python that way. Her's a concrete example: You have a series of regular expressions, and you want to process whichever one matches. No line of text is allowed to match more than one regex (to prevent excessive processing), but any line could match any regex. if re1.match(line) as match: # do stuff with the match object elif re2.match(line) as match: # do different stuff, using the matched substrings # etc etc etc Adding another one is as simple as determining its position in the sequence (since earlier matches take precedence over later ones). Removing one is just deleting its elif and corresponding block of code. They are peers. The fact that Python doesn't currently have syntax that allows this *in an elif chain* doesn't change this; if you were to write it as pseudo-code, you would write them as peers. That's why the function-with-return or loop-with-break still looks better, because it structures the code the way that logically makes sense - flat, not nested. ChrisA
On 15 February 2014 11:22, Chris Angelico <rosuav@gmail.com> wrote:
On Sat, Feb 15, 2014 at 12:12 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 15 February 2014 10:49, Chris Angelico <rosuav@gmail.com> wrote:
And that's my point. No matter how the *computer* sees them, the *programmer* sees them as peers. Adding an additional option to the list should not change the indentation level of any other, because the logical structure of the code hasn't changed.
You snipped the salient point in all this: the relevant case is one where the programmer wants to check *different* things, and *only* wants to calculate them if the earlier things are false. If they were true peers, this approach would work:
x = alternative_1() y = alternative_2() z = alternative_3()
if x: # Do something with x elif y: # Do something with y elif z: # Do something with z
I believe they're still peers; they just happen to be unable to fit into the physical structure of Python that way. Her's a concrete example: You have a series of regular expressions, and you want to process whichever one matches. No line of text is allowed to match more than one regex (to prevent excessive processing), but any line could match any regex.
if re1.match(line) as match: # do stuff with the match object elif re2.match(line) as match: # do different stuff, using the matched substrings # etc etc etc
This use case is a large part of why the "first" PyPI project exists and why itertools.first_true has been proposed for inclusion in the stdlib: http://bugs.python.org/issue18652 (a patch would help greatly in getting that to actually happen for 3.5) The re.match case would then be written as: match = first_true(pattern.match for pattern in patterns) If the handling is identical, there's no need for an if/elif chain at all, otherwise the revised if/elif chain is based on the attributes of the match object.
Adding another one is as simple as determining its position in the sequence (since earlier matches take precedence over later ones).
And with first_true, it's similarly simple (especially in the case where the patterns are different, but the handling is predominantly the same, since then it's just a matter of adding a new entry to the list of patterns).
Removing one is just deleting its elif and corresponding block of code. They are peers. The fact that Python doesn't currently have syntax that allows this *in an elif chain* doesn't change this; if you were to write it as pseudo-code, you would write them as peers. That's why the function-with-return or loop-with-break still looks better, because it structures the code the way that logically makes sense - flat, not nested.
That doesn't mean embedded assignments are the answer though - they're a sledgehammer solution that is tempting as a workaround for larger structural issues. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 15 February 2014 11:40, Nick Coghlan <ncoghlan@gmail.com> wrote:
The re.match case would then be written as:
match = first_true(pattern.match for pattern in patterns)
Oops, that was supposed to be: match = first_true(pattern.match(line) for pattern in patterns) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Sat, Feb 15, 2014 at 12:40 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
If the handling is identical, there's no need for an if/elif chain at all, otherwise the revised if/elif chain is based on the attributes of the match object.
If the handling's identical, short-circuiting 'or' will do the job. Assume it's not.
Removing one is just deleting its elif and corresponding block of code. They are peers. The fact that Python doesn't currently have syntax that allows this *in an elif chain* doesn't change this; if you were to write it as pseudo-code, you would write them as peers. That's why the function-with-return or loop-with-break still looks better, because it structures the code the way that logically makes sense - flat, not nested.
That doesn't mean embedded assignments are the answer though - they're a sledgehammer solution that is tempting as a workaround for larger structural issues.
I agree that embedded assignment isn't necessarily the answer. I just think that a solution that has them all at the same indentation level makes more sense than one that nests them inside each other, for exactly the same reason that we have 'elif' in the first place. ChrisA
I am stepping late on the thread - but the wish for having a short cut for: if expensive_computation_0(): x = expensive_computation_0() # Do something with x... And havign dealt before with languages where one explicitly deal with the operanbd stack (like postscript), lead me to deploy this very simple module I called "stackfull" (in a wordplay with "stackless") - The functions in there simply annotate a list in the locals() dict of the current running frame (which cannot be accessed as a variable), and work as stack operators on that list - so the situation above become: if push(expensive_computation_0()): x = pop() # Do something with x... (all functions return the original operand, besides side effects on the "stack") It was devised most as a toy, but my wish was to fill the gap Python has for this pattern - so, if anyone is interested in invest in this idea so that we could have a nice, real useful thing to be used in the near future, be my guest. https://pypi.python.org/pypi/stackfull https://github.com/jsbueno/stackfull On 14 February 2014 23:43, Chris Angelico <rosuav@gmail.com> wrote:
On Sat, Feb 15, 2014 at 12:40 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
If the handling is identical, there's no need for an if/elif chain at all, otherwise the revised if/elif chain is based on the attributes of the match object.
If the handling's identical, short-circuiting 'or' will do the job. Assume it's not.
Removing one is just deleting its elif and corresponding block of code. They are peers. The fact that Python doesn't currently have syntax that allows this *in an elif chain* doesn't change this; if you were to write it as pseudo-code, you would write them as peers. That's why the function-with-return or loop-with-break still looks better, because it structures the code the way that logically makes sense - flat, not nested.
That doesn't mean embedded assignments are the answer though - they're a sledgehammer solution that is tempting as a workaround for larger structural issues.
I agree that embedded assignment isn't necessarily the answer. I just think that a solution that has them all at the same indentation level makes more sense than one that nests them inside each other, for exactly the same reason that we have 'elif' in the first place.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Feb 16, 2014, at 18:06, "Joao S. O. Bueno" <jsbueno@python.org.br> wrote:
I am stepping late on the thread - but the wish for having a short cut for:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x...
And havign dealt before with languages where one explicitly deal with the operanbd stack (like postscript), lead me to deploy this very simple module I called "stackfull" (in a wordplay with "stackless") -
The functions in there simply annotate a list in the locals() dict of the current running frame (which cannot be accessed as a variable), and work as stack operators on that list - so the situation above become:
if push(expensive_computation_0()): x = pop() # Do something with x...
(all functions return the original operand, besides side effects on the "stack") It was devised most as a toy, but my wish was to fill the gap Python has for this pattern - so, if anyone is interested in invest in this idea so that we could have a nice, real useful thing to be used in the near future, be my guest.
https://pypi.python.org/pypi/stackfull https://github.com/jsbueno/stackfull
Clever. What happens if expensive_computation() raises? Does the push get ignored? Capture the exception, push it, and re-raise, so the next pop will raise the same exception? Is there any reason this has to be local? If it were, say, a thread-local global, it could be used to wedge additional arguments through functions that weren't expecting them. (Together with a "mark" function of some kind you could even use this to experiment with alternate calling conventions, etc.) I still don't see how this fills a gap that needs to be filled; how is the first version any more readable, writable, concise, whatever than the second? if push(expensive()): x = pop() dostuff(x) x = expensive() if x: dostuff(x) But it's a clever idea even if it doesn't help there.
On 14 February 2014 23:43, Chris Angelico <rosuav@gmail.com> wrote:
On Sat, Feb 15, 2014 at 12:40 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
If the handling is identical, there's no need for an if/elif chain at all, otherwise the revised if/elif chain is based on the attributes of the match object.
If the handling's identical, short-circuiting 'or' will do the job. Assume it's not.
Removing one is just deleting its elif and corresponding block of code. They are peers. The fact that Python doesn't currently have syntax that allows this *in an elif chain* doesn't change this; if you were to write it as pseudo-code, you would write them as peers. That's why the function-with-return or loop-with-break still looks better, because it structures the code the way that logically makes sense - flat, not nested.
That doesn't mean embedded assignments are the answer though - they're a sledgehammer solution that is tempting as a workaround for larger structural issues.
I agree that embedded assignment isn't necessarily the answer. I just think that a solution that has them all at the same indentation level makes more sense than one that nests them inside each other, for exactly the same reason that we have 'elif' in the first place.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 17 February 2014 15:46, Andrew Barnert <abarnert@yahoo.com> wrote:
On Feb 16, 2014, at 18:06, "Joao S. O. Bueno" <jsbueno@python.org.br> wrote:
I am stepping late on the thread - but the wish for having a short cut for:
if expensive_computation_0(): x = expensive_computation_0() # Do something with x...
And havign dealt before with languages where one explicitly deal with the operanbd stack (like postscript), lead me to deploy this very simple module I called "stackfull" (in a wordplay with "stackless") -
The functions in there simply annotate a list in the locals() dict of the current running frame (which cannot be accessed as a variable), and work as stack operators on that list - so the situation above become:
if push(expensive_computation_0()): x = pop() # Do something with x...
(all functions return the original operand, besides side effects on the "stack") It was devised most as a toy, but my wish was to fill the gap Python has for this pattern - so, if anyone is interested in invest in this idea so that we could have a nice, real useful thing to be used in the near future, be my guest.
https://pypi.python.org/pypi/stackfull https://github.com/jsbueno/stackfull
Clever.
What happens if expensive_computation() raises? Does the push get ignored? Capture the exception, push it, and re-raise, so the next pop will raise the same exception?
As it is today, the operators there are just normal functions (I had some distant plans of fiddling with Pymacro one of these days to see if they could be something different). So, if "expensive_computation" raises, push will never be called.
Is there any reason this has to be local? If it were, say, a thread-local global, it could be used to wedge additional arguments through functions that weren't expecting them. (Together with a "mark" function of some kind you could even use this to experiment with alternate calling conventions, etc.)
There is no special reason for it to be local, other than 'a simple implementation' that would allow me to have the a value of "expensive_computation()" I could re-use in the same expression.
I still don't see how this fills a gap that needs to be filled; how is the first version any more readable, writable, concise, whatever than the second?
if push(expensive()): x = pop() dostuff(x)
x = expensive() if x: dostuff(x)
When I wrote it, my need was actually for a pattern in the inline "if" operation: x = pop() if complex_comparison(push(expensive_operation())) else None clear() (again, "complex_comparison" need just not to be that comples, just something that has to check the value of "expensive()" for anything other than boolean true/falsiness) Though I recognize the pattern gets in the way of readability, since in this case the pop is placed before the "push" . But if this gets any traction (even in my personal projects), maybe writing: x = None if not complex_comparison(push(expensive_operation())) else pop() might be more recommended. As I said, it is more or less a toy project, although I had used it "for real" in some places - but it does fill a gap I perceive in Python syntax, which is the one discussed in this thread. And I am all open to expand "stackfull" into something else, like, for example, "even use this to experiment with alternate calling conventions, etc" :-) js -><-
On Tue, Feb 18, 2014 at 9:59 AM, Joao S. O. Bueno <jsbueno@python.org.br>
As it is today, the operators there are just normal functions (I had some distant plans of fiddling with Pymacro one of these days to see if they could be something different). So, if "expensive_computation" raises, push will never be called.
Which is also the semantics I would expect based on a purely conceptual reading of the theory. Keep it like that :)
x = pop() if complex_comparison(push(expensive_operation())) else None clear()
(again, "complex_comparison" need just not to be that comples, just something that has to check the value of "expensive()" for anything other than boolean true/falsiness)
How about: x = complex_comparison(push(expensive_operation())) and pop() That'll leave you with some falsy value, rather than specifically None, but if you know expensive_operation will return something nonzero, that could work. ChrisA
participants (13)
-
Andrew Barnert -
Chris Angelico -
Ethan Furman -
Jelle Zijlstra -
Joao S. O. Bueno -
Mathias Panzenböck -
Michele Lacchia -
Nathan Schneider -
Nick Coghlan -
Ram Rachum -
spir -
Steven D'Aprano -
Terry Reedy