Python: Even a Feature That You Do Not Use Can Bite You
blog.petrzemek.net
blog.petrzemek.net
In this case you hit the most benign sort of change: Code that was previously syntactically invalid is now syntactically valid but has silly semantics. There’s never going to be a way to avoid this with full generality, and your code never worked — type annotations just delayed your runtime error by one line.
When annotations were introduced in 3.4, there was no requirement they be used to annotate types instead of something else, so any valid Python expression is allowed syntactically. In 3.6 the use of annotations for anything other than types was provisionally deprecated, and in 3.7 is fully deprecated. In 3.8, non-type usage will be disallowed as a TypeError, and the only allowable non-type annotations will be strings, (which will be interpreted as bare identifiers for backward compatibility with pre-3.7 code that uses them as forward references). At this point your code will revert to raising an exception on exactly the same line it used to.
> type annotations just delayed your runtime error by one line.
Or it introduces a subtle bug because this was intended as a reassignment or somesuch.
You’re right I’m being uncharitable by saying “understanding”. I’m annoyed he bothered writing an accusatory blog post about this aspect of the language without mentioning any of the alternative syntax proposals helpfully discussed in the PEP, or exploring why annotations work this way. The designers have been very transparent and conservative in considering different ways of doing it, and the OP doesn’t offer any ideas for improving the interpreter, or note that the behavior he’s complaining about will be fixed in 3.8.
The problem is that there's not really such a thing as static analysis in python. For example, if I wrap it in a function like this, mypy doesn't complain at all.
def blah():
ages = {}
ages['John']: 42
print(ages['John'])
return ages from typing import Any
def blah() -> Any:
...
I'm not sure what you mean by "there's not really such a thing as static analysis" in Python -- that's exactly what Mypy does.For what it's worth, I think Python's approach here is the right one (at least, for Python.) As others have noted, the common answer to this among Python users is to just use a linter to catch these kinds of issues, although I find that unsatisfying- linters can't catch everything, and they require you to have your stuff together well enough to use them (e.g., you can't be a student who is already overwhelmed with the bare necessary stack.) There are things that seem like they could be done to alleviate these kinds of problems within the language itself; most obviously, for the particular problem in the OP, a variable annotation expression could require the right-hand to be a type expression. It's not clear to me why these kinds of moves aren't taken.
Because when annotations were introduced in PEP 3107 they were not meant exclusively for type hinting. We're in a period now where non-type usage is deprecated but allowed for compatibility with old code. In 3.8 a non-type evaluation of the RHS will be disallowed.
Regarding type hints' ability to be arbitrary code, I suspect this is to allow you to use nested type hints from the `typing` library, like `Tuple[int, int]`, as well as more complex or user-defined types. Plus it just gives more flexibility in general.
I'd be interested to hear the author's suggestions on alternative syntax or ways to catch when the programmer intended something other than a type hint, or even arguments that type hints are extraneous and should not have been added - personally, I've found them quite useful recently, in combination with pylint.
A common response to them was "you don't have to use it; you can ignore it and you're no worse off than you were before". I think it's reasonable for him to point out that this isn't quite true.
But I'll admit I got bit by exactly this recently, wrote up an "assignment" using a colon instead of an equal sign and it didn't do anything, but didn't fail either.
Somewhat frustratingly, pycharm didn't warn about it either. I should probably check their bug tracker to see if somebody already reported it.
In fact, the type hints of the typing module are not actually types.
What I wanted when I found the PEP a few days ago were docstrings for instance variables, which isn't one of the things that this provides, so I stopped reading. I ended up using properties but it's a bit verbose.
It's great that typings are in a module so it can be extended or replaced!
Poly/ML 5.6 Release
> 5 : int;
val it = 5: int
But you cannot just use 42 where a type is expected: > 5 : 42;
poly: : error: <identifier> expected but 42 was found
Static Errors
It's pretty silly that Python did not develop an actual type system if annotations like that are supported.For instance linters not understanding type specificatins beyond a certain complexity but still using them in your code and some custom decorator to check them at run/dev time
Any IDE or linter worth its salt would catch this for you.
I would assume mypy would cause an error.
This is true even in languages that don't undergo syntactic extension. If you call a nonexistent function today, your program bombs. Tomorrow, that function could be added, so now something unexpected happens, perhaps.
Lisps are susceptible. Today (foo bar) is diagnosed; tomorrow it's new syntax (not simply a new function), because foo exists as a macro operator.
However, functions and macros, being named, can be namespaced to curtail such issues. Random, ad hoc read syntax extensions, not so much.
One approach is that files, or sections of files, can support annotations which indicate what version of the syntax is required.
Under GCC, if you want only C89, so that any syntactic extensions from later dialects are invalid syntax, you use '-ansi' on the command line to select that dialect. For oinstance, support for // comments disappears, and so x //blah/ y is x / y, rather than x followed by a comment-to-end-of-line.
Once a coworker came to me with a program that was unexpectedly failing, and it turned out that somewhere in the code there was a function that takes a callable, but instead of
something(iterable, func)
he had written something(iterable, func())
There were no IDE errors/warning (in fact PyCharm tends to induce this error by auto inserting the parens) and the behavior that resulted made it tricky to catch.Nonetheless, personally I’d take that over most other options for run of the mill progamming tasks.
This would also not be accepted by mypy.
It's really just a syntax change. I probably wouldn't have chosen that particular syntax using colons, or I would have forced another keyword like "set" in front of it, but it is what it is. He created a bug and he likely won't do it again, so problem solved.
>>> [][0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
>>> [][0] : "bug"
>>>PS: wait until he reads about := assignement syntax