Most active commenters
  • BerislavLopac(5)

←back to thread

317 points est | 12 comments | | HN request time: 0.324s | source | bottom
1. BerislavLopac ◴[] No.17448998[source]
I 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?
replies(5): >>17449022 #>>17449029 #>>17449035 #>>17449036 #>>17452328 #
2. netheril96 ◴[] No.17449022[source]
Does 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?
3. theSage ◴[] No.17449029[source]
Agreed 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?
replies(1): >>17452040 #
4. eesmith ◴[] No.17449035[source]
Historically 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?
replies(1): >>17449124 #
5. keedon ◴[] No.17449036[source]

  for n in [i for i in range(100) if i%2 == 0]:
    print n
Will work (if a bit repetitive looking)
replies(1): >>17449104 #
6. BerislavLopac ◴[] No.17449104[source]
It's not just repetitive; this particular example actually creates a list before starting the external loop -- imagine it with range(100000000) or something. It is better if you replace [] with (), which creates a generator.
replies(1): >>17449881 #
7. BerislavLopac ◴[] No.17449124[source]
How is that ambiguity currently handled in comprehensions?

My point is that it would be nice to have a consistent syntax for all for-loops, either being a part of a comprehension or standing on their own.

EDIT:

> 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):
Not really, I gives me "NameError: name 'n' is not defined". Unless it is an 'n' defined in the outer scope, of course.
replies(1): >>17449276 #
8. eesmith ◴[] No.17449276{3}[source]
"How is that ambiguity currently handled in comprehensions?"

A bit poorly. Compare:

  >>> f(1, 2 for x in )
    File "<stdin>", line 1
      f(1, 2 for x in )
                      ^
  SyntaxError: invalid syntax
  >>> f(1, 2 for x in r)
    File "<stdin>", line 1
  SyntaxError: Generator expression must be parenthesized if not sole argument
See how the first one gives the location of the error while the second does not? As I recall, this is because the first can be generated during parsing, while the second is done after the AST is generated, when the position information is no longer present.

That's why the following:

  >>> f(2 for x in X) + g(1, 2 for y in Y) + h(z**2 for z in Z)
    File "<stdin>", line 1
  SyntaxError: Generator expression must be parenthesized if not sole argument
doesn't tell you which generation expression has the problem.

Yes, I meant that if 'n' is defined in an outer scope. The expression I gave is not a syntax error but a run-time error.

replies(1): >>17456971 #
9. Herald_MJ ◴[] No.17449881{3}[source]
`range()` also takes an optional `step` argument which would help here.
10. BerislavLopac ◴[] No.17452040[source]
Yes, 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).
11. UncleEntity ◴[] No.17452328[source]
I write that all the time and when python complains I usually rewrite it to something like:

for odd_numbers in [n for n in range(100) if n%2]:

after a quick "stupid python" comment.

12. BerislavLopac ◴[] No.17456971{4}[source]
This does not answer my question, so I checked -- comprehensions simply do not accept the `else` clause:

    >>> [a for a in range(10) if True else range(2)]
      File "<stdin>", line 1
        [a for a in range(10) if True else range(2)]
                                         ^
    SyntaxError: invalid syntax
And this is the argument why I can't have my wish, because the standard `for` loops have always accepted `if else`, so it would be a backward incompatible change.

That said, I have another idea: an update to the comprehension syntax which would omitting duplication of variables, using a new "for in" construct. For example, this line:

    (x for x in range(100) if x%2)
...could be written as:

    (x for in range(100) if x%2)
Just an idea... :D