[Python-ideas] PEP 484 (Type Hints) -- second draft
Guido van Rossum
guido at python.org
Tue Mar 24 00:13:57 CET 2015
(This one took longer to respond to because there are so many nits. :-)
On Sat, Mar 21, 2015 at 8:37 AM, Steven D'Aprano <steve at pearwood.info>
wrote:
> 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?
>
Yes (see other messages in the thread).
> > 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.
>
I only want this special treatment for None. The PEP seems to be pretty
clear about this.
> [...]
> > 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.
>
Well, __debug__ tracks -O (__debug__ is false iff -O is given). But a type
checker usually doesn't have the luxury to know whether the code is meant
to run with -O or without it. So it assumes -O is *not* given, i.e.
__debug__ is True.
(You could try to type-check twice, once with -O and once without, but this
idea doesn't really scale when you consider all the other flags that might
be treated this way. And it's not really worth the trouble.)
> [...]
> > 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.
>
That fear ought to be reduced to zero by words in the PEP; have you got any
suggestions? The mere presence of a module-wide comment to disable type
checks might actually *increase* the fear that (absent such a comment) type
checks might be mandatory.
I could imagine other reasons for wanting a file-scoped directive, but I'm
not sure -- there's a lot of discussion in
https://github.com/ambv/typehinting/issues/35, maybe you can make sense of
it.
> > 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
>
The actual initialization might have to happen later, separately in
different branches of an if-statement; or this might be a class variable.
Jukka gave some more reasons for having Undefined in
https://github.com/ambv/typehinting/issues/20
> > 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.
>
It should unconditionally believe the cast. (Reference:
https://github.com/ambv/typehinting/issues/15#issuecomment-69136820)
> 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.
>
Seems you're not alone here. :-) I've opened
https://github.com/ambv/typehinting/issues/63
> > 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.
>
That has slightly different semantics -- while the type checker should
unconditionally believe cast(), #type: comments are required to be
consistent.
In the example from the PEP (find_first_str()) the cas() is required, since
the derived type is object.
> > 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.
>
I'm tracking this at https://github.com/ambv/typehinting/issues/64 now.
--
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20150323/327e52d0/attachment-0001.html>
More information about the Python-ideas
mailing list