PEP 526 - var annotations and the spirit of python
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Wed Jul 4 11:31:16 EDT 2018
On Wed, 04 Jul 2018 13:48:26 +0100, Bart wrote:
> Presumably one type hint applies for the whole scope of the variable,
> not just the one assignment.
You know how in C you can write
int x = 1; # the type applies for just this one assignment
x = 2.5; # perfectly legal, right?
Wait, no, of course you can't do that. Why would you suggest that as even
a possibility?
Of course the type (whether inferred or annotated) applies for the entire
scope of that variable.
> Which means that here:
>
> x: int = 3
> x = f(x)
>
> you know x should still an int after these two statements, because the
> type hint says so. Without it:
>
> x = 3
> x = f(x)
>
> x could be anything.
That's not how type checking works. It makes *no difference* whether the
type is inferred or hinted. Type hints only exist to cover the cases the
type inference engine can't determine, or determine too strictly. See
below.
In the Dark Ages of type-checking, the compiler was too dumb to work out
for itself what the type of variables is, so you have to explicitly
declare them all, even the most obvious ones. Given such a declaration:
int x = 3; # using C syntax
the type checker is smart enough to look at the next line:
x = f(x);
and complain with a type-error if f() returns (say) a string, or a list.
Checking that the types are compatible is the whole point of type
checking.
Now fast forward to the Enlightenment of type-inference, first used in a
programming language in 1973 (so older than half the programmers alive
today). That purpose doesn't go away because we're using type inference.
With type-inference, the type-checker is smart enough to recognise what
type a variable is supposed to be (at least sometimes):
x = 3; # of course it's an int, what else could it be?
x = f(x);
and likewise complain if f(x) returns something other than an int.
There's no point in type checking if you don't, you know, actually
*check* the types.
With type inference, the only reason to declare a variable's type is if
the type checker can't infer it from the code, or if it infers the wrong
type. (More on this later.)
To do otherwise is as pointless and annoying as those comments which
merely repeat what the code does:
import math # import the math module
mylist.append(v) # append v to mylist
counter += 1 # add 1 to counter
s = s.upper() # convert s to uppercase
x: int = 3 # assign the int 3 to x
Don't be That Guy who writes comments stating the bleeding obvious.
There's not always enough information for the type checker to infer the
right type. Sometimes the information simply isn't there:
x = [] # a list of what?
and sometimes you actually did intend what looks like a type-error to the
checker:
x = 3 # okay, x is intended to be an int
x = "spam" # wait, this can't be right
In the later case, you can annotate the variable with the most general
"any type at all" type:
from typing import Any
x: Any = 3 # x can be anything, but happens to be an int now
x = "spam" # oh that's fine then
or you can simply not check that module. (Type checking is optional, not
mandatory.)
>> A better example would be:
>>
>> x: int = None
>>
>> which ought to be read as "x is an int, or None, and it's currently
>> None".
>
> In that case the type hint is lying.
"Practicality beats purity."
"This type, or None" is such a common pattern that any half-way decent
type checker ought to be able to recognise it. You can, of course,
explicitly annotate it:
x: Optional[int] = None
but the type checker should infer that if you assign None to a variable
which is declared int, you must have meant Optional[int] rather than just
int. If it doesn't, get a better type checker.
Note that None is a special case (because sometimes special cases *are*
special enough to break the rules). This should be an error:
x: int = "hello world" # wait, that's not an int
But this is fine:
x: Union[int, str] = "hello world"
x is permitted to be an int or a string, and it happens to currently be a
string.
> If both x and y have type hints of 'int', then with this:
>
> z = x + y
>
> you might infer that z will be also 'int'
Indeed. If you inferred that z was a list, you're doing it wrong :-)
> (whether it it type hinted or not). But if either of x and y can
> be None, then this might not even execute.
If x or y could be None, the type checker should complain that None does
not support the + operator. The whole point of type checking is to find
bugs like that at compile time. A type checker that doesn't, you know,
*actually find type bugs* is a waste of time and effort.
This is Type-Checking 101 stuff. A type checker which can't even
recognise that None+1 is a type error is a pretty crappy type checker.
Why do you assume that the state of the art in type-checkers in 2018 is
*worse* than the state of the art in 1953 when Fortran came out?
> If something is an int, then make it an int:
>
> x: int = 0
Indeed, that's often the best way, except for the redundant type hint,
which makes you That Guy:
x: int = 0 # set x to the int 0
But the point of my example was that x is not an int. It is an optional
int, that is, an int or None.
--
Steven D'Aprano
"Ever since I learned about confirmation bias, I've been seeing
it everywhere." -- Jon Ronson
More information about the Python-list
mailing list