Ideas about how software should behave (was: replacing `else` with `then` in `for` and `try`)

Ian Kelly ian.g.kelly at gmail.com
Tue Nov 7 16:16:49 EST 2017


On Tue, Nov 7, 2017 at 12:10 PM, Chris Angelico <rosuav at gmail.com> wrote:
> On Wed, Nov 8, 2017 at 4:28 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
>> On Sat, Nov 4, 2017 at 6:40 AM, Chris Angelico <rosuav at gmail.com> wrote:
>>> Maybe we're not defending the abuse of other contributors. Maybe we're
>>> defending a legitimate, if somewhat caustic, response to a ridiculous
>>> suggestion.
>>
>> I don't think it was a ridiculous suggestion.
>>
>> Assigment to False is a syntax error, even though it's lexically valid
>> and was accepted in the past.
>
> Assignment to None was and is a syntax error, and assignment to False
> was legal only for backward compatibility within the 2.x line. I'm not
> sure what your point is. None, False, and True are all keywords, not
> built-ins, so you can't assign to them (any more than you could assign
> to a literal integer).

That's a false equivalence. There is nothing about None, False or True
that *requires* them to be keywords, and my point is that in Python 2,
two out of the three were *not* keywords. Making them keywords was a
backward-incompatible change and entirely unnecessary, but it was done
because it was deemed to be worthwhile.

>> Inconsistent indentation is a syntax error, even though it could be
>> parsed and has been in the past.
>
> I'm not sure what you mean by "inconsistent" here, unless it's that
> tabs and spaces had a declared equivalency that they now don't.

Yes.

> Unindenting to a level you've never used has always been an error;
> being sloppy has never been:
>
> if 1:
>     if 2:
>         pass
>   pass # always an error
>     if 3:
>      pass # never an error
>
> Again, though, I'm not sure what your point is. Are you saying that it
> was ridiculous (or called ridiculous) to separate tabs and spaces
> rather than treat them as equivalent? Or are you saying that it ought
> to be legal?

No, my point is not about indentation. I listed these things as
examples of useful syntax errors that are not unlike the for-else
syntax error suggestion. In this case, the change to treat tabs and
spaces separately for indentation was a good one, albeit backward
incompatible, and the suggestion to disallow for-else without break is
likewise a good one, albeit backward incompatible.

>> Wildcard imports inside a function are a syntax error, even though
>> it's lexically valid and mostly harmless.
>
> Mostly harmless? Hmm. Okay:
>
> def func1():
>     spam = 1
>     def func2():
>         from module import *
>         def func3():
>             nonlocal spam
>             spam += 1
>
> What does func3's spam refer to?

In my opinion, it should refer to the variable from func1, since the
star import can't be assumed to introduce a "spam" variable, and it
doesn't make sense for the meaning of the code (and the generated byte
code content) to depend on whether it does.

> I don't know for sure if that's why star imports got banned, but I
> also don't know for sure what should happen in the above situation
> either.

I think it's more likely because fast locals don't support dynamic
modification, the same reason why a call to exec() from a function
body can't modify the local variables either. It's not that this is
technically infeasible; it's just unsupported.

>> Using "yield from" inside an async coroutine is a syntax error, even
>> though it's lexically valid and "await" and "yield from" are nearly
>> identical.
>
> I'm not sure about this one, but the equivalence of await and yield
> from is a red herring. The part that's more surprising is this:
>
>>>> async def foo():
> ...        yield from [1,2,3]
> ...
>   File "<stdin>", line 2
> SyntaxError: 'yield from' inside async function
>>>> async def foo():
> ...        for _ in [1,2,3]: yield _
> ...
>
> (Yes, I know "yield from" does a lot more than "for... yield" does)
>
> But in comparison to "for... break", this is definitely not an error
> on the basis that it "makes no sense". It's more likely to be an error
> because of some internal limitation, in the same way that async
> coroutines weren't possible _at all_ initially:

No, it isn't. They share implementation. The only difference between
the two apart from the syntactic restrictions is that await validates
the type of its argument and looks for a method called __await__
instead of __iter__; see PEP 492.

I suspect that the reason for the restriction was to reserve lexical
space for asynchronous generators to be supported in the future,
although PEP 525 only permits yield and not yield from, having
explicitly deferred yield from as "less critical" and "requiring
serious redesign".

>> I haven't seen any argument against making "else" without "break" a
>> syntax error that wouldn't also apply to the above, with the exception
>> of Steve's manufactured interactive example ("manufactured" because
>> who really uses for-else interactively? If I really care that much
>> about output formatting I'm going to put it in a script). If there is
>> any extant code that would actually be broken by this, it's very
>> likely buggy.
>
> There are many MANY constructs that are broadly useless.
>
> Global declaration without assignment:
>>>> def foo():
> ...     global print
> ...     print("hello")
> ...
>
> Unused variable (often indicates a misspelling):
>>>> def bar():
> ...     x = 1
> ...     return y
> ...
>
> Never-executed bodies:
>>>> def spam():
> ...     if False: print("ham")
> ...     while False: print("ham")
> ...     for _ in []: print("ham")
> ...     try: pass
> ...     except ZeroDivisionError: print("ham")
> ...
>
> (In CPython 3.7, the optimizer will eliminate the first two, but not
> the for or try. That could change, of course.)

All of these are things that a linter should probably catch and warn
about. If you had said that the break syntax suggestion was a good
idea but probably better suited as a linter warning than as a
SyntaxError integrated into the parser, then I would likely agree with
you. That's not what you said, though. You said the suggestion was
"ridiculous".


> Not one of these is syntactically invalid. Why should "else without
> break" be trapped by the parser? Your other examples mostly have good
> parser-level reasons for being errors

No, they don't. All four of them could just as easily also be accepted
by the parser and only flagged as linter warnings.



More information about the Python-list mailing list