
Hello Everyone, If I've done this incorrectly, please let me know so that I can improve/revise. I'm new to the Python community and quite enjoy the more functional features of Python 3, but have I have a peeve about it. I'd like to propose and discuss the following enhancement to Python 3: *Consider the following trivial for-loop:* chars = "abcaaabkjzhbjacvb" seek = {'a','b','c'} count = 0for a in chars: if a in seek: count += 1 Gross. Twice nested for a simple count. *We could refactor the block like so:* chars = "abcaaabkjzhbjacvb" seek = {'a','b','c'} count = 0for a in filter(lambda c: c in seek, chars): count += 1 Which is all well and good, but doesn't quite read like English. It's verbose, too. It also uses advanced concepts new programmers may not understand. *We could do this:* chars = "abcaaabkjzhbjacvb" seek = {'a','b','c'} count = sum([1 for a in chars if a in seek]) However, this changes important semantics by creating an entire new list before summing. Also, adding just one more expression to the most nested block thwarts that refactor. I propose the following enhancement: chars = "abcaaabkjzhbjacvb" seek = {'a','b','c'} count = 0for a in chars if a in seek: count += 1 *What happened there?* I've allowed a condition to follow the "for" construct without a colon or newline between. *To be clear, this remains incorrect:* chars = "abcaaabkjzhbjacvb" seek = {'a','b','c'} count = 0for a in chars # No colon prior to the newline if a in seek: count += 1 *Value proposal:* I assert that the inlined 'if' condition pattern is superior to the alternative refactors. Right now, the way to acquire an invariant in a more nasty loop would be: for a in iterable: if condition: continue But this is messy and not particularly Pythonic. The filter approach uses concepts that should not be necessary for this task. The comprehension approach has different, undesirable semantics. *Conclusion:* I wanted to submit my thoughts here before getting too deep into this. Any input would be appreciated! Thanks everyone.

On Fri, Apr 10, 2020 at 5:26 PM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
count = 0 for a in chars: count = count + 1 if a in seek else count Once nested -- if nested == gross, then this is not gross. (However, I prefer the twice nested which I find clear and simple -- not gross.) André Roberge

André Roberge wrote:
Nothing wrong with ``` for a in chars: if a in seek: count += 1 ``` It is readable and does the job. There are other approaches that can do the job too. For instance, ``` len([char for char in chars if char in seek]) ``` So why do we need a new syntax for this case? What does this new syntax so special?

(meant to reply to the thread here so I'll send it again) Thanks everyone for the input. I used a trivial example where the inside is a single, simple expression (which can be refactored in many different, clever ways) but the idea was that more complicated blocks could not be (easily) refactored in similar ways. Additionally, there may be further nested loops inside, each having their own conditions -- creating a twice-nested overhead for each one, rapidly becoming gross. This is "special" because it reduces the nesting overhead of conditioned loops from 2 to 1, which is especially important if you've got several nested loops: for a in A: if cond_a: for b in B: if cond_b: # code block then becomes: for a in A if cond_a: for b in B if cond_b: # code block you could even do: for a, b in zip(A,B) if cond_a and cond_b: # code block Either option I feel is far more suitable than the first example. If you'd like me to use a better demonstrative example, let me know. On Fri, Apr 10, 2020 at 3:56 PM <jdveiga@gmail.com> wrote:

On Sat, Apr 11, 2020 at 7:06 AM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
As a side point, this isn't what most people will think of when you say "loop invariants". This would be better described as a "filtered loop". It's something that's been suggested/requested periodically. You may want to search the archives to see what's been said before. Personally, I would quite like this, but the benefit is fairly marginal, so there's usually a fair amount of pushback. ChrisA

Thanks. How do I go explore the archives? I think the spirit of this is along the following line: for(int i = 0; (cond); i++) ... Is a common way to a express a loop breaking out once a condition is not met, as is true for: while cond But there is no way to express (in terse fashion) a loop in which a condition is true but does not break if it is not satisfied. That's normally expressed by nesting an 'if' statement but by doing this, the 'for' and 'if' appear as separate when really the 'if' could be considered as part of the definition of the loop itself. On Fri, Apr 10, 2020, 4:19 PM Chris Angelico <rosuav@gmail.com> wrote:

On Sat, Apr 11, 2020 at 7:41 AM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
Thanks. How do I go explore the archives?
They're available from the Mailman page linked in the footer: https://mail.python.org/mailman3/lists/python-ideas.python.org/ You might have to do some digging, as the words "filter" and "list" will come up quite a lot, but the results will be quite informative.
Right, I absolutely agree. Logically, you're combining a loop with a condition. In a comprehension, that can be done easily and cleanly; but in the statement form, it requires an additional indentation level. One neat trick, though, is to invert the condition. If you have a lot of code that would be guarded by "if cond:", you can instead write "if not cond: continue" and then proceed with the code from there. The main block of code doesn't get indented inside the 'if' (because it isn't inside it), but it's still guarded by the same condition (since the loop body will be skipped completely). ChrisA

10.04.20 23:21, Elliott Dehnbostel пише:
Don't listen to anyone. There is only one obvious way to write this in one line: count = +(lambda *a: a[0](*a))((lambda q, r, s, i: r(r, q, s, i, next(i, s))), (lambda r, q, s, i, x: x is not s and (x in seek) + q(q, r, s, i)), [], iter(chars)) It is not even use such non-functional paradigm as a for loop.

I appreciate all your comments and input :-) Since I'm still new-ish to Python (only a few months, but not new to programming in general) I of course am not the best source of a strict technical design for such a feature. I am however wondering how something like this ever "advances" from a little idea to something more material and eventually a feature of the language. Do we all just sit here with our thinking caps on for awhile? 😄 On Fri, Apr 10, 2020 at 6:03 PM Eric Fahlgren <ericfahlgren@gmail.com> wrote:

On Sat, Apr 11, 2020 at 10:11 AM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
I appreciate all your comments and input :-) Since I'm still new-ish to Python (only a few months, but not new to programming in general) I of course am not the best source of a strict technical design for such a feature. I am however wondering how something like this ever "advances" from a little idea to something more material and eventually a feature of the language. Do we all just sit here with our thinking caps on for awhile?
Since this is a frequently-requested feature, you can expect a LOT of discussion. There will be arguments in favour and against, lots and LOTS of comments, probably hundreds of emails. :) If there's reasonable consensus that this is worth moving forward with, then you should be able to find one core developer to be your sponsor, and then you could proceed to write a PEP. If it comes to that, I can help you with technical details. A PEP would gather together all the arguments, all the decisions made (eg if there are multiple competing syntax options), and anything else that needs to be written down. Then the PEP itself gets debated from here to Alpha Centauri and back, and eventually it'll be either accepted or rejected by the Steering Council. Not every proposal follows the same pattern, but in this case, I'm fairly confident that it'll need a PEP (if it goes forward at all). ChrisA

I am all in for that proposal. Let the construction: for element in iterable if condition: loop_body mean: for element in iterable: if not condition: continue loop_body However this construction may be abused (in a good way) to cause the `condition` be evaluated on every loop iteration. And this is exactly what we need for loop invariants. class InvariantError(AssertionError): pass def loop_invariant(condition:bool): if not condition: raise InvariantError return True for element in iterable if loop_invariant(condition): loop_body We are killing 3 birds with 1 stone: 1. We are unifying normal loop syntax with list comprehension syntax. 2. We are adding a syntactic sugar for a very useful feature (filtering). 3. We are adding a means of specifying loop invariants where they belong. To specify invariants on `while` loops we could do something like: while loop_invariant(condition) and running: loop_body so no new syntax is needed. With this syntax, annotatin loops with invariants would be possible without changing indentation. haael

On Fri, Apr 10, 2020 at 11:20:55PM +0100, haael wrote:
Fortuntely, it is already possible to write that in Python today without the negative logic and `continue`. for element in iterable: if condition: loop_body Benefits of the existing syntax: - It uses composition of simple building blocks instead of a specialised syntax that has to be learned. Once you have learned "for loops" and "if statements" you can combine them and pretty much everyone will know what they mean, without having to learn dedicated syntax. - Not ambiguous: there is no question whether the "if" part applies to the entire loop or each iteration. # Apply the if statement to the entire loop. if condition: for element in iterable: loop_body # Apply the if statement to each iteration of the loop. for element in iterable: if condition: loop_body - Backwards compatible all the way back to Python 1.5. - Makes the condition more visible (brings it to the front of the line, instead of hiding it way at the end of the `for`. - If you are paid by the line, you earn more. Cons: - Uses one extra line. - Uses one extra indent. - Uses one extra colon. If the `;` key on your keyboard is broken you will have to copy and paste it from elsewhere. -- Steven

- Backwards compatible all the way back to Python 1.5. What!? It's not so recent as that: Python 1.0.1 (Jul 15 2016) Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

On 11/04/20 10:20 am, haael wrote:
That's a *very* misleading way to check loop invariants, because it makes it look like a filter rather than an invariant check. Also it doesn't quite do a complete job, because it doesn't check the invariant after the last iteration, and doesn't check it at all if the iterable is empty.
while loop_invariant(condition) and running: loop_body
Again misleading, because it looks like part of the loop condition. -- Greg

On Sat, Apr 11, 2020 at 03:19:06PM +1200, Greg Ewing wrote:
Also it doesn't quite do a complete job, because it doesn't check the invariant after the last iteration,
I'm not sure whether loop invariants should be checked at the beginning of the loop, the end of the loop, or both. Anyone know what Eiffel does?
and doesn't check it at all if the iterable is empty.
That is surely okay though. If the loop is never entered at all, by the principle of vacuous truth the condition inside the loop is true. For what it's worth, I agree that loop invariants should look like assertions, not ordinary conditionals. -- Steven

On 11/04/20 5:38 pm, Steven D'Aprano wrote:
That is surely okay though. If the loop is never entered at all, by the principle of vacuous truth the condition inside the loop is true.
No. A loop invariant is something that is true before entering the loop, and remains true after each iteration. This is so that you can assert (loop_invariant and termination_condition) after the loop finishes, even if it executes zero times. One of the reasons people sometimes suggest adding new syntax for asserting loop invariants is that it's currently awkward to do all of this properly without writing the invariant twice. -- Greg

Give me a better one then. I need a way for quickly annotating loops with invariants. The problem with context managers is that it needs indentation change which is definitely not "quick". Imagine I wanted to take code from some public repo and add loop invariants to it. If the diff touches half of the lines of the code, it will never be merged. We can quickly annotate functions with decorators and type hints (both are one-liners). We can quickly specify assertions (one-liner). Now give me one-liner for loop invariants.

Hello, On Sat, 11 Apr 2020 10:24:33 +0100 haael <haael@interia.pl> wrote:
Did you consider implementing (properly) "from __future__ import braces"? It will allow you to write one-liners like: for var in sth { if (cond) do_sth; rest_of_code }
In diff format: for var in sth: + invariant_comma_d_apostrophe_oh(var != kaboom) rest_of_code [] -- Best regards, Paul mailto:pmiscml@gmail.com

On Sat, Apr 11, 2020 at 10:24:33AM +0100, haael wrote:
You have a social problem: you want to annotate for-loops with some sort of unspecified loop invariant, but the maintainers won't accept it if you dump a huge diff in their laps. Okay, but that's between you and the project maintainers, nothing to do with us. -- Steven

On Apr 10, 2020, at 13:29, Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
It sounds like you really are just looking for generator expressions, which were added in Python 2.4 a couple decades ago. They have the same syntax as list comprehensions without the square brackets, but they generate one element at a time instead of generating a new list. If that’s your only problem here, just remove the square brackets and you’re done.
Also, adding just one more expression to the most nested block thwarts that refactor. No it doesn’t:
count = sum(1 for a in chars if a.isalpha() and a in seek) And you don’t have to stop with adding another expression; you can add a whole new if clause, or even a nested for clause: count = sum(1 for s in strings if s.isalpha() for a in s if a in seek) Of course if you add too much, it becomes a lot harder to read—we’re already pushing it here. But in that case, you can always pull out any subexpression into a function: def matches(s): return (a for a in s if a in seek) count = sum(1 for s in strings for a in matches(s)) … which can often then be simplified further (it should be obvious how here). Or, even better, you can usually create a pipeline of two (or more) generator expressions: chars = (a for s in strings for a in s) count = sum(1 for a in chars if a in seek) (And sometimes it’s natural to replace some of these with map, itertools functions, two-arg iter, etc.; sometimes it’s not. Do whichever one is more natural in each case, of course.) The great thing about this style is that you can pipeline a dozen transformations without any nesting, and without adding any confusion. They just pile up linearly and you can read each one on its own. David Beazley’s “Generator Tricks for Systems Programmers” presentation has some great examples, which demonstrate it a lot better than I ever could. And of course you can always fall back to writing nested block statements. The nice declarative syntax of comprehensions is great when it’s simple enough to understand the flow at a glance—but when it isn’t, that usually means you need a visual guide to understand the flow, and that’s exactly what indentation is for. And if there are too many indents, then that usually means you should be refactoring the inner part into a function. Sure, “usually” isn’t “always”, both times. There are some cases that fall maddeningly in between, where a sort of hybrid where you had nested blocks but could collapse a couple together here and there would be the most readable possibility. I know I run into one a couple times/year. But I don’t think those cases are really much more common than that. Any new feature complicates Python—and this one would potentially encourage people to abuse the feature even when it’s really making things less readable rather than more. Comprehension syntax is great _because_ comprehensions are limited. You know you can read things declaratively, because there can’t be any statements in there or any flow control besides the linear-nested clauses. That doesn’t apply to nested blocks.

On Fri, Apr 10, 2020 at 4:27 PM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
I would definitely not write it that way. Instead I would write: for a in chars: count += a in seek Or quite likely simply: count = sum(a in seek for a in chars) Now your trivial example is ... well, trivial. Often we want to do different things if the membership is or isn't satisfied. Sometimes we can simplify like this: stuff = [this if a in seek else that for a in chars]
Neither the inline if nor your proposal deal with actual blocks: for a in chars: if a in seek: do_this(a) and_that(a) else: other_stuff(a) still_more(a) But neither do we actually need any shorter way to spell that, which is already clear. -- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

What you seem to be discussing is not normally known as a loop invariant. "Loop invariants" are normally understood to be a form of invariant, i.e. something that doesn't change, not something which does change from iteration to iteration. A loop invariant is an assertion about the state of your program which is true at the beginning and end of each loop. https://en.wikipedia.org/wiki/Loop_invariant For example, we might make a trivial loop invariant like this: vowels = "aeiou" for c in vowels: # pointless loop invariant assert c in vowels Invariants are normally assertions, which may be disabled without changing the meaning of the code. I'm sorry, I don't see any benefit to your proposal aside from saving one line and one indent. I don't consider that sufficient benefit to make it worth adding redundant syntax to the language. -- Steven

On Fri, Apr 10, 2020 at 5:26 PM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
count = 0 for a in chars: count = count + 1 if a in seek else count Once nested -- if nested == gross, then this is not gross. (However, I prefer the twice nested which I find clear and simple -- not gross.) André Roberge

André Roberge wrote:
Nothing wrong with ``` for a in chars: if a in seek: count += 1 ``` It is readable and does the job. There are other approaches that can do the job too. For instance, ``` len([char for char in chars if char in seek]) ``` So why do we need a new syntax for this case? What does this new syntax so special?

(meant to reply to the thread here so I'll send it again) Thanks everyone for the input. I used a trivial example where the inside is a single, simple expression (which can be refactored in many different, clever ways) but the idea was that more complicated blocks could not be (easily) refactored in similar ways. Additionally, there may be further nested loops inside, each having their own conditions -- creating a twice-nested overhead for each one, rapidly becoming gross. This is "special" because it reduces the nesting overhead of conditioned loops from 2 to 1, which is especially important if you've got several nested loops: for a in A: if cond_a: for b in B: if cond_b: # code block then becomes: for a in A if cond_a: for b in B if cond_b: # code block you could even do: for a, b in zip(A,B) if cond_a and cond_b: # code block Either option I feel is far more suitable than the first example. If you'd like me to use a better demonstrative example, let me know. On Fri, Apr 10, 2020 at 3:56 PM <jdveiga@gmail.com> wrote:

On Sat, Apr 11, 2020 at 7:06 AM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
As a side point, this isn't what most people will think of when you say "loop invariants". This would be better described as a "filtered loop". It's something that's been suggested/requested periodically. You may want to search the archives to see what's been said before. Personally, I would quite like this, but the benefit is fairly marginal, so there's usually a fair amount of pushback. ChrisA

Thanks. How do I go explore the archives? I think the spirit of this is along the following line: for(int i = 0; (cond); i++) ... Is a common way to a express a loop breaking out once a condition is not met, as is true for: while cond But there is no way to express (in terse fashion) a loop in which a condition is true but does not break if it is not satisfied. That's normally expressed by nesting an 'if' statement but by doing this, the 'for' and 'if' appear as separate when really the 'if' could be considered as part of the definition of the loop itself. On Fri, Apr 10, 2020, 4:19 PM Chris Angelico <rosuav@gmail.com> wrote:

On Sat, Apr 11, 2020 at 7:41 AM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
Thanks. How do I go explore the archives?
They're available from the Mailman page linked in the footer: https://mail.python.org/mailman3/lists/python-ideas.python.org/ You might have to do some digging, as the words "filter" and "list" will come up quite a lot, but the results will be quite informative.
Right, I absolutely agree. Logically, you're combining a loop with a condition. In a comprehension, that can be done easily and cleanly; but in the statement form, it requires an additional indentation level. One neat trick, though, is to invert the condition. If you have a lot of code that would be guarded by "if cond:", you can instead write "if not cond: continue" and then proceed with the code from there. The main block of code doesn't get indented inside the 'if' (because it isn't inside it), but it's still guarded by the same condition (since the loop body will be skipped completely). ChrisA

10.04.20 23:21, Elliott Dehnbostel пише:
Don't listen to anyone. There is only one obvious way to write this in one line: count = +(lambda *a: a[0](*a))((lambda q, r, s, i: r(r, q, s, i, next(i, s))), (lambda r, q, s, i, x: x is not s and (x in seek) + q(q, r, s, i)), [], iter(chars)) It is not even use such non-functional paradigm as a for loop.

I appreciate all your comments and input :-) Since I'm still new-ish to Python (only a few months, but not new to programming in general) I of course am not the best source of a strict technical design for such a feature. I am however wondering how something like this ever "advances" from a little idea to something more material and eventually a feature of the language. Do we all just sit here with our thinking caps on for awhile? 😄 On Fri, Apr 10, 2020 at 6:03 PM Eric Fahlgren <ericfahlgren@gmail.com> wrote:

On Sat, Apr 11, 2020 at 10:11 AM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
I appreciate all your comments and input :-) Since I'm still new-ish to Python (only a few months, but not new to programming in general) I of course am not the best source of a strict technical design for such a feature. I am however wondering how something like this ever "advances" from a little idea to something more material and eventually a feature of the language. Do we all just sit here with our thinking caps on for awhile?
Since this is a frequently-requested feature, you can expect a LOT of discussion. There will be arguments in favour and against, lots and LOTS of comments, probably hundreds of emails. :) If there's reasonable consensus that this is worth moving forward with, then you should be able to find one core developer to be your sponsor, and then you could proceed to write a PEP. If it comes to that, I can help you with technical details. A PEP would gather together all the arguments, all the decisions made (eg if there are multiple competing syntax options), and anything else that needs to be written down. Then the PEP itself gets debated from here to Alpha Centauri and back, and eventually it'll be either accepted or rejected by the Steering Council. Not every proposal follows the same pattern, but in this case, I'm fairly confident that it'll need a PEP (if it goes forward at all). ChrisA

I am all in for that proposal. Let the construction: for element in iterable if condition: loop_body mean: for element in iterable: if not condition: continue loop_body However this construction may be abused (in a good way) to cause the `condition` be evaluated on every loop iteration. And this is exactly what we need for loop invariants. class InvariantError(AssertionError): pass def loop_invariant(condition:bool): if not condition: raise InvariantError return True for element in iterable if loop_invariant(condition): loop_body We are killing 3 birds with 1 stone: 1. We are unifying normal loop syntax with list comprehension syntax. 2. We are adding a syntactic sugar for a very useful feature (filtering). 3. We are adding a means of specifying loop invariants where they belong. To specify invariants on `while` loops we could do something like: while loop_invariant(condition) and running: loop_body so no new syntax is needed. With this syntax, annotatin loops with invariants would be possible without changing indentation. haael

On Fri, Apr 10, 2020 at 11:20:55PM +0100, haael wrote:
Fortuntely, it is already possible to write that in Python today without the negative logic and `continue`. for element in iterable: if condition: loop_body Benefits of the existing syntax: - It uses composition of simple building blocks instead of a specialised syntax that has to be learned. Once you have learned "for loops" and "if statements" you can combine them and pretty much everyone will know what they mean, without having to learn dedicated syntax. - Not ambiguous: there is no question whether the "if" part applies to the entire loop or each iteration. # Apply the if statement to the entire loop. if condition: for element in iterable: loop_body # Apply the if statement to each iteration of the loop. for element in iterable: if condition: loop_body - Backwards compatible all the way back to Python 1.5. - Makes the condition more visible (brings it to the front of the line, instead of hiding it way at the end of the `for`. - If you are paid by the line, you earn more. Cons: - Uses one extra line. - Uses one extra indent. - Uses one extra colon. If the `;` key on your keyboard is broken you will have to copy and paste it from elsewhere. -- Steven

- Backwards compatible all the way back to Python 1.5. What!? It's not so recent as that: Python 1.0.1 (Jul 15 2016) Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

On 11/04/20 10:20 am, haael wrote:
That's a *very* misleading way to check loop invariants, because it makes it look like a filter rather than an invariant check. Also it doesn't quite do a complete job, because it doesn't check the invariant after the last iteration, and doesn't check it at all if the iterable is empty.
while loop_invariant(condition) and running: loop_body
Again misleading, because it looks like part of the loop condition. -- Greg

On Sat, Apr 11, 2020 at 03:19:06PM +1200, Greg Ewing wrote:
Also it doesn't quite do a complete job, because it doesn't check the invariant after the last iteration,
I'm not sure whether loop invariants should be checked at the beginning of the loop, the end of the loop, or both. Anyone know what Eiffel does?
and doesn't check it at all if the iterable is empty.
That is surely okay though. If the loop is never entered at all, by the principle of vacuous truth the condition inside the loop is true. For what it's worth, I agree that loop invariants should look like assertions, not ordinary conditionals. -- Steven

On 11/04/20 5:38 pm, Steven D'Aprano wrote:
That is surely okay though. If the loop is never entered at all, by the principle of vacuous truth the condition inside the loop is true.
No. A loop invariant is something that is true before entering the loop, and remains true after each iteration. This is so that you can assert (loop_invariant and termination_condition) after the loop finishes, even if it executes zero times. One of the reasons people sometimes suggest adding new syntax for asserting loop invariants is that it's currently awkward to do all of this properly without writing the invariant twice. -- Greg

Give me a better one then. I need a way for quickly annotating loops with invariants. The problem with context managers is that it needs indentation change which is definitely not "quick". Imagine I wanted to take code from some public repo and add loop invariants to it. If the diff touches half of the lines of the code, it will never be merged. We can quickly annotate functions with decorators and type hints (both are one-liners). We can quickly specify assertions (one-liner). Now give me one-liner for loop invariants.

Hello, On Sat, 11 Apr 2020 10:24:33 +0100 haael <haael@interia.pl> wrote:
Did you consider implementing (properly) "from __future__ import braces"? It will allow you to write one-liners like: for var in sth { if (cond) do_sth; rest_of_code }
In diff format: for var in sth: + invariant_comma_d_apostrophe_oh(var != kaboom) rest_of_code [] -- Best regards, Paul mailto:pmiscml@gmail.com

On Sat, Apr 11, 2020 at 10:24:33AM +0100, haael wrote:
You have a social problem: you want to annotate for-loops with some sort of unspecified loop invariant, but the maintainers won't accept it if you dump a huge diff in their laps. Okay, but that's between you and the project maintainers, nothing to do with us. -- Steven

On Apr 10, 2020, at 13:29, Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
It sounds like you really are just looking for generator expressions, which were added in Python 2.4 a couple decades ago. They have the same syntax as list comprehensions without the square brackets, but they generate one element at a time instead of generating a new list. If that’s your only problem here, just remove the square brackets and you’re done.
Also, adding just one more expression to the most nested block thwarts that refactor. No it doesn’t:
count = sum(1 for a in chars if a.isalpha() and a in seek) And you don’t have to stop with adding another expression; you can add a whole new if clause, or even a nested for clause: count = sum(1 for s in strings if s.isalpha() for a in s if a in seek) Of course if you add too much, it becomes a lot harder to read—we’re already pushing it here. But in that case, you can always pull out any subexpression into a function: def matches(s): return (a for a in s if a in seek) count = sum(1 for s in strings for a in matches(s)) … which can often then be simplified further (it should be obvious how here). Or, even better, you can usually create a pipeline of two (or more) generator expressions: chars = (a for s in strings for a in s) count = sum(1 for a in chars if a in seek) (And sometimes it’s natural to replace some of these with map, itertools functions, two-arg iter, etc.; sometimes it’s not. Do whichever one is more natural in each case, of course.) The great thing about this style is that you can pipeline a dozen transformations without any nesting, and without adding any confusion. They just pile up linearly and you can read each one on its own. David Beazley’s “Generator Tricks for Systems Programmers” presentation has some great examples, which demonstrate it a lot better than I ever could. And of course you can always fall back to writing nested block statements. The nice declarative syntax of comprehensions is great when it’s simple enough to understand the flow at a glance—but when it isn’t, that usually means you need a visual guide to understand the flow, and that’s exactly what indentation is for. And if there are too many indents, then that usually means you should be refactoring the inner part into a function. Sure, “usually” isn’t “always”, both times. There are some cases that fall maddeningly in between, where a sort of hybrid where you had nested blocks but could collapse a couple together here and there would be the most readable possibility. I know I run into one a couple times/year. But I don’t think those cases are really much more common than that. Any new feature complicates Python—and this one would potentially encourage people to abuse the feature even when it’s really making things less readable rather than more. Comprehension syntax is great _because_ comprehensions are limited. You know you can read things declaratively, because there can’t be any statements in there or any flow control besides the linear-nested clauses. That doesn’t apply to nested blocks.

On Fri, Apr 10, 2020 at 4:27 PM Elliott Dehnbostel <pydehnbostel@gmail.com> wrote:
I would definitely not write it that way. Instead I would write: for a in chars: count += a in seek Or quite likely simply: count = sum(a in seek for a in chars) Now your trivial example is ... well, trivial. Often we want to do different things if the membership is or isn't satisfied. Sometimes we can simplify like this: stuff = [this if a in seek else that for a in chars]
Neither the inline if nor your proposal deal with actual blocks: for a in chars: if a in seek: do_this(a) and_that(a) else: other_stuff(a) still_more(a) But neither do we actually need any shorter way to spell that, which is already clear. -- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

What you seem to be discussing is not normally known as a loop invariant. "Loop invariants" are normally understood to be a form of invariant, i.e. something that doesn't change, not something which does change from iteration to iteration. A loop invariant is an assertion about the state of your program which is true at the beginning and end of each loop. https://en.wikipedia.org/wiki/Loop_invariant For example, we might make a trivial loop invariant like this: vowels = "aeiou" for c in vowels: # pointless loop invariant assert c in vowels Invariants are normally assertions, which may be disabled without changing the meaning of the code. I'm sorry, I don't see any benefit to your proposal aside from saving one line and one indent. I don't consider that sufficient benefit to make it worth adding redundant syntax to the language. -- Steven
participants (13)
-
Andrew Barnert
-
André Roberge
-
Chris Angelico
-
David Mertz
-
Elliott Dehnbostel
-
Eric Fahlgren
-
Greg Ewing
-
haael
-
jdveiga@gmail.com
-
Paul Sokolovsky
-
Serhiy Storchaka
-
Soni L.
-
Steven D'Aprano