[Python-ideas] PEP 484 (Type Hints) -- second draft

Steven D'Aprano steve at pearwood.info
Sat Mar 21 16:37:32 CET 2015


On Fri, Mar 20, 2015 at 09:59:32AM -0700, Guido van Rossum wrote:

> Union types
> -----------
[...]
> As a shorthand for ``Union[T1, None]`` you can write ``Optional[T1]``;

That only saves three characters. Is it worth it?


> An optional type is also automatically assumed when the default value is
> ``None``, for example::
> 
>   def handle_employee(e: Employee = None): ...

Should that apply to all default values or just None? E.g. if I have

    def spam(s: str = 23): ... 

should that be inferred as Union[str, int] or be flagged as a type 
error? I think that we want a type error here, and it's only None that 
actually should be treated as special. Perhaps that should be made 
explicit in the PEP.


[...]
> For the purposes of type hinting, the type checker assumes ``__debug__``
> is set to ``True``, in other words the ``-O`` command-line option is not
> used while type checking.

I'm afraid I don't understand what you are trying to say here. I would 
have expected that __debug__ and -O and the type checker would be 
independent of each other.


[...]
> To mark portions of the program that should not be covered by type
> hinting, use the following:
> 
> * a ``@no_type_check`` decorator on classes and functions
> 
> * a ``# type: ignore`` comment on arbitrary lines
> 
> .. FIXME: should we have a module-wide comment as well?

I think so, if for no other reason than it will reduce the fear of some 
people that type checks will be mandatory.



> Type Hints on Local and Global Variables
> ========================================
> 
> No first-class syntax support for explicitly marking variables as being
> of a specific type is added by this PEP.  To help with type inference in
> complex cases, a comment of the following format may be used::
> 
>   x = []   # type: List[Employee]
> 
> In the case where type information for a local variable is needed before
> it is declared, an ``Undefined`` placeholder might be used::
> 
>   from typing import Undefined
> 
>   x = Undefined   # type: List[Employee]
>   y = Undefined(int)

How is that better than just bringing forward the variable declaration?

    x = []   # type: List[Employee]
    y = 0



> Casts
> =====
> 
> Occasionally the type checker may need a different kind of hint: the
> programmer may know that an expression is of a more constrained type
> than the type checker infers.  For example::
>
>   from typing import List
> 
>   def find_first_str(a: List[object]) -> str:
>       index = next(i for i, x in enumerate(a) if isinstance(x, str))
>       # We only get here if there's at least one string in a
>       return cast(str, a[index])
> 
> The type checker infers the type ``object`` for ``a[index]``, but we
> know that (if the code gets to that point) it must be a string.  The
> ``cast(t, x)`` call tells the type checker that we are confident that
> the type of ``x`` is ``t``.

Is the type checker supposed to unconditionally believe the cast, or 
only if the cast is more constrained than the infered type (like str and 
object, or bool and int)?

E.g. if the type checker infers int, and the cast says list, I'm not 
entirely sure I would trust the programmer more than the type checker.

My feeling here is that some type checkers will unconditionally trust 
the cast, and some will flag the mismatch, or offer a config option to 
swap between the two, and that will be a feature for type checkers to 
compete on.


I'm also going to bike-shed the order of arguments. It seems to me that 
we want to say:

    cast(x, T)  # pronounced "cast x to T"

rather than Yoda-speak "cast T x to we shall" *wink*. That also matches 
the order of isinstance(obj, type) calls and makes it easier to 
remember.


> At runtime a cast always returns the
> expression unchanged -- it does not check the type, and it does not
> convert or coerce the value.

I'm a little concerned about cast() being a function. I know that it's a 
do-nothing function, but there's still the overhead of the name lookup 
and function call. It saddens me that giving a hint to the type checker 
has a runtime cost, small as it is.

(I know that *technically* annotations have a runtime cost too, but 
they're once-only, at function definition time, not every time you call 
the function.)

Your point below that cast() can be used inside expressions is a valid 
point, so there has to be a cast() function to support those cases, but 
for the example given here where the cast occurs in a return statement, 
wouldn't a type comment do?

    return some_expression  # type: T

hints that some_expression is to be treated as type T, regardless of 
what was infered.


> Casts differ from type comments (see the previous section).  When
> using a type comment, the type checker should still verify that the
> inferred type is consistent with the stated type.  When using a cast,
> the type checker trusts the programmer.  Also, casts can be used in
> expressions, while type comments only apply to assignments.


> Stub Files
> ==========
[...]
> Stub files may use the ``.py`` extension or alternatively may use the
> ``.pyi`` extension.  The latter makes it possible to maintain stub
> files in the same directory as the corresponding real module.

I don't like anything that could cause confusion between stub files and 
actual Python files. If we allow .py extension on stub files, I'm sure 
there will be confusing errors where people somehow manage to get the 
stub file imported instead of the actual module they want.

Is there any advantage to allowing stub files use a .py extension? If 
not, then don't allow it.


-- 
Steve


More information about the Python-ideas mailing list