PEP 484 (Type Hints) -- first draft round
After a long editing process we've got PEP 484
https://www.python.org/dev/peps/pep-0484/ (Type Hints) ready for your
review. This is by no means final, and several areas are either open (to be
resolved in a later draft) or postponed (to a different PEP altogether).
But there's enough meat that I think we can start having the discussion.
Please also see PEP 483 https://www.python.org/dev/peps/pep-0483/ (The
Theory of Type Hint; copied and reformatted from the original Quip document
that I posted just before last Christmas) and PEP 482
https://www.python.org/dev/peps/pep-0482/ (Literature Overview for Type
Hints, by Łukasz). Those are informational PEPs though; the actual spec is
focused in PEP 484 (the only one on the Standards Track).
As I said earlier, I hope to have a rough consensus before PyCon
https://us.pycon.org/2015/ and working code (just the typing.py module
https://github.com/ambv/typehinting/tree/master/prototyping, really)
committed to CPython before the last 3.5 alpha
https://www.python.org/dev/peps/pep-0478/.
Here is the raw text of PEP 484. Fire away!!
PEP: 484
Title: Type Hints
Version: $Revision$
Last-Modified: $Date$
Author: Guido van Rossum
I forgot to mention the tracker! A lot of the recent discussions regarding PEP 484 have actually been held in an issue tracker on GitHub: https://github.com/ambv/typehinting/issues . We intend to keep this tracker around forever, as a collection of artifacts that may help understanding various decisions. We also hope that continued discussion will happen in this tracker (both in existing issues and in new ones -- anyone with a github account can open a new issue). So when should you use the tracker and when should you post to python-ideas? That's really up to you. The tracker is probably a better place to have detailed discussions about type system esoterica; python-ideas is probably a better place to ask for clarification. When in doubt, try python-ideas; and I'd prefer it if the tracker didn't get flooded with new issues about typos and such (you can send those to me directly). Note that the GitHub project also contains the history of the PEP, as well as an experimental typing.py module. The latter is far from finished and hasn't been updated to match the PEP yet, so read it at your own risk. :-) -- --Guido van Rossum (python.org/~guido)
Guido van Rossum schrieb am 16.01.2015 um 18:17:
After a long editing process we've got PEP 484 https://www.python.org/dev/peps/pep-0484/ (Type Hints) ready for your review.
I can't see it mention interoperability with other usages of function annotations. I think the idea was that in this case, the type hints must be explicitly declared as such, e.g. def func(x: {'type': str, 'wobble': 'off', 'ctype': 'std::string[utf8]'}) -> {'type': str, 'wobble': 'sure'}: return x + 'huhu' Stefan
That's discussed in this section:
https://www.python.org/dev/peps/pep-0484/#compatibility-with-other-uses-of-f...
. The dict notation (which was proposed in an earlier thread on
python-ideas) looks too messy to seriously support.
On Fri, Jan 16, 2015 at 11:38 AM, Stefan Behnel
Guido van Rossum schrieb am 16.01.2015 um 18:17:
After a long editing process we've got PEP 484 https://www.python.org/dev/peps/pep-0484/ (Type Hints) ready for your review.
I can't see it mention interoperability with other usages of function annotations. I think the idea was that in this case, the type hints must be explicitly declared as such, e.g.
def func(x: {'type': str, 'wobble': 'off', 'ctype': 'std::string[utf8]'}) -> {'type': str, 'wobble': 'sure'}: return x + 'huhu'
Stefan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
Guido van Rossum schrieb am 16.01.2015 um 20:45:
On Fri, Jan 16, 2015 at 11:38 AM, Stefan Behnel wrote:
Guido van Rossum schrieb am 16.01.2015 um 18:17:
After a long editing process we've got PEP 484 https://www.python.org/dev/peps/pep-0484/ (Type Hints) ready for your review.
I can't see it mention interoperability with other usages of function annotations. I think the idea was that in this case, the type hints must be explicitly declared as such, e.g.
def func(x: {'type': str, 'wobble': 'off', 'ctype': 'std::string[utf8]'}) -> {'type': str, 'wobble': 'sure'}: return x + 'huhu'
That's discussed in this section: https://www.python.org/dev/peps/pep-0484/#compatibility-with-other-uses-of-f... . The dict notation (which was proposed in an earlier thread on python-ideas) looks too messy to seriously support.
So, what you're saying is: either Python type hints are used exclusively, and no other annotations, or the user must add magic comments in all places where annotations are used, so that each tool that processes a subset of them knows which to process and which to ignore. And mixing them in one function will still be completely impossible. I don't see how that's less messy than explicit naming of the type of annotation. It's just less generally usable. Also, how would you support different type systems, such as C/C++ in Cython or Java types in Jython? In both cases, "int" or "float" would mean something different than the Python types "int" and "float", for example. Stefan
Yes, the PEP tries to gently push an agenda where annotations are only used
for Python type hints. Note that this was the original idea when PEP 3107
was created, even if it doesn't say so.
Jim Baker from the Jython team is totally on board with the proposal, he
just wants to have a way to declare that e.g. java.Util.ArrayList is
equivalent to typing.List. This can be done through a stub file. Read
https://github.com/ambv/typehinting/issues/7 for details. Also note that
Jim co-write a paper on the use of gradual typing for Jython, referenced in
PEP 482 (the literature overview) -- see
https://www.python.org/dev/peps/pep-0482/#reticulated-python.
I don't know what to answer for Cython -- doesn't it have its own syntax
for declaring C types? Or is it now also using annotations? If Cython uses
annotations with different meanings, perhaps for Cython files the Python
annotations could be put in stub files. Stub files take precedence over
regular modules when the static checker runs. (Note that the PEP doesn't
yet describe stubs, simply because we ran out of time this week to write it
up. They are implemented by mypy. See
https://github.com/ambv/typehinting/issues/22 .
On Fri, Jan 16, 2015 at 11:56 AM, Stefan Behnel
Guido van Rossum schrieb am 16.01.2015 um 20:45:
On Fri, Jan 16, 2015 at 11:38 AM, Stefan Behnel wrote:
Guido van Rossum schrieb am 16.01.2015 um 18:17:
After a long editing process we've got PEP 484 https://www.python.org/dev/peps/pep-0484/ (Type Hints) ready for your review.
I can't see it mention interoperability with other usages of function annotations. I think the idea was that in this case, the type hints must be explicitly declared as such, e.g.
def func(x: {'type': str, 'wobble': 'off', 'ctype': 'std::string[utf8]'}) -> {'type': str, 'wobble': 'sure'}: return x + 'huhu'
That's discussed in this section:
https://www.python.org/dev/peps/pep-0484/#compatibility-with-other-uses-of-f...
. The dict notation (which was proposed in an earlier thread on python-ideas) looks too messy to seriously support.
So, what you're saying is: either Python type hints are used exclusively, and no other annotations, or the user must add magic comments in all places where annotations are used, so that each tool that processes a subset of them knows which to process and which to ignore. And mixing them in one function will still be completely impossible.
I don't see how that's less messy than explicit naming of the type of annotation. It's just less generally usable.
Also, how would you support different type systems, such as C/C++ in Cython or Java types in Jython? In both cases, "int" or "float" would mean something different than the Python types "int" and "float", for example.
Stefan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations. The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype"). If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x=cython.int, y=cython.p_char)". OTOH, Cython could easily understand the proposed type declarations and apply runtime type checks for them to compiled Python code. Not sure if that would be considered an interesting feature, though. Stefan
Stefan Behnel schrieb am 17.01.2015 um 10:43:
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations.
The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype").
If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x=cython.int, y=cython.p_char)".
BTW, I think namespaces naturally fix this issue. If all of the proposed type system lives either in the builtin namespace or in the "typing" module, then annotations and type hints from any other module namespace MUST simply be ignored by tools that don't understand them, and are also easy to ignore. Then you could write "x: typing.List[cython.int]", and static type checkers could still validate that all input for "x" is a list (of some unknown item type), and Cython could enforce that anything coming out of such a list at runtime can coerce to a C "int". Stefan
Stefan Behnel schrieb am 17.01.2015 um 10:58:
Stefan Behnel schrieb am 17.01.2015 um 10:43:
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations.
The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype").
If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x=cython.int, y=cython.p_char)".
BTW, I think namespaces naturally fix this issue. If all of the proposed type system lives either in the builtin namespace or in the "typing" module, then annotations and type hints from any other module namespace MUST simply be ignored by tools that don't understand them, and are also easy to ignore. Then you could write "x: typing.List[cython.int]", and static type checkers could still validate that all input for "x" is a list (of some unknown item type), and Cython could enforce that anything coming out of such a list at runtime can coerce to a C "int".
On 01/17/2015 01:58 AM, Stefan Behnel wrote:
Stefan Behnel schrieb am 17.01.2015 um 10:43:
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations.
The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype").
If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x=cython.int, y=cython.p_char)".
BTW, I think namespaces naturally fix this issue. If all of the proposed type system lives either in the builtin namespace or in the "typing" module, then annotations and type hints from any other module namespace MUST simply be ignored by tools that don't understand them, and are also easy to ignore. Then you could write "x: typing.List[cython.int]", and static type checkers could still validate that all input for "x" is a list (of some unknown item type), and Cython could enforce that anything coming out of such a list at runtime can coerce to a C "int".
+1 -- ~Ethan~
On Sat, Jan 17, 2015 at 1:58 AM, Stefan Behnel
BTW, I think namespaces naturally fix this issue. If all of the proposed type system lives either in the builtin namespace or in the "typing" module, then annotations and type hints from any other module namespace MUST simply be ignored by tools that don't understand them, and are also easy to ignore. Then you could write "x: typing.List[cython.int]", and static type checkers could still validate that all input for "x" is a list (of some unknown item type), and Cython could enforce that anything coming out of such a list at runtime can coerce to a C "int".
As I responded to your previous message, I think this can indeed be solved very elegantly by providing a stub module for cython that equates cython.int with typing.Any. -- --Guido van Rossum (python.org/~guido)
On Sat, Jan 17, 2015 at 1:43 AM, Stefan Behnel
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations.
The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype").
Actually, while the PEP is still lacking in clarity and we're still having some discussions, type variables will most likely be "invariant" by default, which I think is the "exact types" notion you are after. There's lots of discussion in https://github.com/ambv/typehinting/issues/2 (you might want to read this from the end :-).
If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x=cython.int , y=cython.p_char)".
Perhaps you could supply a stub for the cython module that declares everything in it as type 'Any'? That should shut the type checker up.
OTOH, Cython could easily understand the proposed type declarations and apply runtime type checks for them to compiled Python code. Not sure if that would be considered an interesting feature, though.
You should ask Cython's users what they think. For me personally, it has taken me a long time to make peace with the idea that the static type checker runs as a separate program, like a linter, and has no effect on the runtime behavior of the program. But now that I have made that peace, I feel it's easier to make progress. I no longer feel the pressure to design a type system that can describe everything Python can do (which is impossible). It's enough to design a type system that can describe most of what most programmers do with Python most of the time (when they're not feeling too hacky). As long as there's an escape to say "don't worry about this part," such a type checker can be a very useful tool (like a linter, but much better; it can also be used in an IDE). (And in fact there are several escape hatches. You could choose not to run the checker at all, or you could ignore some or all of its advice. Or you can leave out annotations from parts of your program. Or you can use an explicit 'Any' annotation. ('Any' is not the same as 'object'!) Or you can use a decorator or a magic comment to disable checking of code that uses annotations for other purposes.) -- --Guido van Rossum (python.org/~guido)
Guido van Rossum schrieb am 18.01.2015 um 05:11:
On Sat, Jan 17, 2015 at 1:43 AM, Stefan Behnel wrote:
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations.
The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype").
Actually, while the PEP is still lacking in clarity and we're still having some discussions, type variables will most likely be "invariant" by default, which I think is the "exact types" notion you are after. There's lots of discussion in https://github.com/ambv/typehinting/issues/2 (you might want to read this from the end :-).
No, AFAICT, it's actually not what I meant. The discussion above seems to refer only to the case where a container like "List[X]" may or may not accept subtypes of *X*. For Cython, what matters is whether "list" (or "List[X]") refers to exactly a "list" or allows subtypes of the "list" container type. If the type annotation allows subtypes of builtins, it's useless for Cython, as it does not allow it to generate better code than it does anyway. If it means "exactly list and no subtypes of list", Cython can use it to avoid generating generic fallback code. That's why Cython's current type system enforces exact types when builtin Python types are declared. (You may call it use case optimised: declare only types that help.) And it's one of the reasons why the proposed type system representation is of so little value for Cython. It's simply not intended for anything but type checking. Note that I'm not proposing Cython's semantics for a type system representation that is designed only for type checking, but I guess it would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x=cython.int , y=cython.p_char)".
Perhaps you could supply a stub for the cython module that declares everything in it as type 'Any'? That should shut the type checker up.
Yes, I guess that would allow Cython's extensions to Python's type system to be ignored. We could even try to be a little smarter and map some C parts of Cython's type system down to more generic types in Python's type system (e.g. int32->int). That would at least give us a minimal baseline level of "interoperability" (as in "it doesn't break and isn't completely useless"). Regarding non-typish declarations (like the contrived doc() example in my pull request), will there be a way (maybe in that "stub" mechanism) to tell type analysis tools that whatever they are looking for, they are not going to find it in this specific kind of annotation, as it's "not a type"? That seems sufficiently different (semantically) from "this represents any type". If it's really left to users to do that at a per-annotation basis, it sounds cumbersome enough to make the real intention appear as saying "annotations are for types only". Stefan
On Sun, Jan 18, 2015 at 12:36 PM, Stefan Behnel
Guido van Rossum schrieb am 18.01.2015 um 05:11:
On Sat, Jan 17, 2015 at 1:43 AM, Stefan Behnel wrote:
Guido van Rossum schrieb am 16.01.2015 um 21:08:
I don't know what to answer for Cython -- doesn't it have its own syntax for declaring C types? Or is it now also using annotations?
It can, but it's optional. When I added support for it, I took care not to enable it by default, so that it wouldn't accidentally interfere with other usages of annotations.
The current proposal seems less generous in this regard, and the way I see it, the only gain from Cython's point of view is its very limited support for container item type declarations and function signatures of callable objects. Both are restricted to Python object types and even imply slightly different semantics than Cython (I didn't see a notion of "exact types" as opposed to "type or subtype").
Actually, while the PEP is still lacking in clarity and we're still having some discussions, type variables will most likely be "invariant" by default, which I think is the "exact types" notion you are after. There's lots of discussion in https://github.com/ambv/typehinting/issues/2 (you might want to read this from the end :-).
No, AFAICT, it's actually not what I meant. The discussion above seems to refer only to the case where a container like "List[X]" may or may not accept subtypes of *X*. For Cython, what matters is whether "list" (or "List[X]") refers to exactly a "list" or allows subtypes of the "list" container type. If the type annotation allows subtypes of builtins, it's useless for Cython, as it does not allow it to generate better code than it does anyway. If it means "exactly list and no subtypes of list", Cython can use it to avoid generating generic fallback code.
Can you explain how this works? How does Cython manage to use the knowledge that the argument's __class__ is equal to builtins.list to generate more efficient code? Also, don't you have to insert dynamic checks to ensure the caller passes exactly a list anyway? Very few people subclass builtins.list (usually it's much simpler to subclass collections.abc.MutableSequence). So perhaps Cython could just assume that typing.List means builtins.list and (dynamically) reject calls that pass a subclass of builtins.list?
That's why Cython's current type system enforces exact types when builtin Python types are declared. (You may call it use case optimised: declare only types that help.) And it's one of the reasons why the proposed type system representation is of so little value for Cython. It's simply not intended for anything but type checking.
Note that I'm not proposing Cython's semantics for a type system representation that is designed only for type checking, but I guess it would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
Can you come up with a specific proposal? Maybe we're not as far apart as we think. :-)
If there was at least a notion of "unknown types" that tools like static type checkers MUST ignore (couldn't find that in the PEP either), then Cython users could mix this with Cython's Python level type mapping through the "cython" magic module, e.g. something like "cython.struct(x= cython.int , y=cython.p_char)".
Perhaps you could supply a stub for the cython module that declares everything in it as type 'Any'? That should shut the type checker up.
Yes, I guess that would allow Cython's extensions to Python's type system to be ignored. We could even try to be a little smarter and map some C parts of Cython's type system down to more generic types in Python's type system (e.g. int32->int). That would at least give us a minimal baseline level of "interoperability" (as in "it doesn't break and isn't completely useless").
Right. I still don't understand enough about Cython's type system to be able to make useful suggestions myself -- I am really hoping that you'll take the time to understand PEP 484 well enough so you can help guide it towards something that would benefit Cython without giving up the existing goals (which are similar to what is done e.g. in Hack and Typescript -- see PEP 482).
Regarding non-typish declarations (like the contrived doc() example in my pull request), will there be a way (maybe in that "stub" mechanism) to tell type analysis tools that whatever they are looking for, they are not going to find it in this specific kind of annotation, as it's "not a type"? That seems sufficiently different (semantically) from "this represents any type". If it's really left to users to do that at a per-annotation basis, it sounds cumbersome enough to make the real intention appear as saying "annotations are for types only".
We'll start out with a way using magic comments (maybe # type: OFF|ON) to disable the interpretation of annotations as types by the type checker during specific sections of a module. The checker will then assume no annotations, and in that case it will assume every argument and return value has the special type 'Any'. (Please do read up in PEP 483 about the important difference between Any and object.) We'll also have a decorator to disable type checking per function or class. -- --Guido van Rossum (python.org/~guido)
On 1/18/2015 10:40 PM, Guido van Rossum wrote:
On Sun, Jan 18, 2015 at 12:36 PM, Stefan Behnel
does anyway. If it means "exactly list and no subtypes of list", Cython can use it to avoid generating generic fallback code.
Can you explain how this works? How does Cython manage to use the knowledge that the argument's __class__ is equal to builtins.list to generate more efficient code?
The obvious part is that Cython will directly access the C array of pointers .. and *assume* that each pointer points to a member of the sequence, in order. The less obvious part is how a subclass can break that assumption. Suppose, for instance, one wants a list of pairs, without the space overhead of a concrete tuple object for each pair. Here is the beginning of a list subclass solution (not necessarily the best approach, but a possible one). class PairList(list): def __getitem__(self, i): g = super().__getitem__ return g(2*i), g(2*i+1) pl = PairList([1,2,3,4]) print(pl[0], pl[1])
(1, 2) (3, 4)
For Cython to execute this correctly, it would need the subclass fallback code that would go through the custom .__getitem__. Storing a triangular matrix in a list, with __getitem__ calculating the linear index from an input row, col pair, is another possible example.
Very few people subclass builtins.list ... So perhaps Cython could just assume that typing.List means builtins.list and (dynamically) reject calls that pass a subclass of builtins.list?
Identifying a triangular matrix as a List might not be too useful in any case. ...
would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
Can you come up with a specific proposal? Maybe we're not as far apart as we think. :-)
Is the use of concrete types like list disallowed? --- My memory of the pre-3.0 discussion is that we did not add something like typing then because we did not know how to write it. You suggested (something like) "let people experiment and we can adopt and adapt the best result." Now is the anticipated future. I agree that mypy seems to be the best starting point of something both usable and useful that we are likely to get. It would be nice to have adapted version in an early alpha. I presume you plan to label typing 'experimental', as I believe asyncio was. -- Terry Jan Reedy
On Sun, Jan 18, 2015 at 10:06 PM, Terry Reedy
Storing a triangular matrix in a list, with __getitem__ calculating the linear index from an input row, col pair, is another possible example.
well, I prefer do do this sort of thing with a "has a" relationship, anyway, which would work better. Particularly if you are changing the API of get_item.... So we may be OK.
Very few people subclass builtins.list ... So perhaps Cython could just assume that typing.List means builtins.list and (dynamically) reject calls that pass a subclass of builtins.list?
Identifying a triangular matrix as a List might not be too useful in any case.
exactly. -Chris -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
On Sun, Jan 18, 2015 at 10:06 PM, Terry Reedy
On 1/18/2015 10:40 PM, Guido van Rossum wrote:
On Sun, Jan 18, 2015 at 12:36 PM, Stefan Behnel
does anyway. If it means "exactly list and no subtypes of list",
Cython can use it to avoid generating generic fallback code.
Can you explain how this works? How does Cython manage to use the knowledge that the argument's __class__ is equal to builtins.list to generate more efficient code?
The obvious part is that Cython will directly access the C array of pointers .. and *assume* that each pointer points to a member of the sequence, in order. The less obvious part is how a subclass can break that assumption.
Suppose, for instance, one wants a list of pairs, without the space overhead of a concrete tuple object for each pair. Here is the beginning of a list subclass solution (not necessarily the best approach, but a possible one).
class PairList(list): def __getitem__(self, i): g = super().__getitem__ return g(2*i), g(2*i+1)
pl = PairList([1,2,3,4]) print(pl[0], pl[1])
(1, 2) (3, 4)
For Cython to execute this correctly, it would need the subclass fallback code that would go through the custom .__getitem__.
Storing a triangular matrix in a list, with __getitem__ calculating the linear index from an input row, col pair, is another possible example.
But these are toy examples. For "real" code, you have to override a lot more methods, and the usual implementation approach is not to subclass list but to subclass collections.abc.MutableSequence. Then the problem goes away, in some sense, because MutableSequence isn't compatible with typing.List anyway.
Very few people subclass builtins.list ... So perhaps Cython could just assume that typing.List means builtins.list and (dynamically) reject calls that pass a subclass of builtins.list?
Identifying a triangular matrix as a List might not be too useful in any case. ...
would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
Can you come up with a specific proposal? Maybe we're not as far apart as we think. :-)
Is the use of concrete types like list disallowed?
You mean as an argument type hint? It is allowed, but we're not mutating the builtins.list to allow subscripting as a class, so you could only use e.g. "def foo(a: list) -> list:", you couldn't use list[int]. And it would still mean "allow subclasses".
--- My memory of the pre-3.0 discussion is that we did not add something like typing then because we did not know how to write it. You suggested (something like) "let people experiment and we can adopt and adapt the best result." Now is the anticipated future. I agree that mypy seems to be the best starting point of something both usable and useful that we are likely to get. It would be nice to have adapted version in an early alpha. I presume you plan to label typing 'experimental', as I believe asyncio was.
Yes. (Actually, provisional is the term. But yes.) -- --Guido van Rossum (python.org/~guido)
On 1/18/2015 10:40 PM, Guido van Rossum wrote:
Very few people subclass builtins.list ... So perhaps Cython could just assume that typing.List means builtins.list and (dynamically) reject calls that pass a subclass of builtins.list?
That's actually a reasonable idea. Anything that accepts something as weird as a trianglular matrix implemented as a list subclass will probably accept any kind of sequence, so you might as well type it as Sequence (or whatever the abstract sequence interface is to be called). -- Greg
Greg Ewing schrieb am 19.01.2015 um 21:28:
On 1/18/2015 10:40 PM, Guido van Rossum wrote:
Very few people subclass builtins.list ... So perhaps Cython could just assume that typing.List means builtins.list and (dynamically) reject calls that pass a subclass of builtins.list?
That's actually a reasonable idea. Anything that accepts something as weird as a trianglular matrix implemented as a list subclass will probably accept any kind of sequence, so you might as well type it as Sequence (or whatever the abstract sequence interface is to be called).
Well, that's what Cython currently does, based on its own type declaration semantics for builtin types. I agree that subtyping is rare for lists, but it's quite common for dicts, including defaultdict, OrderedDict, or even YourOwnMissingKeyDictImpl. And there's set and frozenset, so if you type something as "set" (which no-one does, really), it would have to allow frozenset as well in most (read-only) cases (or maybe the other way round). But set and frozenset do not inherit from each other, so instead of typing something as "set", it would have to be the "Set" or "MutableSet" ABC type, which then is uninteresting again for any C level optimisation, as it's way too broad and there's no C-API for it. There's really a visible gap between what's helpful at a Python type checking level and what's a helpful compiler type hint for Cython and CPython. Stefan
Guido van Rossum schrieb am 19.01.2015 um 04:40:
On Sun, Jan 18, 2015 at 12:36 PM, Stefan Behnel wrote:
would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
Can you come up with a specific proposal? Maybe we're not as far apart as we think. :-)
Normally, "x: List[int]" would mean that x is either a list or a subtype of list, where each item is an int or a subtype. To disallow subtypes, you could have something like "exactly(List)[int]" or "List[exactly(int)]" or "exactly(List[int])", which would then make it clear that the exact type matters, only for the List in the first case, only for the item type in the second, and (this is not necessarily required) for all types inside of the "exactly()" call in the last case. The return value of "exactly()" could be some kind of type wrapper object which marks the wrapped type construct as allowing "no subtypes". This would keep the normal case of allowing subtypes everywhere normal and simple, but would allow restricting the type to disallow subtypes in the cases where it seems appropriate. Stefan
What do people think about allowing the annotation to be a function that
returns the type? Is this too complicated to understand/implement?
import typing
def Doc(param_doc, param_type=typing.Any):
#do something with the documentation for the parameter...
return param_type
def print_twice(message: Doc("something to print")):
print(message)
print(message)
def Prime():
return Doc("A prime number", int)
I've done my best to read through the thread/PEP before replying, but
please excuse me if I've missed something and this is a dumb question. If
this is out of the question, perhaps people wishing to encode more
information into function annotations could (ab)use subscripting.
On Wed, Jan 21, 2015 at 2:31 AM, Stefan Behnel
Guido van Rossum schrieb am 19.01.2015 um 04:40:
On Sun, Jan 18, 2015 at 12:36 PM, Stefan Behnel wrote:
would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
Can you come up with a specific proposal? Maybe we're not as far apart as we think. :-)
Normally, "x: List[int]" would mean that x is either a list or a subtype of list, where each item is an int or a subtype. To disallow subtypes, you could have something like "exactly(List)[int]" or "List[exactly(int)]" or "exactly(List[int])", which would then make it clear that the exact type matters, only for the List in the first case, only for the item type in the second, and (this is not necessarily required) for all types inside of the "exactly()" call in the last case. The return value of "exactly()" could be some kind of type wrapper object which marks the wrapped type construct as allowing "no subtypes".
This would keep the normal case of allowing subtypes everywhere normal and simple, but would allow restricting the type to disallow subtypes in the cases where it seems appropriate.
Stefan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Wed, Jan 21, 2015 at 10:38 AM, Matt
What do people think about allowing the annotation to be a function that returns the type? Is this too complicated to understand/implement?
I think that since type hints have to be analyzed at compile time, that execution of arbitrary Python code is a non-starter. Smarter folks than me may well see a way this could happen though. Skip
I think that since type hints have to be analyzed at compile time, that execution of arbitrary Python code is a non-starter.
From the way the abstract is worded, it sounds like arbitrary expressions could be used:
Abstract base classes, types available in the ``types`` module, and user-defined classes may be used as type hints as well. Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined
I had assumed that this means that the annotation is any arbitrary python
expression that evaluates to one of these things. Even if the syntax is
more limited (as I guess it must be), I assume that Union and generics work
not because the static analyzers know anything special about Union, but
that typing.Union is a python class and could have just as easily been
defined outside of the standard library. Even if function calls do not
work, I imagine people can somehow abuse generics to capture documentation
or other things inside type hints.
As an example, let's say a function expects a positive integer. A
programmer could achieve this with an assert statement near the top of the
function, a decorator, or by making users pass in a PositiveInt object
(subclass of int). However, they may prefer to annotate the function as
Type[int, Positive]. Perhaps a better example would be something like
Type[FilePath, exists, readable]
I like the idea of keeping annotation syntax very simple and primarily
about static type checking, but if it's true that people can experiment
with other uses for function annotations without violating the current
syntax, then it sounds like everybody wins.
On Wed, Jan 21, 2015 at 11:45 AM, Skip Montanaro
On Wed, Jan 21, 2015 at 10:38 AM, Matt
wrote: What do people think about allowing the annotation to be a function that returns the type? Is this too complicated to understand/implement?
I think that since type hints have to be analyzed at compile time, that execution of arbitrary Python code is a non-starter. Smarter folks than me may well see a way this could happen though.
Skip
I need to cut this subthread short, but basically, for a successful type
check, the expressions used in annotations *cannot* be arbitrary
expressions. The type checker has a special understanding of things defined
int 'typing', and it knows e.g. what typing.Union means. It supports simple
aliases through assignments, so that you can say e.g.
import typing
U = typing.Union
def foo(U[int, str]) -> str: ...
but it does not understand arbitrary call chaines, so that e.g. this does
*not* work:
import typing
def u(a, b): return typing.Union[a, b]
def foo(u(int, str)) -> str: ...
On Wed, Jan 21, 2015 at 10:27 AM, Matt
I think that since type hints have to be analyzed at compile time,
that execution of arbitrary Python code is a non-starter.
From the way the abstract is worded, it sounds like arbitrary expressions could be used:
Abstract base classes, types available in the ``types`` module, and user-defined classes may be used as type hints as well. Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined
I had assumed that this means that the annotation is any arbitrary python expression that evaluates to one of these things. Even if the syntax is more limited (as I guess it must be), I assume that Union and generics work not because the static analyzers know anything special about Union, but that typing.Union is a python class and could have just as easily been defined outside of the standard library. Even if function calls do not work, I imagine people can somehow abuse generics to capture documentation or other things inside type hints.
As an example, let's say a function expects a positive integer. A programmer could achieve this with an assert statement near the top of the function, a decorator, or by making users pass in a PositiveInt object (subclass of int). However, they may prefer to annotate the function as Type[int, Positive]. Perhaps a better example would be something like Type[FilePath, exists, readable]
I like the idea of keeping annotation syntax very simple and primarily about static type checking, but if it's true that people can experiment with other uses for function annotations without violating the current syntax, then it sounds like everybody wins.
On Wed, Jan 21, 2015 at 11:45 AM, Skip Montanaro
wrote:
On Wed, Jan 21, 2015 at 10:38 AM, Matt
wrote: What do people think about allowing the annotation to be a function that returns the type? Is this too complicated to understand/implement?
I think that since type hints have to be analyzed at compile time, that execution of arbitrary Python code is a non-starter. Smarter folks than me may well see a way this could happen though.
Skip
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
Let's discuss this idea of "exactly" further in the tracker, where I just
created an issue that touches on the same idea:
https://github.com/ambv/typehinting/issues/52
On Tue, Jan 20, 2015 at 11:31 PM, Stefan Behnel
Guido van Rossum schrieb am 19.01.2015 um 04:40:
On Sun, Jan 18, 2015 at 12:36 PM, Stefan Behnel wrote:
would be nice to at least be able to explicitly express somehow that subtypes should be disallowed.
Can you come up with a specific proposal? Maybe we're not as far apart as we think. :-)
Normally, "x: List[int]" would mean that x is either a list or a subtype of list, where each item is an int or a subtype. To disallow subtypes, you could have something like "exactly(List)[int]" or "List[exactly(int)]" or "exactly(List[int])", which would then make it clear that the exact type matters, only for the List in the first case, only for the item type in the second, and (this is not necessarily required) for all types inside of the "exactly()" call in the last case. The return value of "exactly()" could be some kind of type wrapper object which marks the wrapped type construct as allowing "no subtypes".
This would keep the normal case of allowing subtypes everywhere normal and simple, but would allow restricting the type to disallow subtypes in the cases where it seems appropriate.
Stefan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
On Tue, Jan 20, 2015 at 9:09 PM, Greg Ewing
Andrew Barnert wrote:
But I think there's a hole in the premise. Python's key strength is good debug-ability and amazingly powerful introspection. (I mean Python's _two_ key strengths are...) Information that has no runtime meaning to the interpreter may still have meaning to the user who's debugging code,
There's nothing wrong with making the annotations *available* at run time. All I'm saying is that *evaluating* them at run time hinders rather than helping for the intended use.
That's debatable. While evaluating them at run time causes some issues with forward references (mostly for container-ish class definitions), it also catches many mistakes early, such as not importing the referenced classes. Surely if you are reference a class that's not a built-in you have to import it before you can reference in a type hint -- otherwise how would the type checker know where to look for the class definition? FWIW if you really don't want to have them evaluated at run time, you can just put all type hints in string quotes. mypy will treat them as forward references, but AFAIC it doesn't insist that a forward reference contains at least one symbol that is not defined yet at the place where it occurs. :-) I doubt that this style has many things to recommend it though (see previous paragraph). Let me also use this response to explain why I think "::" is a bad idea: - First and foremost, Python usually (an d intentionally!) uses punctuation in a way derived from or similar to long-standing traditions for written text and basic math (with a few going back to old programming conventions, like using * for multiplication). The single colon used for type annotations is an example of this: it is analogous to the way the colon is often used in prose. Switching to a double colon would make it a magical operator -- especially since a single colon in the same position would have a similar but different meaning. Decorator syntax notwithstanding, magical operators are and should remain rare in Python. Eight years ago we picked the winning syntax for argument annotations. It would be tragic if we now were to replace it with this syntactic hack. - I want the type checker to work for earlier versions of Python 3 as well, so that library maintainers (at least for code that doesn't need to support Python 2) can start including type hints in their code without locking that code in to Python 3.5 -- they can use a backport of the (run time) typing package from PyPI, and the type checker can handle earlier Python versions just fine. (All of this already works for mypy.) - What would you do for function return type hints? "-->" or "->>" or "->:" or "->->" all look completely arbitrary. - Looking ahead, if type hints are successful we might want to add a similar notation to declare the type of variables or class attributes, for example "age: int = 99" or perhaps "var age: int = 99". But if we were to use "::" for type hints in function definitions, for consistency we would have to use "age:: int = 99", which would just perpetuate the syntactic hack. -- --Guido van Rossum (python.org/~guido)
Guido van Rossum wrote:
That's debatable. While evaluating them at run time causes some issues with forward references (mostly for container-ish class definitions), it also catches many mistakes early, such as not importing the referenced classes.
Wouldn't the static type checker catch that even earlier? If you reference something in a type expression that you haven't imported, the static checker won't know what it means, so surely it must complain.
Switching to a double colon would make it a magical operator -- especially since a single colon in the same position would have a similar but different meaning.
I don't see how it's any more magical than '==' being a distinct token rather than just two '=' tokens in a row, and 'x == 2' having a different meaning from 'x = 2'.
Eight years ago we picked the winning syntax for argument annotations. It would be tragic if we now were to replace it with this syntactic hack.
I think it's tragic that we seem to have painted ourselves into a corner where we can't give that winning syntax the semantics that would be best for what we've now decided on as the winning use case. How about we just deprecate relying on run-time evaluation of argument annotations? You've pretty much declared that all existing uses of them are deprecated, so it wouldn't cause much more breakage in the long run, and it would give us a way out of the corner in the future if we wanted. -- Greg
On Wed, Jan 21, 2015 at 2:18 PM, Greg Ewing
Guido van Rossum wrote:
That's debatable. While evaluating them at run time causes some issues with forward references (mostly for container-ish class definitions), it also catches many mistakes early, such as not importing the referenced classes.
Wouldn't the static type checker catch that even earlier? If you reference something in a type expression that you haven't imported, the static checker won't know what it means, so surely it must complain.
But the static checker is like a linter and it's common to run those infrequently (e.g. only when submitting a diff for review) while running unittests frequently (e.g. whenever the developer has written some new code). Assuming you have fast unittests.
Switching to a double colon would make it a magical operator --
especially since a single colon in the same position would have a similar but different meaning.
I don't see how it's any more magical than '==' being a distinct token rather than just two '=' tokens in a row, and 'x == 2' having a different meaning from 'x = 2'.
It's different because == is used in nearly all languages in common use (certainly all in the TIOBE top 10) while :: would be a brand new invention here (unrelated to e.g. C++'s :: scoping operator).
Eight years ago we picked the winning syntax for argument annotations. It
would be tragic if we now were to replace it with this syntactic hack.
I think it's tragic that we seem to have painted ourselves into a corner where we can't give that winning syntax the semantics that would be best for what we've now decided on as the winning use case.
To the contrary, I don't think it's tragic at all. It's how most Python evolution happens. mypy wouldn't have happened the way it did if we didn't have annotations: Jukka's original plan was a custom language, which would have made it just one more Ph.D. project without any users.
How about we just deprecate relying on run-time evaluation of argument annotations? You've pretty much declared that all existing uses of them are deprecated, so it wouldn't cause much more breakage in the long run, and it would give us a way out of the corner in the future if we wanted.
I don't see a way to get there from here. Talking about deprecating them is cheap -- actually breaking the code can't happen until 3.6 at the earliest (if we were to issue deprecation warnings by default in 3.5 for every annotation that's not a class or imported from typing), or 3.7 realistically (if in 3.5 we document the deprecation but don't act on it). The provisional status of PEP 484 makes even the more conservative scenario unlikely. And, having coded up much of a possible typing.py module already ( https://github.com/ambv/typehinting/tree/master/prototyping) I really don't see the big problem with run-time evaluation of constraints. -- --Guido van Rossum (python.org/~guido)
On 01/22/2015 11:30 AM, Guido van Rossum wrote:
But the static checker is like a linter and it's common to run those infrequently (e.g. only when submitting a diff for review) while running unittests frequently (e.g. whenever the developer has written some new code). Assuming you have fast unittests.
This still doesn't make sense to me. If your unit tests are any good, they'll contain tests that use the types you're supposed to be importing, so they'll fail if you forget to import them.
And, having coded up much of a possible typing.py module already (https://github.com/ambv/typehinting/tree/master/prototyping) I really don't see the big problem with run-time evaluation of constraints.
Well, having to quote forward-referenced types is a fairly big aesthetic problem for me, even if it's not a practical one. To my mind it's a worse syntactic hack than '::' would be. But it seems you're not bothered by it so much. -- Greg
On Wed, Jan 21, 2015 at 4:04 PM, Greg Ewing
On 01/22/2015 11:30 AM, Guido van Rossum wrote:
But the static checker is like a linter and it's common to run those infrequently (e.g. only when submitting a diff for review) while running unittests frequently (e.g. whenever the developer has written some new code). Assuming you have fast unittests.
This still doesn't make sense to me. If your unit tests are any good, they'll contain tests that use the types you're supposed to be importing, so they'll fail if you forget to import them.
Not necessarily. I've seen plenty of code that takes instances a certain class as argument and calls methods of that type but never constructs a new instance nor does any of the other things for which you need the class name, and where the module defining the class is never imported. Such code works fine, but when you want to declare the type you have to import it.
And, having coded up much of a possible typing.py module already
(https://github.com/ambv/typehinting/tree/master/prototyping) I really don't see the big problem with run-time evaluation of constraints.
Well, having to quote forward-referenced types is a fairly big aesthetic problem for me, even if it's not a practical one. To my mind it's a worse syntactic hack than '::' would be. But it seems you're not bothered by it so much.
You can write a lot of code without ever having to use a forward reference. But "::" would have to be used everywhere. Or perhaps I value the ability to use this with Python 3.4 more than you do. -- --Guido van Rossum (python.org/~guido)
On 01/22/2015 01:50 PM, Guido van Rossum wrote:
I've seen plenty of code that takes instances a certain class as argument and calls methods of that type but never constructs a new instance nor does any of the other things for which you need the class name,
If the only thing the name is needed for is static checking, I don't see what's so bad about not finding out until you actually run the static check. Adding those imports earlier doesn't make the code work any better or show up any more runtime bugs. -- Greg
On Wed, Jan 21, 2015 at 8:25 PM, Greg Ewing
On 01/22/2015 01:50 PM, Guido van Rossum wrote:
I've seen plenty of code that takes instances a certain class as argument and calls methods of that type but never constructs a new instance nor does any of the other things for which you need the class name,
If the only thing the name is needed for is static checking, I don't see what's so bad about not finding out until you actually run the static check. Adding those imports earlier doesn't make the code work any better or show up any more runtime bugs.
Also, if static type checking succeeds, it will be integrated into many IDEs/editors, so you will see type checker errors even before you get to run the tests. Eugene
On Wed, Jan 21, 2015 at 8:39 PM, Eugene Toder
On Wed, Jan 21, 2015 at 8:25 PM, Greg Ewing
wrote: On 01/22/2015 01:50 PM, Guido van Rossum wrote:
I've seen plenty of code that takes instances a certain class as argument and calls methods of that type but never constructs a new instance nor does any of the other things for which you need the class name,
If the only thing the name is needed for is static checking, I don't see what's so bad about not finding out until you actually run the static check. Adding those imports earlier doesn't make the code work any better or show up any more runtime bugs.
Also, if static type checking succeeds, it will be integrated into many IDEs/editors, so you will see type checker errors even before you get to run the tests.
So I think we're all agreed that the current proposal is fine! -- --Guido van Rossum (python.org/~guido)
On Jan 21, 2015, at 11:47 PM, Guido van Rossum
On Wed, Jan 21, 2015 at 8:39 PM, Eugene Toder
wrote: On Wed, Jan 21, 2015 at 8:25 PM, Greg Ewing wrote: On 01/22/2015 01:50 PM, Guido van Rossum wrote: I've seen plenty of code that takes instances a certain class as argument and calls methods of that type but never constructs a new instance nor does any of the other things for which you need the class name, If the only thing the name is needed for is static checking, I don't see what's so bad about not finding out until you actually run the static check. Adding those imports earlier doesn't make the code work any better or show up any more runtime bugs.
Also, if static type checking succeeds, it will be integrated into many IDEs/editors, so you will see type checker errors even before you get to run the tests.
So I think we're all agreed that the current proposal is fine!
*grumble* ;P I know I keep arguing for a slightly different syntax so that its easy to differentiate between type hints and other uses, but I do want to say that I like the proposal. It seems quite practical to me. Would you at least consider adding something to the proposal that makes it clear that a given annotation is a type and not something else? Thanks, Cem Karan
On Thu, Jan 22, 2015 at 10:18 PM, Cem Karan
I know I keep arguing for a slightly different syntax so that its easy to differentiate between type hints and other uses, but I do want to say that I like the proposal. It seems quite practical to me. Would you at least consider adding something to the proposal that makes it clear that a given annotation is a type and not something else?
The absence of a decorator that uses the annotations some other way should make it clear that they're type hints, just as the absence of a "global" statement should make it clear that assigned names are local to a function. ChrisA
On Thu Jan 22 2015 at 12:17:37 Chris Angelico
The absence of a decorator that uses the annotations some other way should make it clear that they're type hints, just as the absence of a "global" statement should make it clear that assigned names are local to a function.
What about code that already exists? What about modules that want to support Python installations that already exist (hence don't have the `typing` module)? What about module authors who simply don't want to make their code ugly with a no-op decorator just to signify that they're not making it ugly with type annotations? Ed Kellett
On Thu, Jan 22, 2015 at 11:32 PM, Ed Kellett
On Thu Jan 22 2015 at 12:17:37 Chris Angelico
wrote: The absence of a decorator that uses the annotations some other way should make it clear that they're type hints, just as the absence of a "global" statement should make it clear that assigned names are local to a function.
What about code that already exists? What about modules that want to support Python installations that already exist (hence don't have the `typing` module)? What about module authors who simply don't want to make their code ugly with a no-op decorator just to signify that they're not making it ugly with type annotations?
If you don't run the type checker, and the annotations will continue to do what they always have done. Seems easy enough. Supporting Python installations that already exist is as simple as either installing a backported typing.py, or not using a feature that didn't exist at that point. Same as the compatibility requirements of using enumerations in earlier versions of Python - you either grab the one off PyPI, or don't use 'em. I'm not seeing a problem here... have I missed something? ChrisA
On Thu Jan 22 2015 at 12:38:17 Chris Angelico
If you don't run the type checker, and the annotations will continue to do what they always have done. Seems easy enough.
Supporting Python installations that already exist is as simple as either installing a backported typing.py, or not using a feature that didn't exist at that point. Same as the compatibility requirements of using enumerations in earlier versions of Python - you either grab the one off PyPI, or don't use 'em. I'm not seeing a problem here... have I missed something?
Put simply, code can't support existing Python installations and future ones at the same time... unless it adds a dependency (is there a dummy typing.py available on PyPI?) or just doesn't use annotations for *anything*. It's kind of like enumerations, except there aren't any proposals threatening to punish you for not using them. Ed Kellett
On Thu Jan 22 2015 at 7:49:38 AM Chris Angelico
On Thu, Jan 22, 2015 at 11:43 PM, Ed Kellett
wrote: It's kind of like enumerations, except there aren't any proposals threatening to punish you for not using them.
What's threatening to punish you for not using annotations?
If you read one of Ed's earlier emails it sounds like he thinks you need the @no_type_hints decorator even if you don't provide *any* annotations, which is incorrect (the decorator is only to signal that annotations you did specify are not type hints; not specifying any annotations signals the same thing). Heck, the decorator isn't even technically necessary if the linter or IDE you are using that can process the type hints chooses to treat unrecognized annotations as Any instead of throwing an error or something.
On Thu Jan 22 2015 at 14:53:56 Brett Cannon
If you read one of Ed's earlier emails it sounds like he thinks you need the @no_type_hints decorator even if you don't provide *any* annotations, which is incorrect (the decorator is only to signal that annotations you did specify are not type hints; not specifying any annotations signals the same thing). Heck, the decorator isn't even technically necessary if the linter or IDE you are using that can process the type hints chooses to treat unrecognized annotations as Any instead of throwing an error or something.
I feel like I've been significantly misunderstood. To be clear, I'm talking about using @no_type_checks or whatever the decorator is called for code I have that *already uses annotations*. Type checking will either fail, or become ubiquitous to the point that passing type checking is a prerequisite to being considered "good" for any Python module. Should the latter happen, I'll have to break my code for past or future Python versions, no? Personally, I'd like to believe that this proposal and the echochamber that's surrounded it has all been just a bad dream, but given that it's almost certainly going to make it into Python at this point, it'd be nice not to have to make good code bad just to maintain compatibility with it. Since type hints already depend on the 'typing' module anyway, what exactly would be wrong with the type checker defaulting off unless it is imported? Ed Kellett
On Fri, Jan 23, 2015 at 2:10 AM, Ed Kellett
Type checking will either fail, or become ubiquitous to the point that passing type checking is a prerequisite to being considered "good" for any Python module. Should the latter happen, I'll have to break my code for past or future Python versions, no?
If that ever does happen, it won't be for a very VERY long time. So you would have the choice between breaking your code for <=3.4 or for
=4.7 or something. Is that really so likely?
ChrisA
On 22 January 2015 at 15:10, Ed Kellett
Type checking will either fail, or become ubiquitous to the point that passing type checking is a prerequisite to being considered "good" for any Python module. Should the latter happen, I'll have to break my code for past or future Python versions, no?
You would never be required to add type hints to your own code. I guess other projects could decide they will only accept contributions that pass the typechecker - but you don't have to contribute to such projects if you don't want to. At least that's my understanding. Personally, I doubt I'll ever use type hints, any more than I use tools like flake8. They don't suit the sort of code I typically write. Paul
On Thu, Jan 22, 2015 at 7:10 AM, Ed Kellett
On Thu Jan 22 2015 at 14:53:56 Brett Cannon
wrote: If you read one of Ed's earlier emails it sounds like he thinks you need the @no_type_hints decorator even if you don't provide *any* annotations, which is incorrect (the decorator is only to signal that annotations you did specify are not type hints; not specifying any annotations signals the same thing). Heck, the decorator isn't even technically necessary if the linter or IDE you are using that can process the type hints chooses to treat unrecognized annotations as Any instead of throwing an error or something.
I feel like I've been significantly misunderstood. To be clear, I'm talking about using @no_type_checks or whatever the decorator is called for code I have that *already uses annotations*.
Or you could just refrain from running the type checker. The checker is not built into CPython. It will be a separate program, run voluntarily, that points out issues, just like a linter. Support you run the pep8 program (a linter mostly for whitespace and naming conventions) and it tells you that your lines are longer than 79 chars. What are you going to do? Reformat your code to fit? You could, but nobody will force you. You could just ignore those warnings, or change pep8's configuration file to disable that message, or just *not run pep8 at all*. SImilar for other linters (e.g. pyflakes, pylint) that point out more substantial issues such as unused or undefined variables. Type checking will either fail, or become ubiquitous to the point that
passing type checking is a prerequisite to being considered "good" for any Python module. Should the latter happen, I'll have to break my code for past or future Python versions, no?
I doubt that is how it will work out. There's a reason we call it *Gradual typing*. You as a Python developer (or a team of collaborating developers for a given project) can choose where you think you will benefit from getting parts of your code type-checked, and where you don't think it is worth it. Again, this is the same as for linting tools. When you're writing a half-hour hack to scrape your old mailbox, you're probably not going to care about a type checker (you run your script and fix it until it works, and then you move on). When you're planning to write a 100,000 line application over a year's time with a team of six programmers you might agree that your code follows the strictest style guides, passes a linter with zero warnings, or, in the future, that it type-checks without warnings. (You should probably also agree on unit testing and integration testing. And you need a code review process. And revision control. But I digress.) If you're in the latter situation there are usually some compromises you have to make -- e.g. your team may not all agree on the line length, but it's better to pick one and stick to it than to continually bicker about it. Similar for type hints -- you may have to agree on how important they are for your project and and adjust your habits accordingly.
Personally, I'd like to believe that this proposal and the echochamber that's surrounded it has all been just a bad dream, but given that it's almost certainly going to make it into Python at this point, it'd be nice not to have to make good code bad just to maintain compatibility with it.
This paragraph (and a some words you wrote earlier) feels really offensive to me. I am doing the best I can. I have listened to an *enormous* amount of feedback and if you read back earlier discussions (going back to I believe September or October 2014) or skim the typehinting issue tracker https://github.com/ambv/typehinting/issues you'll find that I am taking the feedback very seriously. Having got that off my mind, the vast majority of Python code that's been written does not use annotations at all, so there is no reason to change it at all. For code that does use annotation, *nothing* will break at run time -- the code will work unchanged, without any deprecations or slowdowns. The only situation where there may be a desire to change something (the minimal thing being to add a "# type: OFF" comment at the top of a module) is when a library that uses annotations for non-type-hinting purposes is used by an application (or another library) that wants to use type hints, and the user who is running the type checker cannot live with the noise output spewed by the type checker for that library. Even there, the problem could also be solved by having a way to configure the type checker to ignore annotations in certain packages (or to only check code in specific directories).
Since type hints already depend on the 'typing' module anyway, what exactly would be wrong with the type checker defaulting off unless it is imported?
I've considered that, but I find it limiting. It is quite likely that a module can be fully annotated with type hints without ever needing to reference the typing module. For example, all argument types may be simple built-in types (strings and numbers) or user-defined classes. I think it would be unexpected if type hints that were present in a module would be ignored by the type checker unless the typing module was imported. (Also, perhaps ironically, most linters will complain about modules that are imported but not used. :-) But this is an area where we may be able to cater to different preferences by passing the type checker different flags or configurations. So I don't want to make a big thing out of this, and if people think it's useful, this can well be an option. Finally. In the distant future there may be more agreement about the use of annotations for type hints, and users will start asking library authors to change their code. But that's no different than other evolution of the language -- it won't happen overnight, you will get ample warning (DeprecationWarning :-), and the principle of supply and demand will apply. Heck, if you're not using Python 3, you're safe until 2020. -- --Guido van Rossum (python.org/~guido)
On Jan 22, 2015, at 12:05, Guido van Rossum
But this is an area where we may be able to cater to different preferences by passing the type checker different flags or configurations. So I don't want to make a big thing out of this, and if people think it's useful, this can well be an option.
I agree with everything else you said, but I'm not sure I understand this paragraph. The PEP isn't specifying anything about type checkers except how they interpret type annotations. So isn't such a flag just a quality of implementation issue on (hopefully-competing) checkers and IDEs and other tools? Are you just suggesting that such a flag can be added to MyPy (which will likely encourage other checkers to do the same unless nobody finds it useful)? Or are you planning to expand the spec with suggestions for configuration that type checking tools should consider offering?
On Thu, Jan 22, 2015 at 1:34 PM, Andrew Barnert < abarnert@yahoo.com.dmarc.invalid> wrote:
On Jan 22, 2015, at 12:05, Guido van Rossum
wrote: But this is an area where we may be able to cater to different preferences by passing the type checker different flags or configurations. So I don't want to make a big thing out of this, and if people think it's useful, this can well be an option.
I agree with everything else you said, but I'm not sure I understand this paragraph. The PEP isn't specifying anything about type checkers except how they interpret type annotations. So isn't such a flag just a quality of implementation issue on (hopefully-competing) checkers and IDEs and other tools?
Are you just suggesting that such a flag can be added to MyPy (which will likely encourage other checkers to do the same unless nobody finds it useful)? Or are you planning to expand the spec with suggestions for configuration that type checking tools should consider offering?
I guess it should just be a mypy flag. But I think the PEP should have a few reassuring words for users like Ed who worry about this. See: https://github.com/ambv/typehinting/issues/53 -- --Guido van Rossum (python.org/~guido)
On Thu Jan 22 2015 at 20:05:41 Guido van Rossum
This paragraph (and a some words you wrote earlier) feels really offensive to me. I am doing the best I can. I have listened to an *enormous* amount of feedback and if you read back earlier discussions (going back to I believe September or October 2014) or skim the typehinting issue tracker https://github.com/ambv/typehinting/issues you'll find that I am taking the feedback very seriously.
I'm sorry. I don't mean to minimize the work you've put into this, and in any case there was no call to speak so unconstructively.
The only situation where there may be a desire to change something (the minimal thing being to add a "# type: OFF" comment at the top of a module) is when a library that uses annotations for non-type-hinting purposes is used by an application (or another library) that wants to use type hints, and the user who is running the type checker cannot live with the noise output spewed by the type checker for that library.
This is more or less the situation I imagined. Or, more problematic - the user running the type checker might assume the library is no good because it sets off the warnings.
Finally. In the distant future there may be more agreement about the use of annotations for type hints, and users will start asking library authors to change their code. But that's no different than other evolution of the language -- it won't happen overnight, you will get ample warning (DeprecationWarning :-), and the principle of supply and demand will apply. Heck, if you're not using Python 3, you're safe until 2020.
I understand that it's a long way off, but in my view that'd be a regression: I know not many things are using annotations at the moment, but where they are used they fit really well (extra information for command-line-ifying a function being the one I have most experience with). It seems a shame to throw away something so neat when it feels as if the different use cases could coexist peacefully. Thanks for your time, Ed Kellett
On Thu, Jan 22, 2015 at 1:45 PM, Ed Kellett
On Thu Jan 22 2015 at 20:05:41 Guido van Rossum
wrote: This paragraph (and a some words you wrote earlier) feels really offensive to me. I am doing the best I can. I have listened to an *enormous* amount of feedback and if you read back earlier discussions (going back to I believe September or October 2014) or skim the typehinting issue tracker https://github.com/ambv/typehinting/issues you'll find that I am taking the feedback very seriously.
I'm sorry. I don't mean to minimize the work you've put into this, and in any case there was no call to speak so unconstructively.
Apology accepted!
The only situation where there may be a desire to change something (the
minimal thing being to add a "# type: OFF" comment at the top of a module) is when a library that uses annotations for non-type-hinting purposes is used by an application (or another library) that wants to use type hints, and the user who is running the type checker cannot live with the noise output spewed by the type checker for that library.
This is more or less the situation I imagined. Or, more problematic - the user running the type checker might assume the library is no good because it sets off the warnings.
Hopefully it'll be a while before users who are that clueless will figure out how to run the type checker. :-)
Finally. In the distant future there may be more agreement about the use
of annotations for type hints, and users will start asking library authors to change their code. But that's no different than other evolution of the language -- it won't happen overnight, you will get ample warning (DeprecationWarning :-), and the principle of supply and demand will apply. Heck, if you're not using Python 3, you're safe until 2020.
I understand that it's a long way off, but in my view that'd be a regression: I know not many things are using annotations at the moment, but where they are used they fit really well (extra information for command-line-ifying a function being the one I have most experience with). It seems a shame to throw away something so neat when it feels as if the different use cases could coexist peacefully.
Yes, I am well aware that it's a regression. Regressions are taken seriously; see e.g. PEP 4, which describes the deprecation process for modules. It's much the same for language features. But that doesn't mean we don't sometimes decide to change our mind. (And this isn't limited to major version number transitions like Python 2 -> 3.) This is one of those cases. I expect (or hope?) that at some point in the distant future users who see an annotation will know that it is a type hint, without having to figure out whether may be there is a decorator or some other less-visible mechanism (e.g. a metaclass, shudder) that gives it a different meaning. I believe that this is a more desirable future than one in which type hints have to compete with other meta-information for arguments for their syntactic position. At the same time -- let me emphasize this one more time -- I hope there will never be a time in the future where type hints are mandatory or otherwise always expected to exist. (If you want that you should probably switch to Java sooner rather than later. :-) And lastly, such a future is a long way off, and in 3.5 your code won't break. -- --Guido van Rossum (python.org/~guido)
On 1/22/2015 6:19 PM, Guido van Rossum wrote:
At the same time -- let me emphasize this one more time -- I hope there will never be a time in the future where type hints are mandatory or otherwise always expected to exist.
If the PEP does not already say this, it might be helpful to add it -- for those who read it. Probably more important is the doc -- which gets read much more. -- Terry Jan Reedy
On Thu, Jan 22, 2015 at 3:35 PM, Guido van Rossum
The only situation where there may be a desire to change something (the minimal thing being to add a "# type: OFF" comment at the top of a module) is when a library that uses annotations for non-type-hinting purposes is used by an application (or another library) that wants to use type hints, and the user who is running the type checker cannot live with the noise output spewed by the type checker for that library.
Even there, the problem could also be solved by having a way to configure the type checker to ignore annotations in certain packages (or to only check code in specific directories).
Having library authors change their code because type hinting was introduced would be unacceptable. The burden must all on whomever wants to benefit from type checking.
Since type hints already depend on the 'typing' module anyway, what exactly would be wrong with the type checker defaulting off unless it is imported?
I've considered that, but I find it limiting. It is quite likely that a module can be fully annotated with type hints without ever needing to reference the typing module. For example, all argument types may be simple built-in types (strings and numbers) or user-defined classes. I think it would be unexpected if type hints that were present in a module would be ignored by the type checker unless the typing module was imported. (Also, perhaps ironically, most linters will complain about modules that are imported but not used. :-)
"import typing" is a nice and easy compromise, very much in the pythonic way, in my opinion. If you want your module type checked: import typing. It will not be checked otherwise. It's simple, backwards compatible, very little effort for those wanting to jump into PEP484, and zero pain for those who don't. Cheers, -- Juancarlo *Añez*
On 01/22/2015 04:55 PM, Juancarlo Añez wrote:
On Thu, Jan 22, 2015 at 3:35 PM, Guido van Rossum wrote:
Even there, the problem could also be solved by having a way to configure the type checker to ignore annotations in certain packages (or to only check code in specific directories).
Having library authors change their code because type hinting was introduced would be unacceptable.
Annoying, yes. Unacceptable? No. As the language progresses library authors occasionally have to make adjustments (and I am one, so I feel the pain).
The burden must fall on whomever wants to benefit from type checking.
Indeed -- if someone does want type checking, but library xyz is using annotations for something else, then a way (provided by the type checker) to skip certain modules would be good. -- ~Ethan~
On Thu, Jan 22, 2015 at 8:34 PM, Ethan Furman
Indeed -- if someone does want type checking, but library xyz is using annotations for something else, then a way (provided by the type checker) to skip certain modules would be good.
FWIW, the proposition is to skip every module that doesn't "import typing". Cheers, -- Juancarlo *Añez*
On 1/22/2015 3:05 PM, Guido van Rossum wrote:
Having got that off my mind, the vast majority of Python code that's been written does not use annotations at all, so there is no reason to change it at all. For code that does use annotation, *nothing* will break at run time -- the code will work unchanged, without any deprecations or slowdowns.
The only situation where there may be a desire to change something (the minimal thing being to add a "# type: OFF" comment at the top of a module) is when a library that uses annotations for non-type-hinting purposes is used by an application (or another library) that wants to use type hints, and the user who is running the type checker cannot live with the noise output spewed by the type checker for that library.
I believe the dual-use of annotations problem would be solved if static type checkers gave precedence to annotations in stub files. Putting type hints in separate files is *possible* because static checkers do not need and will not use a runtime .__annotations__ attribute. If the presence of a stub file meant that annotations in the corresponding .py file were ignored by static checkers, then they could still be used in .py files to create .__annotations__ for dynamic use. I think typing should have a make_stub(inpy, outstub) function to make blank stub creation easy. It would copy all def headers and add ':' and '->' where appropriate. It could have an any=??? option which, when true, would add 'any' everywhere. Running typing as a script (-m) could run make_stub. An all-any stub would eliminate the need even for #type:off, and could be added by a user without touching the .py file. I think putting type hints in stub files should be documented as at least on a par, if not encouraged, with putting them in .py files. 1. As with writing tests, only a subset of python programmers will enjoy, be willing to, and be good at writing complex type hints. So stub files might be writtin by someone other than the .py author(s). 2. In some cases, type hints intended to be as accurate as possible will not be pretty -- or simple. In a separate file intended for machine consumption, aesthetics can more easily be ignored in favor of accuracy. 3. Users may want to add or customize annotations (perhaps to narrow the allowed arguments to conform to policy). This is better done in a separate file. -- Terry Jan Reedy
On Thu, Jan 22, 2015 at 8:45 PM, Terry Reedy
On 1/22/2015 3:05 PM, Guido van Rossum wrote:
Having got that off my mind, the vast majority of Python code that's been written does not use annotations at all, so there is no reason to change it at all. For code that does use annotation, *nothing* will break at run time -- the code will work unchanged, without any deprecations or slowdowns.
The only situation where there may be a desire to change something (the minimal thing being to add a "# type: OFF" comment at the top of a module) is when a library that uses annotations for non-type-hinting purposes is used by an application (or another library) that wants to use type hints, and the user who is running the type checker cannot live with the noise output spewed by the type checker for that library.
I believe the dual-use of annotations problem would be solved if static type checkers gave precedence to annotations in stub files. Putting type hints in separate files is *possible* because static checkers do not need and will not use a runtime .__annotations__ attribute. If the presence of a stub file meant that annotations in the corresponding .py file were ignored by static checkers, then they could still be used in .py files to create .__annotations__ for dynamic use.
Correct -- if a stub exists the checker should not look at the actual source code at all. (The current PEP draft is light on stubs but we know we need to fix this. Patches welcome!)
I think typing should have a make_stub(inpy, outstub) function to make blank stub creation easy. It would copy all def headers and add ':' and '->' where appropriate. It could have an any=??? option which, when true, would add 'any' everywhere. Running typing as a script (-m) could run make_stub. An all-any stub would eliminate the need even for #type:off, and could be added by a user without touching the .py file.
That sounds like a useful tool, but why should it be in typing.py? It shouldn't be needed at run time -- if the code is being run, the stub isn't needed, and if the code is being type-checked, the code won't be run so the stub won't be generated.
I think putting type hints in stub files should be documented as at least on a par, if not encouraged, with putting them in .py files.
On a par. Stubs are essential for extension modules, and nearly essential for existing packages (both 3rd party and the stdlib!). But I don't want people writing new 3.x code having to deal with two files for every module. I've recently had to do this for C++ and the endless back-and-forth between .hpp and .cpp files (and the subtleties of what is allowed/required in each type of file) drove me nuts.
1. As with writing tests, only a subset of python programmers will enjoy, be willing to, and be good at writing complex type hints. So stub files might be writtin by someone
other than the .py author(s).
Yes, especially for existing libraries that is very useful.
2. In some cases, type hints intended to be as accurate as possible will not be pretty -- or simple. In a separate file intended for machine consumption, aesthetics can more easily be ignored in favor of accuracy.
Hm, I'm skeptical about that. We're not writing Haskell types here. Most type hints are very bread-and-buttery things.
3. Users may want to add or customize annotations (perhaps to narrow the allowed arguments to conform to policy). This is better done in a separate file.
I don't understand this part. Can you clarify, maybe give an example? -- --Guido van Rossum (python.org/~guido)
On 01/22/2015 09:23 PM, Guido van Rossum wrote:
On Thu, Jan 22, 2015 at 8:45 PM, Terry Reedy wrote:
3. Users may want to add or customize annotations (perhaps to narrow the allowed arguments to conform to policy). This is better done in a separate file.
I don't understand this part. Can you clarify, maybe give an example?
As a guess, if somebody has a library function that is somewhat generic, but they will only be using it with a specific type, then having a stub file with that more specific information would help catch bugs where a type is being passed in that is not the required specific type, even though it satisfies the more generic type in the library's annotations. Something like: --8< actual_lib.py ------------------------------- def cool_adder_func(a:Number, b:Number): -> Number return a + b --8< --------------------------------------------- --8< overriding stub file ----------------------- def cool_adder_func(a:int, b:int): -> int pass --8< -------------------------------------------- --8< application -------------------------------- ... cool_adder_func(float_var, Decimal_var) --8< -------------------------------------------- The above would be flagged because in this application, using this stub file, cool_adder_func should only take ints. -- ~Ethan~
It looks like type hinting as a feature is applauded by everyone but the actual syntax/implementation is polarizing. Could we have a section in the PEP listing all alternative sytax/implementations with pros and cons? Stuff like: 1. Stub files 2. property-style type hinting, e.g.: @typehints( arg1: int, arg2: str, returns: str ) def func( arg1, arg2 ) return 'hello' 3. docstrings, e.g.: def func( arg1, arg2 ): """type hints: arg1: int, arg2: str, returns: str""" return 'hello' 4. Cobra-inspired type hints, e.g.: def func( arg1, arg2 ): requires: isinstance( arg1, int ) isinstance( arg2, str ) returns: str Cheers, Daniel -- Psss, psss, put it down! - http://www.cafepress.com/putitdown
Sigh. I guess I'll have to do this.
https://github.com/ambv/typehinting/issues/55
On Fri, Jan 23, 2015 at 5:33 AM, Fetchinson .
It looks like type hinting as a feature is applauded by everyone but the actual syntax/implementation is polarizing. Could we have a section in the PEP listing all alternative sytax/implementations with pros and cons? Stuff like:
1. Stub files
2. property-style type hinting, e.g.:
@typehints( arg1: int, arg2: str, returns: str ) def func( arg1, arg2 ) return 'hello'
3. docstrings, e.g.:
def func( arg1, arg2 ): """type hints: arg1: int, arg2: str, returns: str""" return 'hello'
4. Cobra-inspired type hints, e.g.:
def func( arg1, arg2 ): requires: isinstance( arg1, int ) isinstance( arg2, str ) returns: str
Cheers, Daniel
-- Psss, psss, put it down! - http://www.cafepress.com/putitdown _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
Guido van Rossum writes:
I think typing should have a make_stub(inpy, outstub) function to make blank stub creation easy. [...]
That sounds like a useful tool, but why should it be in typing.py?
It's an obvious place to look for it. You could put it in the checking application, but with multiple applications that would result in code duplication, wouldn't it?
I think putting type hints in stub files should be documented as at least on a par, if not encouraged, with putting them in .py files.
On a par. Stubs are essential for extension modules, and nearly essential for existing packages (both 3rd party and the stdlib!). But I don't want people writing new 3.x code having to deal with two files for every module.
I think what Terry is suggesting is that if you want *both* type hints and some other use of annotations, you could get that by putting the type hints in a separate file, and that this should be documented. I agree the arguments for "encouraged" are weak (except in case where multiple forms of annotation are desired), and the annoyance of the C/C++ .c/.h partition is quite discouraging. Note that, IIUC Terry's idea, this would also allow library authors or users to disable type hinting warnings permanently with "touch <stubfile>". I think that's a reasonable burden, especially since it only requires directory write access for the user to disable warnings if they have a package using non-typing annotations. Aside: I can't agree with those who think that many users of typing should have to request type-checking *three* times: once by writing the hint, once by running the checker, and once by importing typing.
On Fri, Jan 23, 2015 at 7:59 PM, Stephen J. Turnbull
Guido van Rossum writes:
I think typing should have a make_stub(inpy, outstub) function to make blank stub creation easy. [...]
That sounds like a useful tool, but why should it be in typing.py?
It's an obvious place to look for it. You could put it in the checking application, but with multiple applications that would result in code duplication, wouldn't it?
Hm. It sounds like a kitchen sink or grab bag approach. Nothing else in typing.py has even the remotest use for I/O or text processing. It would be a whole bunch of code that would have nothing in common with the rest of the stuff in typing.py except the name. If we wanted a calculator app, should it live in math? Or perhaps in decimal? I don't think so. FWIW, I just remembered that there is actually a really cool stub generator that's part of mypy: pinfer https://github.com/JukkaL/mypy/tree/master/pinfer (written or at least greatly improved by Dropbox intern Jared Pochtar last summer). It runs your program (typically a unittest suite) and through introspection discovers the argument types of all methods that are called. There's also an old mypy issue https://github.com/JukkaL/mypy/issues/43 referring to an old mypy wiki page http://www.mypy-lang.org/wiki/StubGeneratorTool about stub generation.
I think putting type hints in stub files should be documented as at least on a par, if not encouraged, with putting them in .py files.
On a par. Stubs are essential for extension modules, and nearly essential for existing packages (both 3rd party and the stdlib!). But I don't want people writing new 3.x code having to deal with two files for every module.
I think what Terry is suggesting is that if you want *both* type hints and some other use of annotations, you could get that by putting the type hints in a separate file, and that this should be documented.
Yeah, as a transitional measure. Eventually other use of annotations will look weird to most people.
I agree the arguments for "encouraged" are weak (except in case where multiple forms of annotation are desired), and the annoyance of the C/C++ .c/.h partition is quite discouraging.
Note that, IIUC Terry's idea, this would also allow library authors or users to disable type hinting warnings permanently with "touch <stubfile>". I think that's a reasonable burden, especially since it only requires directory write access for the user to disable warnings if they have a package using non-typing annotations.
But with the current concept of stub files (borrowed from mypy http://www.mypy-lang.org/wiki/CreatingStubsForPythonModules) an empty stub file is not enough. The functions and classes defined in the stub tell the type checker what can be imported from the corresponding real module. I think it would be simpler to have some other, out-of-band way to signal that a module is off-limits. (I've proposed a configuration setting or command line flag to the checker.) This reminds me -- if you have a "# type: OFF" comment at the top of a module, I presume the type checker still reads the rest of the file (at the very least looking for "# type: ON"), and it may still record the presence of things defined there, for the benefit of checking that things imported *from* that module at least exist. The way I envisioned "# type: OFF" was that the type checker will ignore annotations, essentially assuming that annotations it sees don't exist. But when it sees the following in file a.py: # type: OFF def foo(a): return a+1 and in file b.py there is this: from a import foo foo(1, 2, 3) foo('hello') It might still complain about the foo(1, 2, 3) call in b.py, because it still knows that a.foo() takes exactly one argument. My assumption so far has been that # type: OFF tells the checker that it should not interpret annotations, because they may be used for other purposes. (This has been the main reason for proposing # type: OFF.) But maybe we want it to shut up more completely? Maybe there should be a way to tell it that the entire *module object* for a has type Any, so that *all* operations on it are allowed (and return Any)? An empty stub doesn't do that -- but a config option like I proposed above could. Or should we allow some special non-Python syntax in stub files to signal the same thing? E.g. * = * # type: Any or * = Undefined(Any) It might not be hard to teach a type checker (or other stub parser) about such a magic pattern (if there was exactly one form), and instead of touch a.pyi we could use 'echo "* = Undefined(Any)" >a.pyi. Sorry for the rambling post.
Aside: I can't agree with those who think that many users of typing should have to request type-checking *three* times: once by writing the hint, once by running the checker, and once by importing typing.
Thanks. I doubt that's going to happen. -- --Guido van Rossum (python.org/~guido)
On Friday, January 23, 2015 8:41 PM, Guido van Rossum
FWIW, I just remembered that there is actually a really cool stub generator that's part of mypy: pinfer (written or at least greatly improved by Dropbox intern Jared Pochtar last summer). It runs your program (typically a unittest suite) and through introspection discovers the argument types of all methods that are called. There's also an old mypy issue referring to an old mypy wiki page about stub generation.
That's very cool. And it seems like something that different implementations could usefully compete on. For an extreme example, PyPy has within it an algorithm to statically walk all code paths in your bytecode and infer all types for RPython. Modifying that to handle full Python by falling back to Any instead of raising an exception when it finds a potentially heterogeneous collection or a use of eval or whatever seems like it might be a plausible project. Although it would probably be a whole lot slower than the mypy stub generator, it might also be useful as something you only run once/release.
On 01/23/2015 08:40 PM, Guido van Rossum wrote:
This reminds me -- if you have a "# type: OFF" comment at the top of a module, I presume the type checker still reads the rest of the file (at the very least looking for "# type: ON"), and it may still record the presence of things defined there, for the benefit of checking that things imported *from* that module at least exist. The way I envisioned "# type: OFF" was that the type checker will ignore annotations, essentially assuming that annotations it sees don't exist. But when it sees the following in file a.py:
# type: OFF def foo(a): return a+1
and in file b.py there is this:
from a import foo foo(1, 2, 3) foo('hello')
It might still complain about the foo(1, 2, 3) call in b.py, because it still knows that a.foo() takes exactly one argument.
+1 I think "# type: OFF" should just ignore any existing annotations, but still track things such as function parameters -- maybe by assigning Any to all paramaters it finds? -- ~Ethan~
On Sat, Jan 24, 2015 at 11:04 AM, Ethan Furman
I think "# type: OFF" should just ignore any existing annotations, but still track things such as function parameters -- maybe by assigning Any to all paramaters it finds?
Having slept on it, that's probably best for simple, undecorated functions and methods in vanilla classes. For decorated functions and methods it's more complicated though -- if the type checker doesn't understand the decorator it's not safe to assume that the signature is unchanged. (E.g. @staticmethod, @classmethod, @property all modify the signature -- the type checker should know about those, but many custom decorators do this too.) Maybe for decorated functions and methods the best it can do is to record the existence of the function name (since the decorator syntax guarantees that), giving it the type 'Any'. It may also have to do this for classes if it can't figure out whether there is a custom metaclass or what a class decorator does. But we're now out of the realm of how to disable type checking a whole module, and I don't worry so much about this -- to some extent it's a matter of quality of implementation how well the type checker copes with such modules and at what level it must assume the type 'Any'. For quickly disabling a whole module or package, we still have a choice. Every type checker can have its own configuration mechanism that allows selectively telling the checker to stay away from certain module, and this should be good enough for most users; the PEP shouldn't have to specify anything here except hint at the usefulness of such a feature. The question remains what package owners can do if their package has code that causes the type checker to be too noisy. They can use # type: OFF comments (with the above caveat that there might still be noise at call sites outside the package), or they can include stub modules (which require some thought to get right -- even the pinfer tool often guesses wrong). Do we want to support a "type checker shut up!" file can be present in the package root? This may be handy to avoid noise from e.g. mypy, but I'd imagine that e.g. PyCharm might want to ignore this and still look for useful things to aid its completion feature. Thinking about this more, I think we needn't do anything about this now, and we can see how things work out once 3.5 is in active use. -- --Guido van Rossum (python.org/~guido)
On Thu, Jan 22, 2015 at 12:32:40PM +0000, Ed Kellett wrote:
On Thu Jan 22 2015 at 12:17:37 Chris Angelico
wrote: The absence of a decorator that uses the annotations some other way should make it clear that they're type hints, just as the absence of a "global" statement should make it clear that assigned names are local to a function.
What about code that already exists?
What about them? I have lots of code that already exists. (Some of it even works *wink*) Since that code doesn't use annotations, this proposal will not make one iota of difference to that code. *Literally* nothing will change. Some of my code has annotations. But since I'm not using a PEP 484 type-checker on that code, it too will continue to work completely unchanged. The *only* code that will change is that which *I* decide to start using annotations for type-checking. If I'm using Python 3.4 or older, I go to PyPI and download the typing.py module. If I'm using 3.5, it will be included in the standard library. So I add the annotations to my code: as much, or as little, of the code as I choose. I could annotate as little as one function or as much as the entire application. Even then, I still have choices. I can choose whether to perform type-checking or not: - I can just run my code directly with the standard CPython interpreter (the one you are probably using now), and no type-checks will take place. CPython will not include a type-checker, not in 3.5 and perhaps not ever. - I can run my code with Mypy, and it will perform static type-checks before executing the code. - I can run my code with a static linter or type-checker and not run the code. - Or just let my IDE or editor give me better completion hints and flag errors as I type them.
What about modules that want to support Python installations that already exist (hence don't have the `typing` module)?
typing.py will be available on PyPI.
What about module authors who simply don't want to make their code ugly with a no-op decorator just to signify that they're not making it ugly with type annotations?
You don't need to flag functions with a decorator to signify that you aren't using annotations. You just don't use annotations. Nobody (except maybe your boss) will force you to use annotations. Python will continue to work with them or without them. The only people who end up a little worse off are those who already use annotations for something else. But even they won't be *much* worse off, just a little: - Nobody will force them to use a type-checker or mypy, they can just keep using their annotations and *not* use a type-checker. - If they want to interoperate with a type-checker, they will have to take steps to tell the type-checker to skip their annotations. This might be a directive "# typing=OFF" or a "@notyping" decorator. Either way, it won't be onerous. - The worst case is if they want to use their existing annotations AND the new type-hints at the same time, for the same functions. In that case, they will have to find an alternative for their annotations, perhaps moving them into a decorator. -- Steven
On Thu Jan 22 2015 at 6:19:38 AM Cem Karan
On Jan 21, 2015, at 11:47 PM, Guido van Rossum
wrote: On Wed, Jan 21, 2015 at 8:39 PM, Eugene Toder
wrote: On Wed, Jan 21, 2015 at 8:25 PM, Greg Ewing wrote: On 01/22/2015 01:50 PM, Guido van Rossum wrote: I've seen plenty of code that takes instances a certain class as argument and calls methods of that type but never constructs a new instance nor does any of the other things for which you need the class name, If the only thing the name is needed for is static checking, I don't see what's so bad about not finding out until you actually run the static check. Adding those imports earlier doesn't make the code work any better or show up any more runtime bugs.
Also, if static type checking succeeds, it will be integrated into many IDEs/editors, so you will see type checker errors even before you get to run the tests.
So I think we're all agreed that the current proposal is fine!
*grumble* ;P
I know I keep arguing for a slightly different syntax so that its easy to differentiate between type hints and other uses, but I do want to say that I like the proposal. It seems quite practical to me. Would you at least consider adding something to the proposal that makes it clear that a given annotation is a type and not something else?
Guido already has: the typing module and its namespace along with the @no_type_hints decorator. Practicality beats Purity in this case and so adding any unnecessary overhead in the specification of the type hints is not going to fly. And your worry about all needing to flag non-type hints for annotations really only comes into play if the tool consuming them errors out or something when it comes across them. If it plays it safe and chooses unrecognized type hints as Any then any alternative use of annotations will just be ignored. I realize you don't like the loss in flexibility, but it is just a fact that function annotations are going to de-facto become type hint annotations in Python's future. They are the best use for annotations that we have come up with as a community and making the barrier to their use as low as possible puts it in the best interest of everyone to tighten the expectation of what annotations represent.
On Fri, Jan 16, 2015 at 12:08 PM, Guido van Rossum
Yes, the PEP tries to gently push an agenda where annotations are only used for Python type hints.
Type hints, as opposed to arbitrary other uses, or type _hints_ as aopposed to run-time of compile time static typing? Cython, providing both of those, for example. I don't know what to answer for Cython -- doesn't it have its own syntax
for declaring C types?
yes: cdef int i or: def func( int i ). There is also a decorator-based system, for "pure python But I think it would be nice it it could use the same syntax as other systems, particularly if there is an official one.
If Cython uses annotations with different meanings, perhaps for Cython files the Python annotations could be put in stub files.
I think we're good there. But I think Stephan's issues is that currently cython allows both C and Python type names: cdef int i cdef list a_list but where the names conflict, the C name is assumed (int, float, others?) Given C's typedef, I'd say we should just have Cython use unique names -- i.e. c_int -> int, etc. But that all requires that no one decides to write a new python type called c_int. Though I'm not sure it's really a problem to reserve names for Cython. Anyway, I guess it's unclear to me whether the intent is to create a system that can be used for a static/run-time type checker like Cython. There have been a fair number of comments alone the lines of "this is only for lint-like static analysis". But I'd really love to see this move toward full functionailty. In fact, after reading the PEPs, I still don't really see the point of static type analysis that does not lead to compile time or run-time type checking. Maybe someone could provide the motivation for that, oradd alink to the PEP where that is. My question: I see two clear advantages to static typing: type safety and performance. As a proponent of Python (and thus dynamic typing), I have argued a lot that static type checking does indeed catch a lot of bugs, but they are generally shallow bugs -- easy to test for, and easy to understand and fix -- thus I don't much need that. However, I can see how it's a "good thing", and maybe important for "Enterprise" systems. On the other hand, dynamic typing does impact performance in a way that effects my code -- so I tun to Cython (and sometimes C, C++, Fortran) to address those issues. (maybe JIT compilation will one day meet this need, but it hasn't yet). So if there is type specifying, I want i to solve these problems. But where I'm lost is the utility of static analysis that is not used to either provide run-time type checking OR compile optimized code (either JIT or ahead-of-time). It seems that your entire system would have to be fully type-hinted to get the type safety. But once you've done that, why not just use a static language? On the other hand, if run-time type checking is provided, then you could use that at the boundaries of your code base (kind of like Cython does) and get both the safety and performance benifits. I've read the MyPY FAQ, which does address these issues to some extent, but doesn't really make a compelling case to me, anyway. I guess the gist here is that I think if Python is going to have an official type hinting system, it should leave the door open to compile time and run-time type checking. A good test case might be: can it be used for Cython? But this thread seems to be indicating that that is not the goal, and indeed, that it's almost an anti-goal. I hope I'm wrong about that. -Chris -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
On Jan 18, 2015, at 11:39, Chris Barker
I guess the gist here is that I think if Python is going to have an official type hinting system, it should leave the door open to compile time and run-time type checking. A good test case might be: can it be used for Cython? But this thread seems to be indicating that that is not the goal, and indeed, that it's almost an anti-goal. I hope I'm wrong about that.
The impression I get--and I hope this isn't just wishful thinking--is that this is an anti-goal for v1 (Python 3.5), but it's very much a goal for v2 (some future version). The issue is that it's a vague and ill-defined goal at the moment. So, there's a tactical argument that explicitly putting all of that off the table, and designing the simplest thing that could work for some purpose (a static type checker) now, is a better way to get to that long-term goal. Meanwhile, the door is open for people to experiment with fitting Cython C types and ORM column types and all kinds of other things into the system, or using it for hinting a static or JIT optimizer, or anything else. Most likely people who try that will find big gaps that they need filled--and that's exactly how we're going to find out what's needed for the v2 type annotation system.
It occurs to me that it wouldn't be that difficult to create a tool called 'python-annotations-to-cython' (maybe a shorter name like 'py2pyx') that would take whatever type annotation information was contained in a .py file and generate a .pyx file with cython types added in. On Sun, Jan 18, 2015 at 12:46 PM, Andrew Barnert < abarnert@yahoo.com.dmarc.invalid> wrote:
On Jan 18, 2015, at 11:39, Chris Barker
wrote: I guess the gist here is that I think if Python is going to have an official type hinting system, it should leave the door open to compile time and run-time type checking. A good test case might be: can it be used for Cython? But this thread seems to be indicating that that is not the goal, and indeed, that it's almost an anti-goal. I hope I'm wrong about that.
The impression I get--and I hope this isn't just wishful thinking--is that this is an anti-goal for v1 (Python 3.5), but it's very much a goal for v2 (some future version).
The issue is that it's a vague and ill-defined goal at the moment. So, there's a tactical argument that explicitly putting all of that off the table, and designing the simplest thing that could work for some purpose (a static type checker) now, is a better way to get to that long-term goal.
Meanwhile, the door is open for people to experiment with fitting Cython C types and ORM column types and all kinds of other things into the system, or using it for hinting a static or JIT optimizer, or anything else. Most likely people who try that will find big gaps that they need filled--and that's exactly how we're going to find out what's needed for the v2 type annotation system. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
On Jan 18, 2015, at 2:18 PM, David Mertz
On Jan 18, 2015, at 11:39, Chris Barker
wrote: I guess the gist here is that I think if Python is going to have an official type hinting system, it should leave the door open to compile time and run-time type checking. A good test case might be: can it be used for Cython? But this thread seems to be indicating that that is not the goal, and indeed, that it's almost an anti-goal. I hope I'm wrong about that.
The impression I get--and I hope this isn't just wishful thinking--is that this is an anti-goal for v1 (Python 3.5), but it's very much a goal for v2 (some future version).
The issue is that it's a vague and ill-defined goal at the moment. So, there's a tactical argument that explicitly putting all of that off the table, and designing the simplest thing that could work for some purpose (a static type checker) now, is a better way to get to that long-term goal.
Meanwhile, the door is open for people to experiment with fitting Cython C types and ORM column types and all kinds of other things into the system, or using it for hinting a static or JIT optimizer, or anything else. Most likely people who try that will find big gaps that they need filled--and that's exactly how we're going to find out what's needed for the v2 type annotation system. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
On Sun, Jan 18, 2015 at 11:39 AM, Chris Barker
On Fri, Jan 16, 2015 at 12:08 PM, Guido van Rossum
wrote: Yes, the PEP tries to gently push an agenda where annotations are only used for Python type hints.
Type hints, as opposed to arbitrary other uses, or type _hints_ as aopposed to run-time of compile time static typing?
Um, what did you mean by "run-time of compile time static typing"? I'm guessing "or" instead of "of". With that assumption, I think the answer is both. I want annotations to be (eventually) reserved for a standard type notation. The PEP focuses on the notation. The (by far largest) use case I see for the notation is running a separate off-line type checker, which acts like a linter but with much more understanding of types. (But not perfect!) A second use case (for the same notation!) is equally off-line, to provide information an IDE needs so it can give accurate suggestions etc. Some use cases that people (including myself) have often assumed but which I now find mostly anti-ideas: efficient code generation; run-time type checking; exact type checking by the Python interpreter when it compiles and loads modules.
Cython, providing both of those, for example.
I don't know what to answer for Cython -- doesn't it have its own syntax
for declaring C types?
yes:
cdef int i
or:
def func( int i ).
There is also a decorator-based system, for "pure python
But I think it would be nice it it could use the same syntax as other systems, particularly if there is an official one.
I think it would be nice too. But what I hear from Stefan is that Cython's requirements are not a good fit for the proposal. And I believe it makes more sense to let Cython continue to do its own thing rather than trying to either expand the proposal so Cython can use it without change, or allow for alternate type notations so Cython can do its own thing. The latter however sounds closer to what's possible.
If Cython uses annotations with different meanings, perhaps for Cython
files the Python annotations could be put in stub files.
I think we're good there.
But I think Stephan's issues is that currently cython allows both C and Python type names:
cdef int i cdef list a_list
but where the names conflict, the C name is assumed (int, float, others?)
Given C's typedef, I'd say we should just have Cython use unique names -- i.e. c_int -> int, etc. But that all requires that no one decides to write a new python type called c_int. Though I'm not sure it's really a problem to reserve names for Cython.
Everything in the proposal has a namespace (either 'typing' or 'builtins') and the checker is expected to understand and follow imports properly. So I don't see a problem here.
Anyway, I guess it's unclear to me whether the intent is to create a system that can be used for a static/run-time type checker like Cython.
That is not the intent. (Though static and run-time seem two very different forms of type-checking. Apparently Cython does both?)
There have been a fair number of comments alone the lines of "this is only for lint-like static analysis". But I'd really love to see this move toward full functionailty.
Once this proposal is in place you may try to adapt it for that purpose. But I'm not holding my breath, and I think we're better off with an imperfect proposal that can make it into 3.5 than with waiting until the ideal all-encompassing proposal is written.
In fact, after reading the PEPs, I still don't really see the point of static type analysis that does not lead to compile time or run-time type checking. Maybe someone could provide the motivation for that, oradd alink to the PEP where that is.
My question:
I see two clear advantages to static typing: type safety and performance.
But in the case of Python, the performance thing is entirely unproven, and the focus on it is a hindrance to trying to get any type system at all introduced.
As a proponent of Python (and thus dynamic typing), I have argued a lot that static type checking does indeed catch a lot of bugs, but they are generally shallow bugs -- easy to test for, and easy to understand and fix -- thus I don't much need that. However, I can see how it's a "good thing", and maybe important for "Enterprise" systems.
Nobody writes enough tests though, and type checking catches a different category of issues (shallow or not) than tests.
On the other hand, dynamic typing does impact performance in a way that effects my code -- so I tun to Cython (and sometimes C, C++, Fortran) to address those issues. (maybe JIT compilation will one day meet this need, but it hasn't yet).
So if there is type specifying, I want i to solve these problems.
How?
But where I'm lost is the utility of static analysis that is not used to either provide run-time type checking OR compile optimized code (either JIT or ahead-of-time). It seems that your entire system would have to be fully type-hinted to get the type safety. But once you've done that, why not just use a static language?
This is the problem of the traditional focus on type-safety. The traditional idea is that if you can't prove that the entire program is type-safe you should not run any of it (because the entire program is "broken"), and type-checking just part of the program is useless because you still don't know whether the program is "correct". (This is also often combined with the inability to generate code for an incorrect program.) However, even "correct" programs often contain bugs, because the correctness of a program depends on more than its type-safety. (Almost every program has some constraints that cannot be expressed in the language's type system.) However, gradual typing, for dynamically-typed languages, takes a different position. It claims that if you can type-check *some* of your program, the added type safety for that part helps find bugs. It's not a black or white situation. Just like a linter will find some bugs (after you wade through the false positives, tuning the linter options locally and globally to get a decent yield), a type checker of the kind proposed here will find some bugs (after you spend some time adding type annotations to part of the program). This approach works much better for dynamic languages than for static languages, because a dynamic language can still run a program that doesn't type-check correctly. (And for a variety of reasons, the program may still work well enough.) On the other hand, if run-time type checking is provided, then you could
use that at the boundaries of your code base (kind of like Cython does) and get both the safety and performance benifits.
But it sounds like it is Cython's job to do the checking at these boundaries. Isn't this similar to the way Python extensions written in C or C++ have to do type checking at the boundary between Python code and the extension? The specification of these checks should be defined by Cython.
I've read the MyPY FAQ, which does address these issues to some extent, but doesn't really make a compelling case to me, anyway.
It did to me. :-)
I guess the gist here is that I think if Python is going to have an official type hinting system, it should leave the door open to compile time and run-time type checking. A good test case might be: can it be used for Cython? But this thread seems to be indicating that that is not the goal, and indeed, that it's almost an anti-goal. I hope I'm wrong about that.
TBH I think the question of whether it can be used for Cython is still very much open. Why couldn't Cython take code that says a certain parameter has type List[int] and generate code from it? (Though no matter how you express this, I don't see how this could help much, since the input, being generated by regular Python code, would have to be a builtins.list object full of builtins.int objects. At that point there isn't much that Cython can do to speed things up.) -- --Guido van Rossum (python.org/~guido)
On Sun, Jan 18, 2015 at 7:28 PM, Guido van Rossum
Um, what did you mean by "run-time of compile time static typing"? I'm guessing "or" instead of "of". With that assumption, I think the answer is both.
sorry for the typo -- yes that was supposed to be "or" I want annotations to be (eventually) reserved for a standard type
notation. The PEP focuses on the notation. The (by far largest) use case I see for the notation is running a separate off-line type checker, which acts like a linter but with much more understanding of types. (But not perfect!) A second use case (for the same notation!) is equally off-line, to provide information an IDE needs so it can give accurate suggestions etc.
both good things, yes.
Some use cases that people (including myself) have often assumed but which I now find mostly anti-ideas: efficient code generation; run-time type checking; exact type checking by the Python interpreter when it compiles and loads modules.
I guess that's what I"ve pickedup on -- "anti-ideas"? but these are key for some of us...
I think it would be nice too. But what I hear from Stefan is that Cython's requirements are not a good fit for the proposal. And I believe it makes more sense to let Cython continue to do its own thing rather than trying to either expand the proposal so Cython can use it without change, or allow for alternate type notations so Cython can do its own thing. The latter however sounds closer to what's possible.
maybe that will have to be the way to go -- but if still seems like we are building a too-limited system right up front -- that may be necessary to keep it from being too ugly. You've certainly been thinking about this a long time.
Everything in the proposal has a namespace (either 'typing' or 'builtins') and the checker is expected to understand and follow imports properly. So I don't see a problem here.
OK -- that could be very helpful.
Anyway, I guess it's unclear to me whether the intent is to create a
system that can be used for a static/run-time type checker like Cython.
That is not the intent. (Though static and run-time seem two very different forms of type-checking. Apparently Cython does both?)
well, if you typedef a name: cdef int i then Cython generates static C code that uses i as an int (C int in this case). and if you typedef a function's parameters: def func(int i): then Cython generates code that tries to convert the argument to that type at run-time, raising an exception if it can't -- that's what i mean by run time type checking (I guess it's run-time type conversion, but it has to check in order to convert) There have been a fair number of comments alone the lines of "this is only
for lint-like static analysis". But I'd really love to see this move toward full functionailty.
Once this proposal is in place you may try to adapt it for that purpose. But I'm not holding my breath, and I think we're better off with an imperfect proposal that can make it into 3.5 than with waiting until the ideal all-encompassing proposal is written.
makes sense, but hopefully it will be an imperfect proposal that can evolve inot a more-perfect one in the future.
But in the case of Python, the performance thing is entirely unproven, and the focus on it is a hindrance to trying to get any type system at all introduced.
Cython makes all the difference in the world for certain types of code. How is that unproven?
Nobody writes enough tests though, and type checking catches a different category of issues (shallow or not) than tests.
well, hard to argue that catching more bugs isn't a good thing. On the other hand, dynamic typing does impact performance in a way that
effects my code -- so I tun to Cython (and sometimes C, C++, Fortran) to address those issues. (maybe JIT compilation will one day meet this need, but it hasn't yet).
So if there is type specifying, I want i to solve these problems.
How?
I meant that the new type specifying system should be usable by a static or JIT compiler to generate more optimized code -- i.e. Cython (or numba, or....) But where I'm lost is the utility of static analysis that is not used to
either provide run-time type checking OR compile optimized code (either JIT or ahead-of-time). It seems that your entire system would have to be fully type-hinted to get the type safety. But once you've done that, why not just use a static language?
However, gradual typing, for dynamically-typed languages, takes a different position. It claims that if you can type-check *some* of your program, the added type safety for that part helps find bugs. It's not a black or white situation. Just like a linter will find some bugs (after you wade through the false positives, tuning the linter options locally and globally to get a decent yield), a type checker of the kind proposed here will find some bugs (after you spend some time adding type annotations to part of the program).
Again, hard to argue that catching more bugs isn't a good thing. But thanks, this makes it more clear to me what' meant by 'gradual typing" I'm not sure that will satisfy the "enterprise" folks that really want static typing checking, but they can keep using Java, I guess. I do wonder how much this will "infect" Python, and folks will add all this completely unnecessary type hinting that will just make it harder to use libraries off the shelf. On the other hand, if run-time type checking is provided, then you could
use that at the boundaries of your code base (kind of like Cython does) and get both the safety and performance benifits.
But it sounds like it is Cython's job to do the checking at these boundaries. Isn't this similar to the way Python extensions written in C or C++ have to do type checking at the boundary between Python code and the extension?
exactly -- all cython really does is write a C extension for you -- auto-generating all that boilerplate.
The specification of these checks should be defined by Cython.
Sure, but they are almost always a direct result of type, so all I do when I write cython is specify type. And as you point out about gradual typing, I don't need to specify type at all, Cython is perfectly happy working with python objects and leaving the type checking to run time. But just as you can now run any (most, anyway...) python code through Cython, or would be great to be able to run any type annotated code through Cython, and hopefully get some benefit from the type annotations, and not have to essentially add redundant information.
I've read the MyPY FAQ, which does address these issues to some extent,
but doesn't really make a compelling case to me, anyway.
It did to me. :-)
I'm getting close with your additional notes... I guess the gist here is that I think if Python is going to have an
official type hinting system, it should leave the door open to compile time and run-time type checking. A good test case might be: can it be used for Cython? But this thread seems to be indicating that that is not the goal, and indeed, that it's almost an anti-goal. I hope I'm wrong about that.
TBH I think the question of whether it can be used for Cython is still very much open. Why couldn't Cython take code that says a certain parameter has type List[int] and generate code from it?
it probably could -- I think it's a good idea for us to start playing with that sort of thing. -Chris -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
Am 16.01.2015 um 20:45 schrieb Guido van Rossum:
That's discussed in this section: https://www.python.org/dev/peps/pep-0484/#compatibility-with-other-uses-of-f... . I still feel that saying "if you use function annotations, that function cannot be statically checked" is bad.
If type hints are successful, many projects will adopt them, especially big projects and/or projects in corporations. Probably many will require that all code must pass the type checker, and (at least) public functions be type annotated. Let's suppose that indeed happens. If I now were writing a new open source Python library, I would want that my library could be used in big projects, and therefore would have an incentive to type annotate my library. If I don't, a big project might choose another lib instead that does. So in that future, a library that wants to be successful basically can't use type annotations, even if they would have been an elegant solution to a particular problem. That feels somewhat wrong to me. A possible work around using skeletons seems ugly, you would have to edit two different files and keep them in sync. I feel there should be another way to make type annotations and "creative" annotations coexist.
The dict notation (which was proposed in an earlier thread on python-ideas) looks too messy to seriously support. How about using a tuple? I'll admit that I've proposed it before, but got no reaction.
If an annotation is a tuple, the type checker would check each element for known values, and every other thing that uses __annotations__ could do the same. Stefan's example could look something like def func(x: (str, wobble(False), ctype('std::string[utf8]'))) -> (str, wobble(True)) It's still not pretty, but it has the advantage that it is only needed for when more than one annotation is used. It has the disadvantage of preventing (int,str) being an alias for Tuple[int,str]. Actually, I don't care that much what particular solution is used to allow type annotations and non-type annotations to coexist, but I find it important that is possible.
On Fri, Jan 16, 2015 at 12:34 PM, Dennis Brakhane
Am 16.01.2015 um 20:45 schrieb Guido van Rossum:
That's discussed in this section:
https://www.python.org/dev/peps/pep-0484/#compatibility-with-other-uses-of-f...
. I still feel that saying "if you use function annotations, that function cannot be statically checked" is bad.
If type hints are successful, many projects will adopt them, especially big projects and/or projects in corporations. Probably many will require that all code must pass the type checker, and (at least) public functions be type annotated.
Let's suppose that indeed happens. If I now were writing a new open source Python library, I would want that my library could be used in big projects, and therefore would have an incentive to type annotate my library. If I don't, a big project might choose another lib instead that does.
So in that future, a library that wants to be successful basically can't use type annotations, even if they would have been an elegant solution to a particular problem. That feels somewhat wrong to me.
A possible work around using skeletons seems ugly, you would have to edit two different files and keep them in sync.
I feel there should be another way to make type annotations and "creative" annotations coexist.
The cleanest way to do this would be to use decorators for the non-type-hint use case.
The dict notation (which was proposed in an earlier thread on python-ideas) looks too messy to seriously support. How about using a tuple? I'll admit that I've proposed it before, but got no reaction.
If an annotation is a tuple, the type checker would check each element for known values, and every other thing that uses __annotations__ could do the same. Stefan's example could look something like
def func(x: (str, wobble(False), ctype('std::string[utf8]'))) -> (str, wobble(True))
It's still not pretty, but it has the advantage that it is only needed for when more than one annotation is used.
It's very ambiguous though. Especially since plain strings are taken to be type annotations (used for forward references).
It has the disadvantage of preventing (int,str) being an alias for Tuple[int,str].
But we're not proposing that (overloading native container syntax for types was proposed and rejected early on).
Actually, I don't care that much what particular solution is used to allow type annotations and non-type annotations to coexist, but I find it important that is possible.
Decorators. -- --Guido van Rossum (python.org/~guido)
On Jan 16, 2015, at 4:40 PM, Guido van Rossum
On Fri, Jan 16, 2015 at 12:34 PM, Dennis Brakhane
wrote: Am 16.01.2015 um 20:45 schrieb Guido van Rossum: That's discussed in this section: https://www.python.org/dev/peps/pep-0484/#compatibility-with-other-uses-of-f... . I still feel that saying "if you use function annotations, that function cannot be statically checked" is bad.
If type hints are successful, many projects will adopt them, especially big projects and/or projects in corporations. Probably many will require that all code must pass the type checker, and (at least) public functions be type annotated.
Let's suppose that indeed happens. If I now were writing a new open source Python library, I would want that my library could be used in big projects, and therefore would have an incentive to type annotate my library. If I don't, a big project might choose another lib instead that does.
So in that future, a library that wants to be successful basically can't use type annotations, even if they would have been an elegant solution to a particular problem. That feels somewhat wrong to me.
A possible work around using skeletons seems ugly, you would have to edit two different files and keep them in sync.
I feel there should be another way to make type annotations and "creative" annotations coexist.
The cleanest way to do this would be to use decorators for the non-type-hint use case.
The dict notation (which was proposed in an earlier thread on python-ideas) looks too messy to seriously support. How about using a tuple? I'll admit that I've proposed it before, but got no reaction.
If an annotation is a tuple, the type checker would check each element for known values, and every other thing that uses __annotations__ could do the same. Stefan's example could look something like
def func(x: (str, wobble(False), ctype('std::string[utf8]'))) -> (str, wobble(True))
It's still not pretty, but it has the advantage that it is only needed for when more than one annotation is used.
It's very ambiguous though. Especially since plain strings are taken to be type annotations (used for forward references).
It has the disadvantage of preventing (int,str) being an alias for Tuple[int,str].
But we're not proposing that (overloading native container syntax for types was proposed and rejected early on).
Actually, I don't care that much what particular solution is used to allow type annotations and non-type annotations to coexist, but I find it important that is possible.
Decorators.
If we're using decorators for the annotations, why not use them for the typing as well? E.g., @typing.type(a, int) @doc.doc(a, "some doc") def f(a): pass This would be the equivalent of: def f(a: {"typing.type": int, "doc.doc": "some doc"}): pass The whole dictionary idea will be hidden, and annotations can be used for other purposes while still supporting typing information. I'll admit that stacking the decorators means the code could get very deep, but it still feels like the least bad thing to do. Thanks, Cem Karan
On Fri, Jan 16, 2015 at 5:57 PM, Cem Karan
If we're using decorators for the annotations, why not use them for the typing as well?
Because annotations were intended for type hints.
E.g.,
@typing.type(a, int) @doc.doc(a, "some doc") def f(a): pass
This would be the equivalent of:
def f(a: {"typing.type": int, "doc.doc": "some doc"}): pass
The whole dictionary idea will be hidden, and annotations can be used for other purposes while still supporting typing information. I'll admit that stacking the decorators means the code could get very deep, but it still feels like the least bad thing to do.
You're taking this "nobody should be more equal than others" thing too far. :-) We're trying to come up with a standard way of doing one very important thing. -- --Guido van Rossum (python.org/~guido)
On Jan 16, 2015, at 9:11 PM, Guido van Rossum
On Fri, Jan 16, 2015 at 5:57 PM, Cem Karan
wrote: If we're using decorators for the annotations, why not use them for the typing as well?
Because annotations were intended for type hints.
E.g.,
@typing.type(a, int) @doc.doc(a, "some doc") def f(a): pass
This would be the equivalent of:
def f(a: {"typing.type": int, "doc.doc": "some doc"}): pass
The whole dictionary idea will be hidden, and annotations can be used for other purposes while still supporting typing information. I'll admit that stacking the decorators means the code could get very deep, but it still feels like the least bad thing to do.
You're taking this "nobody should be more equal than others" thing too far. :-) We're trying to come up with a standard way of doing one very important thing.
I understand what you're saying, but I feel like annotations have the capability to be MUCH more useful than just as a place to hold type hints. My example is fairly small, but it feels cleaner than some of the other solutions (e.g., Sphinx's :param: & :type: statements, see http://sphinx-doc.org/domains.html#info-field-lists). I just don't want to close the door to other uses, which is what will happen; as others have said, if type hinting becomes the officially blessed use for annotations, then other uses will be effectively deprecated, even if it is technically possible to continue using them for other purposes. Here is another way (this is a hack and requires more thought): """ # In the typing library class Type(object): def __init__(self, t): self._t = t @property def t(self): return self._t # End of class Type # In use: def foo(a: Type(int)): pass """ I haven't thought too much more about it, but I'm sure someone else can come up with a better way of doing it. The basic idea is that the name of the class and the fact that the argument is the type in question is always known; a static analyzer can scan for it fairly easily. If the type of the annotation is not Type, then you know something else is going on, and the analyzer can skip it. This avoids the question of 'is this dict the type, or is there something inside of it that also includes the type?' This can be further extended using decorators that generate dynamic checking code in debug mode, and are no-ops in normal mode (accessible by a command-line switch or something). I'm not sure at them moment how to handle having both Type objects and other information. I suspect having a catch-all *args, **kwargs in the init is going to be the least bad solution, but other ideas are welcome! Thanks, Cem Karan
Cem Karan
[…] I feel like annotations have the capability to be MUCH more useful than just as a place to hold type hints.
They have that capability. How long do you propose we wait for this capability to be realised? Function annotations have been part of Python since version 3.0, and Guido (IIUC) is deciding that the wait is over: we already have the best use of function annotations we're going to get. -- \ “I went to the hardware store and bought some used paint. It | `\ was in the shape of a house.” —Steven Wright | _o__) | Ben Finney
On Jan 17, 2015, at 9:27 PM, Ben Finney
Cem Karan
writes: […] I feel like annotations have the capability to be MUCH more useful than just as a place to hold type hints.
They have that capability. How long do you propose we wait for this capability to be realised?
Function annotations have been part of Python since version 3.0, and Guido (IIUC) is deciding that the wait is over: we already have the best use of function annotations we're going to get.
I understand what you're saying, and you're right that they are not used much. I only learned about them after I started playing with Python 3.3. But then I'm the bleeding edge for my group at work; everyone else is down in the python 2 world somewhere (some of them are WAY down there somewhere). Changing a language takes time. Do you program in C? Do you still use pthreads? Why? C11 specifies the thread.h header, you should switch to C11 threads right now! Except... many compilers still don't fully support C11, so we're coding to an earlier standard. There is a lot of code that already works as it is. Etc., etc. etc... When a language changes, there is a LOT of catching up to do. First, tools need to support the new facilities and then programmers need to become aware of new facilities, and they have to realize there is a legitimate use for those facilities. Most people I know are unaware that annotations exist; that is an argument for making the change to supporting typing only, simply because there will be fewer people that are affected. At the same time, if someone has a really good idea that could be layered into annotations WELL, they will be hampered by a proposal that locks them out. If typing is done via a Typing class like I mentioned in my last email, then there is a very simple way of determining if we're looking at a type or at something else. There may be other, better ways of layering it in that don't lock other uses out, and which are better suited for static analysis. I'm just asking for a very simple, very clear method of distinguishing uses when parsing code. Types can get complicated quickly, and ad-hoc rules to determine if we're looking at a type, or at something else, can get us in trouble. Simply turning it on or off for a chunk of code also sucks; I LIKE types, so I'd like to put them into my code, but if I have a way of adding documentation to my code as well, then I'd like to do that in addition to the types. There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck. Does all this make sense? Thanks, Cem Karan
On Mon, Jan 19, 2015 at 12:37 AM, Cem Karan
There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
I see a lot of vague "but what if there's something else", and not a lot of concrete "here's a use of annotations that would be locked out". Does anyone actually have another use case that would be seriously harmed by this kind of conflict? Remember, you can simply not use type hints for the function(s) that use other annotations; the conflict is only if you're trying to use both on the same function. ChrisA
On Sun Jan 18 2015 at 3:16:47 PM Chris Angelico
There may be other uses of annotations that programmers haven't yet
On Mon, Jan 19, 2015 at 12:37 AM, Cem Karan
wrote: thought of, simply because they don't know that annotations exist. Locking out those uses would suck. I see a lot of vague "but what if there's something else", and not a lot of concrete "here's a use of annotations that would be locked out". Does anyone actually have another use case that would be seriously harmed by this kind of conflict?
I use annotations in clize, a CLI argument parser, to specify how functions parameters should translate to CLI parameters. This can range from specifying coercion functions/callables for the argument values(ie. int), which could easily be mixed up with typing information as specified here, to specifying aliases for named parameters, marking parameters as undocumented, or simply overriding the translation process altogether for that parameter. Now I could simply instruct users to never use annotations and always use a decorator instead (in fact, I recommend it when Python 2.x compatibility is wished) and have that decorator dump this information elsewhere than in f.__annotations__, but that begs the question of why it isn't as worthy of benefiting from annotation syntax[1] as type-checking. If anything, this is a use of annotations at run-time versus a use at dev-time in an offline fashion. I also find it a bit hasty to proclaim no one has found other viable uses for annotations when Python 3 adoption and thus liberty to use annotations still remains poor.
Remember, you can simply not use type hints for the function(s) that use other annotations; the conflict is only if you're trying to use both on the same function.
If typing is to become the default interpretation of annotations, I fear that this will really just turn into "WTF, your annotations aren't type info, fix it", especially given the recursive nature of those checks. Why can't it be explicit that a function's annotations are type info that should be checked? "typing.check(func)", "@typing.check def func(...):", "import typing; typing.check_all()", "import typing" not followed by "typing.nevermind()", anything? [1] I guess I could write a decorator that moves annotations out of __annotations__ into another dict, but I'm not sure that would be relevant at all to a *static* analyzer which wouldn't have to care what dict things end up in. Again, illustrates the weirdness of having this run-time feature be reserved for static analysis.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Jan 18, 2015, at 7:32, Yann Kaiser
On Sun Jan 18 2015 at 3:16:47 PM Chris Angelico
wrote: On Mon, Jan 19, 2015 at 12:37 AM, Cem Karan
wrote: There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
I see a lot of vague "but what if there's something else", and not a lot of concrete "here's a use of annotations that would be locked out". Does anyone actually have another use case that would be seriously harmed by this kind of conflict?
I use annotations in clize, a CLI argument parser, to specify how functions parameters should translate to CLI parameters. This can range from specifying coercion functions/callables for the argument values(ie. int), which could easily be mixed up with typing information as specified here, to specifying aliases for named parameters, marking parameters as undocumented, or simply overriding the translation process altogether for that parameter.
And I can't imagine that you'd ever want to use static types and clize annotations on the same function, given that the whole point of clize is that the functions will be called dynamically by some dispatcher with arguments from the command line. So, this is a perfect example for why we need a way to mark a function as "these annotations don't mean types". Which is already part of the PEP. (It's also, maybe not coincidentally, another example of the fact that most existing uses of annotations that aren't types are "type-like"--e.g., C types, conversion functions, etc.) But it's not an example for why we need to mix two kinds of annotations in the same function, or where it would be unclear which annotations you're using, which is what Chris was asking for.
Now I could simply instruct users to never use annotations and always use a decorator instead (in fact, I recommend it when Python 2.x compatibility is wished) and have that decorator dump this information elsewhere than in f.__annotations__, but that begs the question of why it isn't as worthy of benefiting from annotation syntax[1] as type-checking. If anything, this is a use of annotations at run-time versus a use at dev-time in an offline fashion.
The obvious answer is to tell users to decorate their CLI functions with @no_type_check, or just not type-check scripts made up of clize functions. I'm willing to bet, without searching, that clize already uses a decorator for cli functions. This implies a possible useful extension to the PEP: a way to declare decorators as implying no_type_check. Then your users wouldn't have to do anything; you'd just declare @clize as meaning no type checking, and their existing code would be type-checkable with no changes.
I also find it a bit hasty to proclaim no one has found other viable uses for annotations when Python 3 adoption and thus liberty to use annotations still remains poor.
This is an argument for not evolving Python 3 at all until everyone switches. And it's not a very good argument, because the only reason for anyone to switch to Python 3 is that it's evolved features that they want. It's also neglecting the fact that Python 3 adoption really isn't that poor. In 2012, many libraries still didn't work in 3.x, any novice question on StackOverflow could be safely assumed to be 2.x-specific, and 2.7 was the obvious and most popular choice for new projects. In 2015, none of that is true anymore. Someone who primarily works on existing applications may not see that, because for an existing application, the cost of moving to 3.x is higher and the benefit lower (you can't design your app to take advantage of new features when it's already designed). But then someone who primarily works on existing applications also has less useful input into the way the language should change. Cem made this even more explicit by bringing up C11: if some code can't assume 2011 for C, no code should assume 2010 for Python. That's ridiculous. C is one of the most stable and conservative languages that people are still using. It also tends to take years for new C features to make it into implementations, because of the way C is designed (a committee works out a definition down to the smallest details, votes on it, publishes it, and then compilers implement it), whereas in Python--as with most other languages--it takes a single release cycle (under 18 months) for every new feature to make it into the implementation(s) that 90% of people are using. A better comparison would be Java 7, ECMAScript 5, or Ruby 1.9. These were all relatively big changes to their languages that happened within a year of Python 3. And nobody would say you can't judge the new features in those languages yet.
Remember, you can simply not use type hints for the function(s) that use other annotations; the conflict is only if you're trying to use both on the same function.
If typing is to become the default interpretation of annotations, I fear that this will really just turn into "WTF, your annotations aren't type info, fix it", especially given the recursive nature of those checks.
Why do you fear that? Default just means default. Escape mechanisms that are only meant to be used in rare cases are usually hidden away and ugly; those that are simple and obvious are meant to be used. (At least in Python; not every language is as nice...)
Why can't it be explicit that a function's annotations are type info that should be checked?
Because it's the less common uses that should be explicitly marked. There's cognitive value in marking something uncommon and none in marking something common. Think of how much boilerplate you're suggesting be added to 80% of code, all for the benefit of saving (at most) the same amount of boilerplate for 20%.
On Jan 18, 2015, at 9:16 AM, Chris Angelico
On Mon, Jan 19, 2015 at 12:37 AM, Cem Karan
wrote: There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
I see a lot of vague "but what if there's something else", and not a lot of concrete "here's a use of annotations that would be locked out". Does anyone actually have another use case that would be seriously harmed by this kind of conflict? Remember, you can simply not use type hints for the function(s) that use other annotations; the conflict is only if you're trying to use both on the same function.
def foo(a: {Type(int), Doc("some doc")}, b): pass vs. def foo(a, b): """ :param a: some doc :type a: int """ pass With annotations, the documentation lives with the argument. If typing rules all, then you might accidentally have the following: def foo(a: int, b): """ :param a: some doc :type a: str <- Whoops! Type conflict! """ pass Or the ugly but workable: def foo(a: int, b): """ :param a: some doc """ pass I'm arguing that with annotations we can put a lot more information in about a given function argument/return value than just typing. Typing and documentation are two things that I can think of off the top of my head. I can't think of others off of the top of my head, but I suspect that is due to my lack of imagination and not the lack of potential. Thanks, Cem Karan
On Jan 18, 2015, at 14:24, Cem Karan
On Jan 18, 2015, at 9:16 AM, Chris Angelico
wrote: On Mon, Jan 19, 2015 at 12:37 AM, Cem Karan
wrote: There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
I see a lot of vague "but what if there's something else", and not a lot of concrete "here's a use of annotations that would be locked out". Does anyone actually have another use case that would be seriously harmed by this kind of conflict? Remember, you can simply not use type hints for the function(s) that use other annotations; the conflict is only if you're trying to use both on the same function.
def foo(a: {Type(int), Doc("some doc")}, b): pass
vs.
def foo(a, b): """ :param a: some doc :type a: int """ pass
With annotations, the documentation lives with the argument. If typing rules all, then you might accidentally have the following:
def foo(a: int, b): """ :param a: some doc :type a: str <- Whoops! Type conflict! """ pass
Or the ugly but workable:
def foo(a: int, b): """ :param a: some doc """ pass
What's ugly about that? Besides being consistent with a wide variety of other languages, it puts signature information in the signature and documentation in the docstring, which seems like the obvious right place for everything.
I'm arguing that with annotations we can put a lot more information in about a given function argument/return value than just typing.
Denser doesn't always mean better. One piece of simple information is easy to read; a lot more information implies a lot more mental processing. Plus, you're ignoring the fact that you're substantially increasing boilerplate, to the point where I think you're not even increasing brevity and density in the first place. For the (hopefully common) case of simple types, having to stick the type in a constructor and then stick that constructor in a set vastly increases how much space it takes to represent the type, and how much mental effort it takes to process it. Just compare: def foo(a: int, b): def foo(a: {Type(int)}, b): That's 160% boilerplate, and it includes two levels of punctuated syntax that pushes the limits of what you can read and process without having to think it through--and that's the simplest possible case. If we did things your way, I think people would avoid annotating simple cases, and clamor for a way to hide other people's annotations when viewing help.
Typing and documentation are two things that I can think of off the top of my head. I can't think of others off of the top of my head, but I suspect that is due to my lack of imagination and not the lack of potential.
Don't sell yourself short. They're the exact same two things that Collin Winter and the collective members of the python-3000 list came up with back in mid-2006 on the way to PEP 3107. And the same two things people have come up with in the nearly decade since. So either there is no obvious wider potential, or the whole Python community is short on imagination. Obviously PEP 484's type hints are a narrower subset of all the potential kinds of typing information suggested in PEP 3107, but if the question is how to unify multiple different typing systems, I think it's pretty clear that a general "lots of separate things in parallel" solution isn't going to be the answer. (Would anyone want to see {typing.Type(int), cython.Type(cython.int32), xrpc.Type(xrpc.JSONNumber)} as an annotation?)
On Jan 18, 2015, at 7:08 PM, Andrew Barnert
On Jan 18, 2015, at 14:24, Cem Karan
wrote: On Jan 18, 2015, at 9:16 AM, Chris Angelico
wrote: On Mon, Jan 19, 2015 at 12:37 AM, Cem Karan
wrote: There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
I see a lot of vague "but what if there's something else", and not a lot of concrete "here's a use of annotations that would be locked out". Does anyone actually have another use case that would be seriously harmed by this kind of conflict? Remember, you can simply not use type hints for the function(s) that use other annotations; the conflict is only if you're trying to use both on the same function.
def foo(a: {Type(int), Doc("some doc")}, b): pass
vs.
def foo(a, b): """ :param a: some doc :type a: int """ pass
With annotations, the documentation lives with the argument. If typing rules all, then you might accidentally have the following:
def foo(a: int, b): """ :param a: some doc :type a: str <- Whoops! Type conflict! """ pass
Or the ugly but workable:
def foo(a: int, b): """ :param a: some doc """ pass
What's ugly about that? Besides being consistent with a wide variety of other languages, it puts signature information in the signature and documentation in the docstring, which seems like the obvious right place for everything.
Ugly in the sense that information is spread out. That said, you are right that it is more consistent with what other languages do.
I'm arguing that with annotations we can put a lot more information in about a given function argument/return value than just typing.
Denser doesn't always mean better. One piece of simple information is easy to read; a lot more information implies a lot more mental processing.
You're right about that, it can be more difficult. However, hunting for information can also be difficult.
Plus, you're ignoring the fact that you're substantially increasing boilerplate, to the point where I think you're not even increasing brevity and density in the first place. For the (hopefully common) case of simple types, having to stick the type in a constructor and then stick that constructor in a set vastly increases how much space it takes to represent the type, and how much mental effort it takes to process it. Just compare:
def foo(a: int, b):
def foo(a: {Type(int)}, b):
That's 160% boilerplate, and it includes two levels of punctuated syntax that pushes the limits of what you can read and process without having to think it through--and that's the simplest possible case. If we did things your way, I think people would avoid annotating simple cases, and clamor for a way to hide other people's annotations when viewing help.
You're right about that. It IS a lot uglier. But like I said, its one saving grace is that it is clear what the annotation is; it is a type, and it is intended for type checking. There isn't any guessing, and there is very fine-grained control over where and when type checking is done. As an alternative, if all basic types had equivalents in the typing library, you could skip the container above. In short: from typing import Int def foo(a: Int, b): # Simple type checking. def foo(a: {Int, Doc("some doc")}, b): # Something more clever. This reduces the boilerplate while still making it possible to determine if we're looking at a type check or something else.
Typing and documentation are two things that I can think of off the top of my head. I can't think of others off of the top of my head, but I suspect that is due to my lack of imagination and not the lack of potential.
Don't sell yourself short. They're the exact same two things that Collin Winter and the collective members of the python-3000 list came up with back in mid-2006 on the way to PEP 3107. And the same two things people have come up with in the nearly decade since. So either there is no obvious wider potential, or the whole Python community is short on imagination.
Obviously PEP 484's type hints are a narrower subset of all the potential kinds of typing information suggested in PEP 3107, but if the question is how to unify multiple different typing systems, I think it's pretty clear that a general "lots of separate things in parallel" solution isn't going to be the answer. (Would anyone want to see {typing.Type(int), cython.Type(cython.int32), xrpc.Type(xrpc.JSONNumber)} as an annotation?)
Only in sort of morbid fascination, kind of like the winners of the obfuscated C code contest... Thanks, Cem Karan
On Sun, Jan 18, 2015 at 05:24:51PM -0500, Cem Karan wrote:
def foo(a: {Type(int), Doc("some doc")}, b): pass
I dislike this, because there is too much in the function signature. I could live with this: def foo(a:int, b): pass or *possibly* this: def foo(a:"some doc", b): pass but not both together. Combining the two doesn't look too bad for a toy example like made-up "foo", but think about a real-world example: # from the statistics module def median_grouped( data: {Type(Iterable[Real]), Doc("grouped continuous data")}, interval: {Type(Real), Doc("class interval")} = 1 ) -> {Type(Real), Doc( "50th percentile (median) of grouped continuous data")}: pass That may be machine-readable, but it is too complicated for me to read, and I wrote it! And that's only a simple function with a mere two parameters and simple one-liner docs. Having written that, I still need to write a doc string, and almost certainly I have to duplicate the annotation docs inside the docstring. So it isn't really saving me much, if anything, over this: @Doc({"data": "grouped continuous data", "interval": "class interval", "return": "50th percentile (median) of grouped continuous data" }) def median_grouped(data:Iterable[Real], interval:Type(Real)=1)->Real: pass It's not *ideal* to have to duplicate the parameter names in the decorator, but except in the most trivial toy cases, I can't have it all: - type annotations - and document annotations - without being too verbose or complex - and still easy to read and write and maintain Something has to give, and since type-hinting is deemed to be the primary use for annotations, it has to be the document annotations. You can still have both type-hints and docs, you just can't use annotations for them both. (But see below.) - If you want to use annotations for documentation, then you cannot use them for type-hints at the same time; - if you want to use annotations for type-hints, then you need another method of dealing with the documentation. Since you almost certainly will need to write a docstring anyway, the best solution in my mind is to move the documentation into the docstring: def median_grouped(data:Iterable[Real], interval:Type(Real)=1)->Real: """Return the 50th percentile (median) of grouped continuous data. :param a: grouped continuous data :param interval: class interval more documentation and docstrings follows... """
With annotations, the documentation lives with the argument. If typing rules all, then you might accidentally have the following:
def foo(a: int, b): """ :param a: some doc :type a: str <- Whoops! Type conflict! """ pass
And that is best solved by having your linter understand the convention for docstrings and flag the error.
I'm arguing that with annotations we can put a lot more information in about a given function argument/return value than just typing.
Of course you can. But I think you shouldn't. You are absolutely free to disagree. Remember, use of the type-checker is optional, and the type-checker itself will be a third-party tool. Tools actually. I expect there will be much competition to develop the best, most powerful type-checker. There is nothing stopping people (including yourself) writing a type-checker which supports your multiple-annotations-in-a-set idea. If it proves itself as a viable, and popular, alternative, then you can come back and try to convince Guido to give it his blessing as officially sanctioned (at which point all the other type-checkers will have to support sets of annotations as well). I *do* believe that it is technically possible to have the type-checker support your {Type(...), Spam(...)} idea, but I *don't* believe that it will be practical or popular. I think it is a case of YAGNI combined with EIYDNIYSDI (Even If You Do Need It You Shouldn't Do It). But I might be wrong, and I encourage you to prove me wrong the only way that really matters: with working code that gets used in the real world. -- Steven
On Jan 18, 2015, at 8:58 PM, Steven D'Aprano
On Sun, Jan 18, 2015 at 05:24:51PM -0500, Cem Karan wrote:
def foo(a: {Type(int), Doc("some doc")}, b): pass
I dislike this, because there is too much in the function signature. I could live with this:
def foo(a:int, b): pass
or *possibly* this:
def foo(a:"some doc", b): pass
but not both together. Combining the two doesn't look too bad for a toy example like made-up "foo", but think about a real-world example:
# from the statistics module def median_grouped( data: {Type(Iterable[Real]), Doc("grouped continuous data")}, interval: {Type(Real), Doc("class interval")} = 1 ) -> {Type(Real), Doc( "50th percentile (median) of grouped continuous data")}: pass
That may be machine-readable, but it is too complicated for me to read, and I wrote it! And that's only a simple function with a mere two parameters and simple one-liner docs.
Having written that, I still need to write a doc string, and almost certainly I have to duplicate the annotation docs inside the docstring. So it isn't really saving me much, if anything, over this:
@Doc({"data": "grouped continuous data", "interval": "class interval", "return": "50th percentile (median) of grouped continuous data" }) def median_grouped(data:Iterable[Real], interval:Type(Real)=1)->Real: pass
I see your point, that was not fun to read.
It's not *ideal* to have to duplicate the parameter names in the decorator, but except in the most trivial toy cases, I can't have it all:
- type annotations - and document annotations - without being too verbose or complex - and still easy to read and write and maintain
Something has to give, and since type-hinting is deemed to be the primary use for annotations, it has to be the document annotations. You can still have both type-hints and docs, you just can't use annotations for them both. (But see below.)
- If you want to use annotations for documentation, then you cannot use them for type-hints at the same time; - if you want to use annotations for type-hints, then you need another method of dealing with the documentation.
Since you almost certainly will need to write a docstring anyway, the best solution in my mind is to move the documentation into the docstring:
def median_grouped(data:Iterable[Real], interval:Type(Real)=1)->Real: """Return the 50th percentile (median) of grouped continuous data.
:param a: grouped continuous data :param interval: class interval
more documentation and docstrings follows... """
OK, so should there be a convention for writing docstrings?
With annotations, the documentation lives with the argument. If typing rules all, then you might accidentally have the following:
def foo(a: int, b): """ :param a: some doc :type a: str <- Whoops! Type conflict! """ pass
And that is best solved by having your linter understand the convention for docstrings and flag the error.
That implies a convention for docstrings is a good idea...
I'm arguing that with annotations we can put a lot more information in about a given function argument/return value than just typing.
Of course you can. But I think you shouldn't.
You are absolutely free to disagree. Remember, use of the type-checker is optional, and the type-checker itself will be a third-party tool. Tools actually. I expect there will be much competition to develop the best, most powerful type-checker.
There is nothing stopping people (including yourself) writing a type-checker which supports your multiple-annotations-in-a-set idea. If it proves itself as a viable, and popular, alternative, then you can come back and try to convince Guido to give it his blessing as officially sanctioned (at which point all the other type-checkers will have to support sets of annotations as well).
I *do* believe that it is technically possible to have the type-checker support your {Type(...), Spam(...)} idea, but I *don't* believe that it will be practical or popular. I think it is a case of YAGNI combined with EIYDNIYSDI (Even If You Do Need It You Shouldn't Do It). But I might be wrong, and I encourage you to prove me wrong the only way that really matters: with working code that gets used in the real world.
You're right. I'll have to work on it. Thanks, Cem Karan
Cem Karan
OK, so should there be a convention for writing docstrings?
The current convention is to use Sphinx to render a reStructuredText docstring, and write the API of a class or function with reStructuredText field lists. URL:http://sphinx-doc.org/domains.html#the-python-domain URL:http://sphinx-doc.org/domains.html#info-field-lists Python's own documentation, as well as a large amount of third-party packages, are written this way. -- \ “It is seldom that liberty of any kind is lost all at once.” | `\ —David Hume | _o__) | Ben Finney
There's also PEP 0257 for docstring formatting.
On Mon, Jan 19, 2015 at 12:51 PM, Ben Finney
Cem Karan
writes: OK, so should there be a convention for writing docstrings?
The current convention is to use Sphinx to render a reStructuredText docstring, and write the API of a class or function with reStructuredText field lists.
URL:http://sphinx-doc.org/domains.html#the-python-domain URL:http://sphinx-doc.org/domains.html#info-field-lists
Python's own documentation, as well as a large amount of third-party packages, are written this way.
-- \ “It is seldom that liberty of any kind is lost all at once.” | `\ —David Hume | _o__) | Ben Finney
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Unfortunately PEP 257 falls short on specifying how to describe arguments
-- it has only one example, it's not normative, and there's not much code
that follows the example. The reST conventions are more common, but the
stdlib itself rarely uses them. It would be nice to come up with a better
convention that takes PEP 484 into account (so doc generators can
incorporate the argument type annotations into the generated output, merged
with per-argument from the docstring).
On Mon, Jan 19, 2015 at 11:25 AM, Ian Cordasco
There's also PEP 0257 for docstring formatting.
On Mon, Jan 19, 2015 at 12:51 PM, Ben Finney
wrote: Cem Karan
writes: OK, so should there be a convention for writing docstrings?
The current convention is to use Sphinx to render a reStructuredText docstring, and write the API of a class or function with reStructuredText field lists.
URL:http://sphinx-doc.org/domains.html#the-python-domain URL:http://sphinx-doc.org/domains.html#info-field-lists
Python's own documentation, as well as a large amount of third-party packages, are written this way.
-- \ “It is seldom that liberty of any kind is lost all at once.” | `\ —David Hume | _o__) | Ben Finney
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
On 01/19/2015 08:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them. It would be nice to come up with a better convention that takes PEP 484 into account (so doc generators can incorporate the argument type annotations into the generated output, merged with per-argument from the docstring).
I'll be happy to implement that. Basically if you use :param x: description :type x: type you could just leave out the "type" line and have it generated from the annotation. I guess it's very similar for Google and numpy style docstrings, which are supported by the (as of 1.3 built-in) Napoleon extension. Georg
"Napoleon"?
On Mon, Jan 19, 2015 at 12:48 PM, Georg Brandl
On 01/19/2015 08:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them. It would be nice to come up with a better convention that takes PEP 484 into account (so doc generators can incorporate the argument type annotations into the generated output, merged with per-argument from the docstring).
I'll be happy to implement that. Basically if you use
:param x: description :type x: type
you could just leave out the "type" line and have it generated from the annotation.
I guess it's very similar for Google and numpy style docstrings, which are supported by the (as of 1.3 built-in) Napoleon extension.
Georg
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
On Mon, Jan 19, 2015 at 2:08 PM, Guido van Rossum
"Napoleon"?
https://pypi.python.org/pypi/sphinxcontrib-napoleon -- Devin
On Mon, Jan 19, 2015 at 12:48 PM, Georg Brandl
wrote: On 01/19/2015 08:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them. It would be nice to come up with a better convention that takes PEP 484 into account (so doc generators can incorporate the argument type annotations into the generated output, merged with per-argument from the docstring).
I'll be happy to implement that. Basically if you use
:param x: description :type x: type
you could just leave out the "type" line and have it generated from the annotation.
I guess it's very similar for Google and numpy style docstrings, which are supported by the (as of 1.3 built-in) Napoleon extension.
Georg
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
I have am idea: how about including a @paramdoc decorator that accepts one
named string argument per parameter and appends information gathered via
type annotations and default values to the docstring?
E.g.
~~~~
@paramdoc(
main_course="Meaty because our society is medieval",
side_dish="Optional, meat is enough",
__return__="Objective opinion")
def eat(main_course: Meat, side_dish=None: Food) -> Opinion:
"""
Decides how well the meat and side dish taste together
"""
...
~~~~
After this, eat.__doc__ would be an object that retains the raw parts as
properties, but otherwise is an elongated version of the original
docstring. Its fourth and third to last line would read something like this:
~~~~
side_dish (Food; defaults to None):
Optional, meat is enough
~~~~
What do you think?
Guido van Rossum
Try to implement it and see how well it works.
On Mon, Jan 19, 2015 at 2:14 PM, Philipp A.
I have am idea: how about including a @paramdoc decorator that accepts one named string argument per parameter and appends information gathered via type annotations and default values to the docstring?
E.g.
~~~~ @paramdoc( main_course="Meaty because our society is medieval", side_dish="Optional, meat is enough", __return__="Objective opinion") def eat(main_course: Meat, side_dish=None: Food) -> Opinion: """ Decides how well the meat and side dish taste together """ ... ~~~~
After this, eat.__doc__ would be an object that retains the raw parts as properties, but otherwise is an elongated version of the original docstring. Its fourth and third to last line would read something like this:
~~~~ side_dish (Food; defaults to None): Optional, meat is enough ~~~~
What do you think?
Guido van Rossum
@ python.org > schrieb am Mo., 19. Jan. 2015 20:56:
-- --Guido van Rossum (python.org/~guido)
On Jan 19, 2015, at 2:16 PM, "Philipp A."
On Jan 19, 2015, at 17:04, Chris Barker - NOAA Federal
On Jan 19, 2015, at 2:16 PM, "Philipp A."
wrote: I have am idea: how about including a @paramdoc decorator
I always teach that the @ syntax is a decoration, not a decorator, whereas a decorator is a function that takes a function and returns another function ( usually a customized version of the passed in function). This distinction between decorators and decoration syntax keeps the door open to do just about anything with decorations, but am I the only one that thinks it's a bad idea to have it be for "any old thing we want to hang off a function"?
But his paramdoc(…) is clearly a function that takes a function and returns a customized version of the function passed in--hence a decorator. And I don't know what distinction you're trying to make. I can guess at a few possibilities, but none of them work. Sure, paramdoc itself is a function that takes some arguments and returns a decorator function, but the same is true for all kinds of things we normally call decorators, like lru_cache and partial. And the function is customized by having a different doc string instead of, say, different parameter annotations or a closure that runs some extra code before the main code, but I don't see how that makes a difference. And the decorator would likely be implemented by mutating and returning the function instead of creating and returning a wrapper, but that's just an implementation detail that's irrelevant to the user (and wraps and many other well-known decorators work that way).
Let's keep decorations for decorators...
-Chris
that accepts one named string argument per parameter and appends information gathered via type annotations and default values to the docstring?
E.g.
~~~~ @paramdoc( main_course="Meaty because our society is medieval", side_dish="Optional, meat is enough", __return__="Objective opinion") def eat(main_course: Meat, side_dish=None: Food) -> Opinion: """ Decides how well the meat and side dish taste together """ ... ~~~~
After this, eat.__doc__ would be an object that retains the raw parts as properties, but otherwise is an elongated version of the original docstring. Its fourth and third to last line would read something like this:
~~~~ side_dish (Food; defaults to None): Optional, meat is enough ~~~~
What do you think?
Guido van Rossum
schrieb am Mo., 19. Jan. 2015 20:56: _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Mon, Jan 19, 2015 at 5:04 PM, Chris Barker - NOAA Federal < chris.barker@noaa.gov> wrote:
I always teach that the @ syntax is a decoration, not a decorator, whereas a decorator is a function that takes a function and returns another function ( usually a customized version of the passed in function). This distinction between decorators and decoration syntax keeps the door open to do just about anything with decorations, but am I the only one that thinks it's a bad idea to have it be for "any old thing we want to hang off a function"?
I think you're the only one who makes this distinction. In common use "decorator" is used to describe both the syntax and the function invoked by the syntax. "Decoration" is never (well, very rarely) used. And calling any function that takes a function and returns one a decorator feels overreaching -- I'd only call it a decorator if it is intended to use with the decorator syntax. -- --Guido van Rossum (python.org/~guido)
On Mon, Jan 19, 2015 at 8:42 PM, Guido van Rossum
On Mon, Jan 19, 2015 at 5:04 PM, Chris Barker - NOAA Federal < chris.barker@noaa.gov> wrote:
I always teach that the @ syntax is a decoration, not a decorator, whereas a decorator is a function that takes a function and returns another function ( usually a customized version of the passed in function). This distinction between decorators and decoration syntax keeps the door open to do just about anything with decorations, but am I the only one that thinks it's a bad idea to have it be for "any old thing we want to hang off a function"?
I think you're the only one who makes this distinction.
No, I'm not -- take a look at any number of tutorial sites, and answers for newbies on SO, etc. I used to teach decorators without making the distinction, and got a lot of confused students. maybe the distinction is only useful for pedagogical purposes, but I introduced it here 'cause it's a bit harder to talk about when a "decorator" can be either a function the behaves a certain way or the @something line in the code.
In common use "decorator" is used to describe both the syntax and the function invoked by the syntax. "Decoration" is never (well, very rarely) used. And calling any function that takes a function and returns one a decorator feels overreaching -- I'd only call it a decorator if it is intended to use with the decorator syntax.
sure -- I'm having a hard time coming up with the word s to describe what I"m talking about as "proper" decorator. The truth is, you can put any callable that takes at least one argument after that @. And I'm not suggesting that python should enforce anything in particular about that. What I am suggesting is that we should not officially suggest that you should use a decorator for any old thing that doesn't fit the usual sprit of decorators. A decorator that simply adds stuff to the docstring feels pretty ugly to me, particular if it goes beyond docs to specifying something important about the typing, etc. Hard to draw a line there, but I think we should keep the spirit of decorators in mind. But enough said. -Chris -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
On Tue, Jan 20, 2015 at 8:28 AM, Chris Barker
On Mon, Jan 19, 2015 at 8:42 PM, Guido van Rossum
wrote: On Mon, Jan 19, 2015 at 5:04 PM, Chris Barker - NOAA Federal < chris.barker@noaa.gov> wrote:
I always teach that the @ syntax is a decoration, not a decorator, whereas a decorator is a function that takes a function and returns another function ( usually a customized version of the passed in function). This distinction between decorators and decoration syntax keeps the door open to do just about anything with decorations, but am I the only one that thinks it's a bad idea to have it be for "any old thing we want to hang off a function"?
I think you're the only one who makes this distinction.
No, I'm not -- take a look at any number of tutorial sites, and answers for newbies on SO, etc. I used to teach decorators without making the distinction, and got a lot of confused students. maybe the distinction is only useful for pedagogical purposes, but I introduced it here 'cause it's a bit harder to talk about when a "decorator" can be either a function the behaves a certain way or the @something line in the code.
It's a fair cop.
In common use "decorator" is used to describe both the syntax and the
function invoked by the syntax. "Decoration" is never (well, very rarely) used. And calling any function that takes a function and returns one a decorator feels overreaching -- I'd only call it a decorator if it is intended to use with the decorator syntax.
sure -- I'm having a hard time coming up with the word s to describe what I"m talking about as "proper" decorator. The truth is, you can put any callable that takes at least one argument after that @. And I'm not suggesting that python should enforce anything in particular about that. What I am suggesting is that we should not officially suggest that you should use a decorator for any old thing that doesn't fit the usual sprit of decorators.
Agreed. The 99% case is that a decorator returns a function that (with some qualifications) calls the decorated function.
A decorator that simply adds stuff to the docstring feels pretty ugly to me, particular if it goes beyond docs to specifying something important about the typing, etc.
This sounds totally fine to me, honestly.
Hard to draw a line there, but I think we should keep the spirit of decorators in mind.
But enough said.
Amen. -- --Guido van Rossum (python.org/~guido)
On Mon, Jan 19, 2015 at 2:14 PM, Philipp A.
I have am idea: how about including a @paramdoc decorator that accepts one named string argument per parameter and appends information gathered via type annotations and default values to the docstring?
E.g.
~~~~ @paramdoc( main_course="Meaty because our society is medieval", side_dish="Optional, meat is enough", __return__="Objective opinion") def eat(main_course: Meat, side_dish=None: Food) -> Opinion: """ Decides how well the meat and side dish taste together """ ...
aside from my thoughts about this being an odd use of the decorator syntax, I fail to see how this is better than simply putting the information in the docstring itself? You still are putting information in two places, making it easy for it to get out of sync, and I think it actually make sit harder to read -- that could end up being one really big decorator. -Chris -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
On 1/19/2015 2:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them.
Because I have not seen such used, except maybe once, I have been meaning to ask if I am allowed to use rst markup in stdlib docstrings?
It would be nice to come up with a better convention that takes PEP 484 into account (so doc generators can incorporate the argument type annotations into the generated output, merged with per-argument from the docstring).
My impression is that the plan is that stdlib .py files would not have type annotations, but that they might be in stub files. Still true? Would type hints be added to the signatures in .rst docs? -- Terry Jan Reedy
On Mon, Jan 19, 2015 at 10:39 PM, Terry Reedy
On 1/19/2015 2:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them.
Because I have not seen such used, except maybe once, I have been meaning to ask if I am allowed to use rst markup in stdlib docstrings?
I prefer you don't use them. We don't generate docs from the stdlib sources, and if someone generated them locally they would probably be doing themselves a disservice -- the docstrings exist for the benefit of the help() builtin, which AFAIK doesn't understand rst markup.
It would be nice to come
up with a better convention that takes PEP 484 into account (so doc generators can incorporate the argument type annotations into the generated output, merged with per-argument from the docstring).
My impression is that the plan is that stdlib .py files would not have type annotations, but that they might be in stub files. Still true?
Correct. The stdlib is a special flower in so many ways... (Stubs are also useful for 3rd party libraries that haven't been updated yet.)
Would type hints be added to the signatures in .rst docs?
I think you and Georg may have to decide what to do. -- --Guido van Rossum (python.org/~guido)
On Jan 20, 2015 5:32 PM, "Guido van Rossum"
On Mon, Jan 19, 2015 at 10:39 PM, Terry Reedy
wrote: On 1/19/2015 2:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them.
Because I have not seen such used, except maybe once, I have been
meaning to ask if I am allowed to use rst markup in stdlib docstrings?
I prefer you don't use them. We don't generate docs from the stdlib
sources, and if someone generated them locally they would probably be doing themselves a disservice -- the docstrings exist for the benefit of the help() builtin, which AFAIK doesn't understand rst markup.
Might the lack of rst support in help() be something to address either as part of this effort or in parallel with it?
IIUC there is no rst support in the stdlib, and including docutils sounds
like a bad idea (it's a huge unmaintained codebase).
On Tue, Jan 20, 2015 at 10:38 AM, Todd
On Jan 20, 2015 5:32 PM, "Guido van Rossum"
wrote: On Mon, Jan 19, 2015 at 10:39 PM, Terry Reedy
wrote: On 1/19/2015 2:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them.
Because I have not seen such used, except maybe once, I have been
meaning to ask if I am allowed to use rst markup in stdlib docstrings?
I prefer you don't use them. We don't generate docs from the stdlib
sources, and if someone generated them locally they would probably be doing themselves a disservice -- the docstrings exist for the benefit of the help() builtin, which AFAIK doesn't understand rst markup.
Might the lack of rst support in help() be something to address either as part of this effort or in parallel with it?
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
On 1/20/2015 11:31 AM, Guido van Rossum wrote:
On Mon, Jan 19, 2015 at 10:39 PM, Terry Reedy
Because I have not seen such used, except maybe once, I have been meaning to ask if I am allowed to use rst markup in stdlib docstrings?
I prefer you don't use them.
That's easy ;-).
My impression is that the plan is that stdlib .py files would not have type annotations, but that they might be in stub files. Still true?
Correct. The stdlib is a special flower in so many ways... (Stubs are also useful for 3rd party libraries that haven't been updated yet.)
My particular interest here is adding type information to the signature information already displayed in Idle calltips. It would be nice if the typing module (eventually) had a function to do that. It could be used by help(function) also. The point is to help people write correct calls in the first place, by providing easily accessed information, rather than wait for a checker or the runtime to find the error. -- Terry Jan Reedy
On Tue, Jan 20, 2015 at 6:57 PM, Terry Reedy
My particular interest here is adding type information to the signature information already displayed in Idle calltips. It would be nice if the typing module (eventually) had a function to do that. It could be used by help(function) also. The point is to help people write correct calls in the first place, by providing easily accessed information, rather than wait for a checker or the runtime to find the error.
That is indeed part of the plan. In first approximation, the repr() of an annotation (if it is one of the classes defined in typing.py, such as Union, Callable, or classes derived from Generic[...]) will be usable to describe the type. E.g. the repr() of Tuple[int, str] is exactly 'Tuple[int, str]'. (This works today in my prototype at https://github.com/ambv/typehinting/tree/master/prototyping .) There are some issues around forward references (not yet implemented) which mean that you probably have to use a simple helper function defined in typing.py to get the annotation, but it should be pretty smooth sailing. -- --Guido van Rossum (python.org/~guido)
On Tue, Jan 20, 2015 at 6:39 AM, Terry Reedy
On 1/19/2015 2:54 PM, Guido van Rossum wrote:
Unfortunately PEP 257 falls short on specifying how to describe arguments -- it has only one example, it's not normative, and there's not much code that follows the example. The reST conventions are more common, but the stdlib itself rarely uses them.
Because I have not seen such used, except maybe once, I have been meaning to ask if I am allowed to use rst markup in stdlib docstrings?
If you want a standard, then the Google and/or Numpy docstring standards are generally better than raw RST anyway: they're readable by humans -- no-one will necessarily even notice you're using a special format, esp. the Google standard -- and sphinx can parse them too in case that ever becomes important. https://google-styleguide.googlecode.com/svn/trunk/pyguide.html#Comments <- then click the little arrow https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#sectio... -- Nathaniel J. Smith Postdoctoral researcher - Informatics - University of Edinburgh http://vorpus.org
On Sun, Jan 18, 2015 at 5:37 AM, Cem Karan
On Jan 17, 2015, at 9:27 PM, Ben Finney
wrote: Cem Karan
writes: […] I feel like annotations have the capability to be MUCH more useful than just as a place to hold type hints.
They have that capability. How long do you propose we wait for this capability to be realised?
Function annotations have been part of Python since version 3.0, and Guido (IIUC) is deciding that the wait is over: we already have the best use of function annotations we're going to get.
I understand what you're saying, and you're right that they are not used much. I only learned about them after I started playing with Python 3.3. But then I'm the bleeding edge for my group at work; everyone else is down in the python 2 world somewhere (some of them are WAY down there somewhere).
Changing a language takes time. Do you program in C? Do you still use pthreads? Why? C11 specifies the thread.h header, you should switch to C11 threads right now! Except... many compilers still don't fully support C11, so we're coding to an earlier standard. There is a lot of code that already works as it is. Etc., etc. etc...
When a language changes, there is a LOT of catching up to do. First, tools need to support the new facilities and then programmers need to become aware of new facilities, and they have to realize there is a legitimate use for those facilities. Most people I know are unaware that annotations exist; that is an argument for making the change to supporting typing only, simply because there will be fewer people that are affected. At the same time, if someone has a really good idea that could be layered into annotations WELL, they will be hampered by a proposal that locks them out. If typing is done via a Typing class like I mentioned in my last email, then there is a very simple way of determining if we're looking at a type or at something else. There may be other, better ways of layering it in that don't lock other uses out, and which are better suited for static analysis.
I'm just asking for a very simple, very clear method of distinguishing uses when parsing code. Types can get complicated quickly, and ad-hoc rules to determine if we're looking at a type, or at something else, can get us in trouble. Simply turning it on or off for a chunk of code also sucks; I LIKE types, so I'd like to put them into my code, but if I have a way of adding documentation to my code as well, then I'd like to do that in addition to the types. There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
Does all this make sense?
I hear you, and I'm not buying it. I've thought about this a lot (more than you, I'm sure) and I'm putting a line in the sand: annotations are for types, and all other uses will eventually have to find some other way (most likely decorators). (The "type: OFF" comments are a transitional measure to avoid breaking other uses without warning in 3.5.) -- --Guido van Rossum (python.org/~guido)
On Jan 18, 2015, at 2:34 PM, Guido van Rossum
On Sun, Jan 18, 2015 at 5:37 AM, Cem Karan
wrote: On Jan 17, 2015, at 9:27 PM, Ben Finney
wrote: Cem Karan
writes: […] I feel like annotations have the capability to be MUCH more useful than just as a place to hold type hints.
They have that capability. How long do you propose we wait for this capability to be realised?
Function annotations have been part of Python since version 3.0, and Guido (IIUC) is deciding that the wait is over: we already have the best use of function annotations we're going to get.
I understand what you're saying, and you're right that they are not used much. I only learned about them after I started playing with Python 3.3. But then I'm the bleeding edge for my group at work; everyone else is down in the python 2 world somewhere (some of them are WAY down there somewhere).
Changing a language takes time. Do you program in C? Do you still use pthreads? Why? C11 specifies the thread.h header, you should switch to C11 threads right now! Except... many compilers still don't fully support C11, so we're coding to an earlier standard. There is a lot of code that already works as it is. Etc., etc. etc...
When a language changes, there is a LOT of catching up to do. First, tools need to support the new facilities and then programmers need to become aware of new facilities, and they have to realize there is a legitimate use for those facilities. Most people I know are unaware that annotations exist; that is an argument for making the change to supporting typing only, simply because there will be fewer people that are affected. At the same time, if someone has a really good idea that could be layered into annotations WELL, they will be hampered by a proposal that locks them out. If typing is done via a Typing class like I mentioned in my last email, then there is a very simple way of determining if we're looking at a type or at something else. There may be other, better ways of layering it in that don't lock other uses out, and which are better suited for static analysis.
I'm just asking for a very simple, very clear method of distinguishing uses when parsing code. Types can get complicated quickly, and ad-hoc rules to determine if we're looking at a type, or at something else, can get us in trouble. Simply turning it on or off for a chunk of code also sucks; I LIKE types, so I'd like to put them into my code, but if I have a way of adding documentation to my code as well, then I'd like to do that in addition to the types. There may be other uses of annotations that programmers haven't yet thought of, simply because they don't know that annotations exist. Locking out those uses would suck.
Does all this make sense?
I hear you, and I'm not buying it. I've thought about this a lot (more than you, I'm sure) and I'm putting a line in the sand: annotations are for types, and all other uses will eventually have to find some other way (most likely decorators). (The "type: OFF" comments are a transitional measure to avoid breaking other uses without warning in 3.5.)
As the BDFL, that is your right. But I would personally rather see this: @functools.lru_cache() def foo(a: {Type(int), Doc("some doc"), Something_else(4,5,6)}): """ Some block of documentation for the function as a whole that I'm too lazy to write out, so I'm just going to copy in a paragraph of `Lorem ipsum http://en.wikipedia.org/wiki/Ipse_lorum`_ to add as filler to break up the mental flow a bit. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ pass Then something like this: @functools.lru_cache() @Something_else(a, 4, 5, 6) def foo(a: int): """ Some block of documentation for the function as a whole that I'm too lazy to write out, so I'm just going to copy in a paragraph of `Lorem ipsum http://en.wikipedia.org/wiki/Ipse_lorum`_ to add as filler to break up the mental flow a bit. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. :param a: some doc """ pass Finally, consider what decorators are NOT required to do; they are not required to return the original object. That means that ordering can potentially matter: @Doc(a, "some doc") # Documenting the argument of the wrapped function @functools.lru_cache() # Creating a new function that wraps the original function @Something_else(a, 4, 5, 6) # Doing something to the argument of the original function def foo(a: int): pass versus @functools.lru_cache() # Wrapping the function @Something_else(a, 4, 5, 6) # Doing something to the argument of the original function @Doc(a, "some doc") # Documenting the argument of the function itself. def foo(a: int): pass So what do automatic documentation generators do in this case? More than likely, we want the second case, which means that someone needs to tell programmers what order to use the decorators in. What if Something_else returns a wrapped version of the function? You need to do this: @functools.lru_cache() # Wrapping the modified function @Something_else(a, 4, 5, 6) # Doing something to the argument of the modified function @Something_else(b, 4, 5, 6) # Doing something to the argument of the original function @Doc(a, "some doc") # Documenting the argument of the function itself. @Doc(b, "some other doc") # Documenting the argument of the function itself. def foo(a: int, b: str): pass instead of this: @functools.lru_cache() # Wrapping the modified, modified function @Doc(a, "some doc") # Documenting the argument of the modified version of the modified function. @Something_else(a, 4, 5, 6) # Doing something to the argument of the modified function @Doc(b, "some other doc") # Documenting the argument of the modified function. @Something_else(b, 4, 5, 6) # Doing something to the argument of the original function def foo(a: int, b: str): pass This starts to feel unnatural, and is definitely confusing. We can then put in place a whole bunch of rules regarding decorators, but then we've got two problems; forcing annotations to be for types only, and forcing new rules on decorators that weren't there in the first place. I know that something like "def foo(a: {Type(int), Doc("some doc"), Something_else(4,5,6)})" isn't pretty. But it is order-independent, it operates on the original function only, and it is pretty easy to parse, even for a static analyzer, AND there is no question that Type() means you want a type check done. Finally, annotations will still be open for other uses. Thanks, Cem Karan
On 19/01/2015 8:34 a.m., Guido van Rossum wrote:
I'm putting a line in the sand: annotations are for types
Do you mean *static* types, or types in general? If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of. If you've decided that static type checking is important enough to officially support, how about designing a new language feature specifically for that? We could, for example, borrow Haskell's :: symbol: class Duck: num_feathers :: int = 4200 def quack(volume :: float) ... The type expressions following :: would have the form of Python expressions, but they would not be evaluated. Introspecting on them at runtime could be supported, but you would get an AST instead of a value. -- Greg
On Sun, Jan 18, 2015 at 4:01 PM, Greg
On 19/01/2015 8:34 a.m., Guido van Rossum wrote:
I'm putting a line in the sand: annotations are for types
Do you mean *static* types, or types in general?
If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of.
If you've decided that static type checking is important enough to officially support, how about designing a new language feature specifically for that?
We could, for example, borrow Haskell's :: symbol:
class Duck:
num_feathers :: int = 4200
def quack(volume :: float) ...
The type expressions following :: would have the form of Python expressions, but they would not be evaluated. Introspecting on them at runtime could be supported, but you would get an AST instead of a value.
There is no time to get such a proposal implemented in time for Python 3.5. Since you are proposing entirely new syntax we could still do this later if we ended up finding that the mypy approach after all is not so useful. And abandoning the mypy-based proposal is easy -- the typing module will be provisional in 3.5, so in 3.6 we could just delete it from the stdlib if we decided we took a wrong turn. People who really liked it could still install it from PyPI. But I don't expect that to happen. I don't see the mypy approach as a waste of facilities evaluated at run time. I have found it a very liberating experience to realize that the type checker could just be a linter and there is an elegant way to avoid noise from the interaction between code that has type annotations and code that doesn't. -- --Guido van Rossum (python.org/~guido)
On Sun, Jan 18, 2015 at 4:01 PM, Greg
On 19/01/2015 8:34 a.m., Guido van Rossum wrote:
I'm putting a line in the sand: annotations are for types
Do you mean *static* types, or types in general?
If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of.
Strong +1. The current proposal is a historical accident, it would not be designed this way if it was designed from scratch. That's not a nice thing to include in the core language. (Although it does happen often, since "practicality beats purity"). I like the :: idea. -- Devin
On Jan 19, 2015, at 3:30 PM, Devin Jeanpierre
On Sun, Jan 18, 2015 at 4:01 PM, Greg
wrote: On 19/01/2015 8:34 a.m., Guido van Rossum wrote:
I'm putting a line in the sand: annotations are for types
Do you mean *static* types, or types in general?
If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of.
Strong +1. The current proposal is a historical accident, it would not be designed this way if it was designed from scratch. That's not a nice thing to include in the core language. (Although it does happen often, since "practicality beats purity").
I like the :: idea.
+1 on the :: idea. As for the typing system, I think the proposal is pretty much spot on. I know I've been harping on annotations being forcefully restricted to types only, but the proposal itself (the typing library, etc.) is a really good proposal. Unless Python suddenly stops being Python, it's not too likely that any typing system can handle all possible cases of what we can do in the language (at least, not without becoming insanely unwieldy). Thanks, Cem Karan
On Mon Jan 19 2015 at 10:53:52 PM Cem Karan
On Jan 19, 2015, at 3:30 PM, Devin Jeanpierre
wrote: On Sun, Jan 18, 2015 at 4:01 PM, Greg
wrote: On 19/01/2015 8:34 a.m., Guido van Rossum wrote:
I'm putting a line in the sand: annotations are for types
Do you mean *static* types, or types in general?
If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of.
Strong +1. The current proposal is a historical accident, it would not be designed this way if it was designed from scratch. That's not a nice thing to include in the core language. (Although it does happen often, since "practicality beats purity").
I like the :: idea.
+1 on the :: idea.
As for the typing system, I think the proposal is pretty much spot on. I know I've been harping on annotations being forcefully restricted to types only, but the proposal itself (the typing library, etc.) is a really good proposal. Unless Python suddenly stops being Python, it's not too likely that any typing system can handle all possible cases of what we can do in the language (at least, not without becoming insanely unwieldy).
The :: idea creates unambiguous space for static/type annotations which wouldn't even have to compile and execute and thus escape less-than-optimal solutions to the forward reference problems, all this without needlessly interfering with existing or potential uses of annotations for run-time purposes, a feature unique(?) to python. +1 on the :: idea.
I like the :: idea.
+1 on the :: idea.
As for the typing system, I think the proposal is pretty much spot on. I know I've been harping on annotations being forcefully restricted to types only, but the proposal itself (the typing library, etc.) is a really good proposal. Unless Python suddenly stops being Python, it's not too likely that any typing system can handle all possible cases of what we can do in the language (at least, not without becoming insanely unwieldy).
The :: idea creates unambiguous space for static/type annotations which wouldn't even have to compile and execute and thus escape less-than-optimal solutions to the forward reference problems, all this without needlessly interfering with existing or potential uses of annotations for run-time purposes, a feature unique(?) to python.
+1 on the :: idea.
Please, no. Type hinting is already making function definitions verbose enough as it is. The very idea that we'd have both ":" and "::" in the language and that somehow this isn't ambiguous is completely obscene to me. There's only so much room in a function definition for information while keeping it clear and readable.
Stephen Hansen wrote:
Type hinting is already making function definitions verbose enough as it is. The very idea that we'd have both ":" and "::" in the language and that somehow this isn't ambiguous is completely obscene to me.
If we were to *really* use :: the way Haskell uses it, then the type information wouldn't be in the function header. It would be on a separate line, much like a decorator: quack :: lambda float, int: AnimalSound def quack(volume, num_times): ... -- Greg
On Mon, Jan 19, 2015 at 10:22 PM, Greg Ewing
Stephen Hansen wrote:
Type hinting is already making function definitions verbose enough as it is. The very idea that we'd have both ":" and "::" in the language and that somehow this isn't ambiguous is completely obscene to me.
If we were to *really* use :: the way Haskell uses it, then the type information wouldn't be in the function header. It would be on a separate line, much like a decorator:
quack :: lambda float, int: AnimalSound def quack(volume, num_times): ...
That's still verbose, its simply vertically verbose. We have decorators for adding information in the vertical space. Again the duplication of effect seems crazy to me: @foo bar :: something, baz: something def bar(baz): pass This reads like a terrible joke. You have : and :: and there's absolutely nothing intuitive or even suggestive about one verses the other, they're almost identical after all. Then you have decorators and ... whatever you call that line? What's the difference? We already have an approved syntax for tagging information to function arguments and to signify return type. We shouldn't have two. Almost no one uses function annotations, in eight years almost no one has come up with a good general use-case for them besides type hinting, which they were always meant for. Let's use them. I do wish you could annotate assignment statements (say, a: int = 5) as I hate the comment feature fiercely, but we can't all have everything. Worst case scenario, someone wants to use the annotations for something else, doesn't get to use mypy or other competing tool on that file. We don't need two syntaxes for this problem space.
Stephen Hansen wrote:
On Mon, Jan 19, 2015 at 10:22 PM, Greg Ewing
mailto:greg.ewing@canterbury.ac.nz> wrote: quack :: lambda float, int: AnimalSound def quack(volume, num_times): ...
That's still verbose, its simply vertically verbose. We have decorators for adding information in the vertical space.
Then you have decorators and ... whatever you call that line? What's the difference?
The difference is that the :: annotations would *not* be evaluated a run time. There's currently no way to get that with a decorator, or anything else in the language, short of abusing comments or string literals. Yet it's what you really want for static type checking. -- Greg
The difference is that the :: annotations would *not* be evaluated a run time. There's currently no way to get that with a decorator, or anything else in the language, short of abusing comments or string literals. Yet it's what you really want for static type checking.
We already have : annotations. If you don't want evaluation at run time, "abuse" string literals -- all you're doing is removing the need to put quotes around the argument. Typing two characters is a very poor justification for new syntax. Especially when its at the cost of making the whole thing horribly more complex by having two syntaxes for essentially the same thing ... and especially when they look confusingly similar and there is no intuitive way to make a distinction between them. def foo(bar: int:: whatever): pass Please, no. We really, really, really do not need two kinds of annotations that look essentially identical. def foo(bar: 'int'): pass if what you "really want" for static type checking is no evaluation, is no big deal. x : 'xyz' is how you spell 'not evaluating' and its only one extra character to type. As an aside, I'm not convinced that what you really want for static type checking is no evaluation, since mypy seems seems to work just fine with evaluation or strings either way. -1 on a second form of annotations. Its like giving a dog a hat, you might think it keeps its ears dry but the dog doesn't need it and looks ridiculous.
On Tue, Jan 20, 2015 at 07:59:40PM +1300, Greg Ewing wrote:
Stephen Hansen wrote:
On Mon, Jan 19, 2015 at 10:22 PM, Greg Ewing
mailto:greg.ewing@canterbury.ac.nz> wrote: quack :: lambda float, int: AnimalSound def quack(volume, num_times): ...
That's still verbose, its simply vertically verbose. We have decorators for adding information in the vertical space.
Then you have decorators and ... whatever you call that line? What's the difference?
The difference is that the :: annotations would *not* be evaluated a run time. There's currently no way to get that with a decorator, or anything else in the language, short of abusing comments or string literals. Yet it's what you really want for static type checking.
Then I guess Python won't have static type checking. I think Guido has been very clear that he has little interest at this stage (if ever) about introducing a Java/Pascal/Haskell style statically typed compiler to Python. I seem to recall he expressed considerable skepticism that such a thing was even practical, or useful even if it could be done. I recall Guido expressing doubts that Nuitka's approach of trying to optimize code statically at compile-time will ever beat Cython's and PyPy's JIT approaches. So anyone thinking that this is the first step to turning Python into Haskell is probably mistaken :-) A type-checker that operates similarly to a linter is proven technology, thanks to mypy. So let's not waste too much time talking about syntax for some feature that Python will probably never get, and even if it does get it, it won't be in 3.5. How about we play around with type-checkers for a release or ten before proposing static typing for Python? -- Steve
On Tue, Jan 20, 2015 at 3:06 AM, Steven D'Aprano
On Tue, Jan 20, 2015 at 07:59:40PM +1300, Greg Ewing wrote:
Stephen Hansen wrote:
Then you have decorators and ... whatever you call that line? What's the difference?
The difference is that the :: annotations would *not* be evaluated a run time. There's currently no way to get that with a decorator, or anything else in the language, short of abusing comments or string literals. Yet it's what you really want for static type checking.
Then I guess Python won't have static type checking.
I think Guido has been very clear that he has little interest at this stage (if ever) about introducing a Java/Pascal/Haskell style statically typed compiler to Python.
Fortunately, Greg wasn't suggesting that. You seem to be misunderstanding the phrase "static type checker". It means something that checks types, statically. Like mypy. Greg's complaints make sense in that context (and I agree with him.) -- Devin
On Jan 20, 2015, at 14:57, Devin Jeanpierre
On Tue, Jan 20, 2015 at 3:06 AM, Steven D'Aprano
wrote: On Tue, Jan 20, 2015 at 07:59:40PM +1300, Greg Ewing wrote:
Stephen Hansen wrote:
Then you have decorators and ... whatever you call that line? What's the difference?
The difference is that the :: annotations would *not* be evaluated a run time. There's currently no way to get that with a decorator, or anything else in the language, short of abusing comments or string literals. Yet it's what you really want for static type checking.
Then I guess Python won't have static type checking.
I think Guido has been very clear that he has little interest at this stage (if ever) about introducing a Java/Pascal/Haskell style statically typed compiler to Python.
Fortunately, Greg wasn't suggesting that. You seem to be misunderstanding the phrase "static type checker". It means something that checks types, statically. Like mypy. Greg's complaints make sense in that context (and I agree with him.)
As I understand it (and please correct me if I'm wrong), Greg is making a very simple point: If type annotations are only for static type checking, as done by something like MyPy, they, by definition, have no use at runtime. But annotations are about storing information with functions at runtime. So the proposal is inherently storing useless information at runtime. (And it's also preventing anyone else from storing useful information there, but that's not the main issue.) But I think there's a hole in the premise. Python's key strength is good debug-ability and amazingly powerful introspection. (I mean Python's _two_ key strengths are...) Information that has no runtime meaning to the interpreter may still have meaning to the user who's debugging code, or interactively exploring it. The fact that inspect.signature returns the parameter and return types is useful information to that user, even if there really is nothing you can do with it programmatically. You could ask why not just always use strings for the annotations. After all, MyPy obviously already has to be able to handle that, and to a human debugging the code and printing out the annotation there's no real difference. But the reason there is pretty obvious: the quotes are extra characters that get in the way of reading the function definition, and 90% of the time they're unnecessary. Think of being able to use a compile-time expression instead of an equivalent string as syntactic sugar--not necessary, but still nice.
On Tue, Jan 20, 2015 at 6:23 PM, Andrew Barnert < abarnert@yahoo.com.dmarc.invalid> wrote:
As I understand it (and please correct me if I'm wrong), Greg is making a very simple point:
If type annotations are only for static type checking, as done by something like MyPy, they, by definition, have no use at runtime. But annotations are about storing information with functions at runtime. So the proposal is inherently storing useless information at runtime. (And it's also preventing anyone else from storing useful information there, but that's not the main issue.)
I believe the argument is slightly different. The main issue is not with storing the annotation, but with evaluating it. This evaluation does not help a static type checker in any way, but introduces problems with forward references, and limits the syntax. For example, a type checker could support the common and natural "T1|T2" syntax for the Union type if the interpreter did not attempt to evaluate it. The current solution is to abandon some syntax, and put other things in quotes, which is a bit ugly. The second issue is that the annotations are only supported on the function arguments and return value. There are other things that are useful to annotate, most obviously class attributes and local variables. The current solution is to put these annotations into comments, which is not pretty too. I think these are both good points. They can be addressed by introducing an entirely new syntax, like suggested above, or by changing and extending how the current syntax works. E.g. one can imagine that annotations are not evaluated. They are always stored as strings for the benefit of introspection, but otherwise are not touched by the interpreter. Also, syntax is added to put annotations on other constructs. At least for the 2 cases above it should not be not hard, and we can store these new annotations on the class object and on the function object respectively. Obviously not evaluating annotations is a breaking change, so it can be a non-starter, but it will make things cleaner. Eugene
On Tue, Jan 20, 2015 at 4:20 PM, Eugene Toder
On Tue, Jan 20, 2015 at 6:23 PM, Andrew Barnert
wrote: As I understand it (and please correct me if I'm wrong), Greg is making a very simple point:
If type annotations are only for static type checking, as done by something like MyPy, they, by definition, have no use at runtime. But annotations are about storing information with functions at runtime. So the proposal is inherently storing useless information at runtime. (And it's also preventing anyone else from storing useful information there, but that's not the main issue.)
I believe the argument is slightly different. The main issue is not with storing the annotation, but with evaluating it. This evaluation does not help a static type checker in any way, but introduces problems with forward references, and limits the syntax. For example, a type checker could support the common and natural "T1|T2" syntax for the Union type if the interpreter did not attempt to evaluate it. The current solution is to abandon some syntax, and put other things in quotes, which is a bit ugly.
The second issue is that the annotations are only supported on the function arguments and return value. There are other things that are useful to annotate, most obviously class attributes and local variables. The current solution is to put these annotations into comments, which is not pretty too.
I would add a third problem: Evaluating at runtime fools people into thinking that this is Python code. In fact, the linter is only able to evaluate very few things, and it might even evaluate them differently to how Python does. This will be a source of confusion and bugs.
I think these are both good points. They can be addressed by introducing an entirely new syntax, like suggested above, or by changing and extending how the current syntax works. E.g. one can imagine that annotations are not evaluated. They are always stored as strings for the benefit of introspection, but otherwise are not touched by the interpreter. Also, syntax is added to put annotations on other constructs. At least for the 2 cases above it should not be not hard, and we can store these new annotations on the class object and on the function object respectively. Obviously not evaluating annotations is a breaking change, so it can be a non-starter, but it will make things cleaner.
This is an interesting idea. Since the original stated purpose of annotations is being overridden to be for mypy style type checkers, the normal backwards compatibility rules maybe don't apply as strongly. -- Devin
Andrew Barnert wrote:
But I think there's a hole in the premise. Python's key strength is good debug-ability and amazingly powerful introspection. (I mean Python's _two_ key strengths are...) Information that has no runtime meaning to the interpreter may still have meaning to the user who's debugging code,
There's nothing wrong with making the annotations *available* at run time. All I'm saying is that *evaluating* them at run time hinders rather than helping for the intended use. -- Greg
On Mon, Jan 19, 2015 at 12:30 PM, Devin Jeanpierre
On Sun, Jan 18, 2015 at 4:01 PM, Greg
wrote: Do you mean *static* types, or types in general?
If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of.
Strong +1. The current proposal is a historical accident, it would not be designed this way if it was designed from scratch. That's not a nice thing to include in the core language. (Although it does happen often, since "practicality beats purity").
It's stronger than that. I'd say that all of Python is a historical accident.
I like the :: idea.
Blechh. It smells of C++ and syntax hacks. PEP 3107 is over 8 years old (also a historical accident :-). Let's stick with the devil we know. -- --Guido van Rossum (python.org/~guido)
Before I forget, your typical forward-reference problem will most likely
look like this:
class X(...):
def method(self, other:: X):
...
On Mon Jan 19 2015 at 11:15:05 PM Guido van Rossum
On Mon, Jan 19, 2015 at 12:30 PM, Devin Jeanpierre
wrote:
On Sun, Jan 18, 2015 at 4:01 PM, Greg
wrote: Do you mean *static* types, or types in general?
If they're only for static type checking, this seems a waste of a facility that evaluates things at run time. Moreover, evaluating them at run time is actually counterproductive, since it makes dealing with things like forward references unnecessarily awkward. It also introduces useless runtime overhead. And they only address part of the problem, since they only apply to functions and not other things we might want to specify the type of.
Strong +1. The current proposal is a historical accident, it would not be designed this way if it was designed from scratch. That's not a nice thing to include in the core language. (Although it does happen often, since "practicality beats purity").
It's stronger than that. I'd say that all of Python is a historical accident.
I like the :: idea.
Blechh. It smells of C++ and syntax hacks. PEP 3107 is over 8 years old (also a historical accident :-). Let's stick with the devil we know.
You know, I feel pretty good when I get to write python helper scripts in Python 3 because I get to write def my_script(address, *, port:'p'=80): ... run(my_script) rather than @kwoargs('port') @annotate(port='p') def my_script(address, port=80): ... run(my_script) With just one parameter to annotate, we go from 19 characters plus imports in Python 2.7, to just 4. In 4 characters, in just as much clarity as with 19, I made "-p 8080" synonymous with "--port 8080". Didn't have to think it through, didn't have to go back and add imports, just had to wish "it would be nice if I could use -p instead". That's what I had in mind when writing this argument parser, when I added annotation support to it, when I made annotations the primary way of specifying anything with it. Annotations have been great, and this little project wouldn't have existed in another programming language. Reserving annotations for typing info, as you suggested is your long-term goal, takes that away and takes eager users of my library on Python 3 halfway back to Python 2. Not cool. I understand, from this thread and previous threads about this PEP, that you intended from the get-go parameter annotations to be used for type hinting and co. This appeared in the annotations PEP as a mere suggestion in a bullet point list, and more importantly did not appear in the docs. If the chain of historical accidents is to continue, maybe it shall include Python opting to spawn a separate kind of annotation better-suited for static analysis alongside the dynamic ones that can be used in all kinds of stuff that look ugly in other languages. If the problem against :: really is "There's no time before 3.5", that's really just "Postpone it until 3.6" waiting to be said. That'll be more time to see if whatever is drafted up is useful to static analysis tools and statically-analysed code bases. It has already waited 24 years after all. As for C++'s infamous "> >" vs ">>", "::" isn't comparable because ": :" does not exist and isn't about to. (Right?) The :: idea otherwise brings advantages to all as outlined before, so I'm a bit frustrated to see it dismissed on mere feeling. --
--Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Mon, Jan 19, 2015 at 3:11 PM, Yann Kaiser
The :: idea otherwise brings advantages to all as outlined before, so I'm a bit frustrated to see it dismissed on mere feeling.
I'm sure I can explain rationally why "::" is a bad idea given Python's historical approach to syntax, but I'm not sure that it would sway you, so I'm saving both of us time by just saying it's unPythonic. With that in mind, there is nothing in the current proposal that prevents "::" from being introduced in 3.6 -- the proposal doesn't change any syntax, so if "::" is possible now, it will still be possible in 3.6. So you have until Python 3.6 to implement it. PEP 484 will be provisional, so if we really liked "::" better by that time we can just withdraw PEP 484 at that time. -- --Guido van Rossum (python.org/~guido)
Guido van Rossum wrote:
On Mon, Jan 19, 2015 at 12:30 PM, Devin Jeanpierre
mailto:jeanpierreda@gmail.com> wrote: I like the :: idea.
Blechh. It smells of C++ and syntax hacks.
That smells like a knee-jerk reaction to me. Just because you've had bad experiences with it in C++ doesn't mean the symbol itself is inherently evil. But there are alternatives. We could borrow 'as' from a certain other language: num_feathers as int = 0 Or exploit the fact that '{' following an identifier is currently undefined: num_feathers {int} = 0 -- Greg
On Tue, Jan 20, 2015 at 07:12:33PM +1300, Greg Ewing wrote:
But there are alternatives. We could borrow 'as' from a certain other language:
num_feathers as int = 0
Which language did you have in mind? Cobra uses that syntax: http://cobra-language.com/trac/cobra/wiki/Variables Even though Cobra is inspired by Python, I think it makes some bad choices, and this is one of them. Python uses "as" for name binding: try: import spam as ham with ham() as eggs: ... except SomeError as err: ... It would be inconsistent and confusing to introduce "as" for type-declarations.
Or exploit the fact that '{' following an identifier is currently undefined:
num_feathers {int} = 0
Now you're just making up random syntax :-) -- Steve
On Fri, Jan 16, 2015 at 08:57:41PM -0500, Cem Karan wrote:
If we're using decorators for the annotations, why not use them for the typing as well? E.g.,
@typing.type(a, int) @doc.doc(a, "some doc") def f(a): pass
This would be the equivalent of:
def f(a: {"typing.type": int, "doc.doc": "some doc"}): pass
Decorators are a second-class solution to the annotation problem, because they require you to repeat the variable name in at least two places. In the past I have strongly defended the idea that type-hinting annotations need to co-exist with other uses for annotations. By this I mean that there should be a simple way for the author of a module who uses annotations for something else to flag that module, or parts of the module, so that the type-checker skips it. There is no good way to have multiple uses of annotations be used in the same function. You have suggested using a dict, but what of the module that wants to give a completely different meaning to dicts as annotations? Your interpetation of annotations is not compatible with that module. Like multiple inheritence, multiple use of annotations is only possible if all parties cooperate and agree on semantics. Besides, the more information you try to squeeze into the function parameter list, the more unwieldy, unreadable and ugly it gets. Type-hinting was Guido's original motivation for introducing annotations, and there is no other use of annotations which has become popular or widespread enough to justify calling it a "standard use". So in the absense of any other standard use of annotations, I am comfortable with giving type-hints special status: - whether type-checking is enabled by default or not, we understand that annotations are primarily for type-hinting, and any annotations are to be understood as type-hints by default; - other uses for annotations are permitted, but it is up to the user to flag the module/class/function so the type-checker skips it; - no provision is made for mixing type-hints and arbitrary other uses in the same annotation (in other words, if you want to use annotations for type-hinting and Foo *at the same time*, you're on your own); - but of course, nothing stops you from creating your own custom annotation scheme that includes Foo + type-hints. If some other use of annotations becomes wildly successful, then it may be worth rethinking the special status of type-hinting in the future. If you want to interoperate with type-hinting, decorators may be a second-class solution, but they are a solution. -- Steve
On 1/16/2015 4:40 PM, Guido van Rossum wrote:
On Fri, Jan 16, 2015 at 12:34 PM, Dennis Brakhane
I feel there should be another way to make type annotations and "creative" annotations coexist.
The cleanest way to do this would be to use decorators for the non-type-hint use case.
I have come to agree with this as the least bad solution. From a runtime perspective, annotation syntax is merely a nice way to attach to a function an attribute whose value is a dict whose keys are function and parameter names. Decorators can do the same (and have been able to do so since introduced). Repeating (and quoting) names in the decorator is a nuisance, but as a reader, I would prefer that to having multiple annotation systems mixed together. The decorator solution, with custom attribute names, scales better to multiple annotation systems. -- Terry Jan Reedy
hi, nice work!
I like most of the ideas, except the semantic comments and some specific
abstract classes.
so criticism first:
1. i don’t think comments should ever have an impact on the code.
i’d prefer “assert isinstance(...)” for type annotations on variables.
2. also i don’t see why Dict, (non-abstract) Set, and List exist as long as
we have Mapping and so on. Tuple is different because of its “Multiple
return value” status and status as “is treated as Union[Class1, Class2]
when used with instanceof(), except and friends”.
but checking for instanceof(..., list) is unpytonic! imho List and Dict
really go against python’s data model and protocol-based nature.
i’d be for consistency: Set and MutableSet, Sequence and MutableSequence,
Mapping and MutableMapping. no “abstract” (because all of this is
abstract), no “frozen” (because frozen sets are just immutable sets and
immutable behavior is a subset of the corresponding mutable behavior).
Questions:
1. will we be able to use Union[...] in places like an except clause, where
tuples are used to mean the same right now? would make sense imho
2. what are ItemsView and KeysView for? what makes them different from
FrozenSet or Sequence?
Guido van Rossum
After a long editing process we've got PEP 484 https://www.python.org/dev/peps/pep-0484/ (Type Hints) ready for your review. This is by no means final, and several areas are either open (to be resolved in a later draft) or postponed (to a different PEP altogether). But there's enough meat that I think we can start having the discussion. Please also see PEP 483 https://www.python.org/dev/peps/pep-0483/ (The Theory of Type Hint; copied and reformatted from the original Quip document that I posted just before last Christmas) and PEP 482 https://www.python.org/dev/peps/pep-0482/ (Literature Overview for Type Hints, by Łukasz). Those are informational PEPs though; the actual spec is focused in PEP 484 (the only one on the Standards Track).
As I said earlier, I hope to have a rough consensus before PyCon https://us.pycon.org/2015/ and working code (just the typing.py module https://github.com/ambv/typehinting/tree/master/prototyping, really) committed to CPython before the last 3.5 alpha https://www.python.org/dev/peps/pep-0478/.
Here is the raw text of PEP 484. Fire away!!
PEP: 484 Title: Type Hints Version: $Revision$ Last-Modified: $Date$ Author: Guido van Rossum
, Jukka Lehtosalo < jukka.lehtosalo@iki.fi>, Łukasz Langa Discussions-To: Python-Dev Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 29-Sep-2014 Post-History: 16-Jan-2015 Resolution: Abstract ========
This PEP introduces a standard syntax for type hints using annotations on function definitions.
The proposal is strongly inspired by mypy [mypy]_.
The theory behind type hints and gradual typing is explained in PEP 483.
Rationale and Goals ===================
PEP 3107 added support for arbitrary annotations on parts of a function definition. Although no meaning was assigned to annotations then, there has always been an implicit goal to use them for type hinting, which is listed as the first possible use case in said PEP.
This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and performance optimizations utilizing type information.
Type Definition Syntax ======================
The syntax leverages PEP 3107-style annotations with a number of extensions described in sections below. In its basic form, type hinting is used by filling function annotations with classes::
def greeting(name: str) -> str: return 'Hello ' + name
This denotes that the expected type of the ``name`` argument is ``str``. Analogically, the expected return type is ``str``. Subclasses of a specified argument type are also accepted as valid types for that argument.
Abstract base classes, types available in the ``types`` module, and user-defined classes may be used as type hints as well. Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined. In addition, the needs of static analysis require that annotations must be simple enough to be interpreted by static analysis tools. (This is an intentionally somewhat vague requirement.)
.. FIXME: Define rigorously what is/isn't supported.
When used as an annotation, the expression ``None`` is considered equivalent to ``NoneType`` (i.e., ``type(None)`` for type hinting purposes.
Type aliases are also valid type hints::
integer = int
def retry(url: str, retry_count: integer): ...
New names that are added to support features described in following sections are available in the ``typing`` package.
Callbacks ---------
Frameworks expecting callback functions of specific signatures might be type hinted using ``Callable[[Arg1Type, Arg2Type], ReturnType]``. Examples::
from typing import Any, AnyArgs, Callable
def feeder(get_next_item: Callable[[], Item]): ...
def async_query(on_success: Callable[[int], None], on_error: Callable[[int, Exception], None]): ...
def partial(func: Callable[AnyArgs, Any], *args): ...
Since using callbacks with keyword arguments is not perceived as a common use case, there is currently no support for specifying keyword arguments with ``Callable``.
Generics --------
Since type information about objects kept in containers cannot be statically inferred in a generic way, abstract base classes have been extended to support subscription to denote expected types for container elements. Example::
from typing import Mapping, Set
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]): ...
Generics can be parametrized by using a new factory available in ``typing`` called ``TypeVar``. Example::
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function return l[0]
In this case the contract is that the returning value is consistent with the elements held by the collection.
``TypeVar`` supports constraining parametric types to classes with any of the specified bases. Example::
from typing import Iterable
X = TypeVar('X') Y = TypeVar('Y', Iterable[X])
def filter(rule: Callable[[X], bool], input: Y) -> Y: ...
.. FIXME: Add an example with multiple bases defined.
In the example above we specify that ``Y`` can be any subclass of Iterable with elements of type ``X``, as long as the return type of ``filter()`` will be the same as the type of the ``input`` argument.
.. FIXME: Explain more about how this works.
Forward references ------------------
When a type hint contains names that have not been defined yet, that definition may be expressed as a string, to be resolved later. For example, instead of writing::
def notify_by_email(employees: Set[Employee]): ...
one might write::
def notify_by_email(employees: 'Set[Employee]'): ...
.. FIXME: Rigorously define this. Defend it, or find an alternative.
Union types -----------
Since accepting a small, limited set of expected types for a single argument is common, there is a new special factory called ``Union``. Example::
from typing import Union
def handle_employees(e: Union[Employee, Sequence[Employee]]): if isinstance(e, Employee): e = [e] ...
A type factored by ``Union[T1, T2, ...]`` responds ``True`` to ``issubclass`` checks for ``T1`` and any of its subclasses, ``T2`` and any of its subclasses, and so on.
One common case of union types are *optional* types. By default, ``None`` is an invalid value for any type, unless a default value of ``None`` has been provided in the function definition. Examples::
def handle_employee(e: Union[Employee, None]): ...
As a shorthand for ``Union[T1, None]`` you can write ``Optional[T1]``; for example, the above is equivalent to::
from typing import Optional
def handle_employee(e: Optional[Employee]): ...
An optional type is also automatically assumed when the default value is ``None``, for example::
def handle_employee(e: Employee = None): ...
This is equivalent to::
def handle_employee(e: Optional[Employee] = None): ...
.. FIXME: Is this really a good idea?
A special kind of union type is ``Any``, a class that responds ``True`` to ``issubclass`` of any class. This lets the user explicitly state that there are no constraints on the type of a specific argument or return value.
Platform-specific type checking -------------------------------
In some cases the typing information will depend on the platform that the program is being executed on. To enable specifying those differences, simple conditionals can be used::
from typing import PY2, WINDOWS
if PY2: text = unicode else: text = str
def f() -> text: ...
if WINDOWS: loop = ProactorEventLoop else: loop = UnixSelectorEventLoop
Arbitrary literals defined in the form of ``NAME = True`` will also be accepted by the type checker to differentiate type resolution::
DEBUG = False ... if DEBUG: class Tracer: <verbose implementation> else: class Tracer: <dummy implementation>
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.
Compatibility with other uses of function annotations -----------------------------------------------------
A number of existing or potential use cases for function annotations exist, which are incompatible with type hinting. These may confuse a static type checker. However, since type hinting annotations have no run time behavior (other than evaluation of the annotation expression and storing annotations in the ``__annotations__`` attribute of the function object), this does not make the program incorrect -- it just makes it issue warnings when a static analyzer is used.
To mark portions of the program that should not be covered by type hinting, use the following:
* a ``@no_type_checks`` decorator on classes and functions
* a ``# type: ignore`` comment on arbitrary lines
.. FIXME: should we have a module-wide comment as well?
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 if was declared, an ``Undefined`` placeholder might be used::
from typing import Undefined
x = Undefined # type: List[Employee] y = Undefined(int)
If type hinting proves useful in general, a syntax for typing variables may be provided in a future Python version.
Explicit raised exceptions ==========================
No support for listing explicitly raised exceptions is being defined by this PEP. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring.
The ``typing`` package ======================
To open the usage of static type checking to Python 3.5 as well as older versions, a uniform namespace is required. For this purpose, a new package in the standard library is introduced called ``typing``. It holds a set of classes representing builtin types with generics, namely:
* Dict, used as ``Dict[key_type, value_type]``
* List, used as ``List[element_type]``
* Set, used as ``Set[element_type]``. See remark for ``AbstractSet`` below.
* FrozenSet, used as ``FrozenSet[element_type]``
* Tuple, used as ``Tuple[index0_type, index1_type, ...]``. Arbitrary-length tuples might be expressed using ellipsis, in which case the following arguments are considered the same type as the last defined type on the tuple.
It also introduces factories and helper members needed to express generics and union types:
* Any, used as ``def get(key: str) -> Any: ...``
* Union, used as ``Union[Type1, Type2, Type3]``
* TypeVar, used as ``X = TypeVar('X', Type1, Type2, Type3)`` or simply ``Y = TypeVar('Y')``
* Undefined, used as ``local_variable = Undefined # type: List[int]`` or ``local_variable = Undefined(List[int])`` (the latter being slower during runtime)
* Callable, used as ``Callable[[Arg1Type, Arg2Type], ReturnType]``
* AnyArgs, used as ``Callable[AnyArgs, ReturnType]``
* AnyStr, equivalent to ``TypeVar('AnyStr', str, bytes)``
All abstract base classes available in ``collections.abc`` are importable from the ``typing`` package, with added generics support:
* ByteString
* Callable
* Container
* Hashable
* ItemsView
* Iterable
* Iterator
* KeysView
* Mapping
* MappingView
* MutableMapping
* MutableSequence
* MutableSet
* Sequence
* Set as ``AbstractSet``. This name change was required because ``Set`` in the ``typing`` module means ``set()`` with generics.
* Sized
* ValuesView
* Mapping
The library includes literals for platform-specific type hinting:
* PY2
* PY3, equivalent to ``not PY2``
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
The following types are available in the ``typing.io`` module:
* IO
* BinaryIO
* TextIO
The following types are provided by the ``typing.re`` module:
* Match and Pattern, types of ``re.match()`` and ``re.compile()`` results
As a convenience measure, types from ``typing.io`` and ``typing.re`` are also available in ``typing`` (quoting Guido, "There's a reason those modules have two-letter names.").
The place of the ``typing`` module in the standard library ----------------------------------------------------------
.. FIXME: complete this section
Usage Patterns ==============
The main use case of type hinting is static analysis using an external tool without executing the analyzed program. Existing tools used for that purpose like ``pyflakes`` [pyflakes]_ or ``pylint`` [pylint]_ might be extended to support type checking. New tools, like mypy's ``mypy -S`` mode, can be adopted specifically for this purpose.
Type checking based on type hints is understood as a best-effort mechanism. In other words, whenever types are not annotated and cannot be inferred, the type checker considers such code valid. Type errors are only reported in case of explicit or inferred conflict. Moreover, as a mechanism that is not tied to execution of the code, it does not affect runtime behaviour. In other words, even in the case of a typing error, the program will continue running.
The implementation of a type checker, whether linting source files or enforcing type information during runtime, is out of scope for this PEP.
.. FIXME: Describe stub modules.
.. FIXME: Describe run-time behavior of generic types.
Existing Approaches ===================
PEP 482 lists existing approaches in Python and other languages.
Is type hinting Pythonic? =========================
Type annotations provide important documentation for how a unit of code should be used. Programmers should therefore provide type hints on public APIs, namely argument and return types on functions and methods considered public. However, because types of local and global variables can be often inferred, they are rarely necessary.
The kind of information that type hints hold has always been possible to achieve by means of docstrings. In fact, a number of formalized mini-languages for describing accepted arguments have evolved. Moving this information to the function declaration makes it more visible and easier to access both at runtime and by static analysis. Adding to that the notion that “explicit is better than implicit”, type hints are indeed *Pythonic*.
Acknowledgements ================
This document could not be completed without valuable input, encouragement and advice from Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, and Radomir Dopieralski.
Influences include existing languages, libraries and frameworks mentioned in PEP 482. Many thanks to their creators, in alphabetical order: Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer, Raoul-Gabriel Urma, and Julien Verlaguet.
References ==========
.. [mypy] http://mypy-lang.org
.. [pyflakes] https://github.com/pyflakes/pyflakes/
.. [pylint] http://www.pylint.org
Copyright =========
This document has been placed in the public domain.
.. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
-- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, Jan 16, 2015 at 12:05 PM, Philipp A.
hi, nice work!
I like most of the ideas, except the semantic comments and some specific abstract classes.
so criticism first:
1. i don’t think comments should ever have an impact on the code.
They don't. They also change the errors emitted by the static checker (which is not invoked when you run the code -- it is like a linter).
i’d prefer “assert isinstance(...)” for type annotations on variables.
But that *would* affect runtime, by slowing down execution. Also for generic types the type checking could be either very slow (if checking List[int] would check every item of a list) or always fail (if a type variable is involved). See discussion at https://github.com/ambv/typehinting/issues/35
2. also i don’t see why Dict, (non-abstract) Set, and List exist as long as we have Mapping and so on. Tuple is different because of its “Multiple return value” status and status as “is treated as Union[Class1, Class2] when used with instanceof(), except and friends”.
but checking for instanceof(..., list) is unpytonic! imho List and Dict really go against python’s data model and protocol-based nature.
i’d be for consistency: Set and MutableSet, Sequence and MutableSequence, Mapping and MutableMapping. no “abstract” (because all of this is abstract), no “frozen” (because frozen sets are just immutable sets and immutable behavior is a subset of the corresponding mutable behavior).
But there is a lot of code that really needs the concrete types. E.g. list+list returns a list, but sequence+sequence may fail (as list+tuple exemplifies). Or list.sort. Etc.
Questions:
1. will we be able to use Union[...] in places like an except clause, where tuples are used to mean the same right now? would make sense imho
No. Remember TOOWTDI. (Also the PEP intentionally stays far away from exception handling.)
2. what are ItemsView and KeysView for? what makes them different from FrozenSet or Sequence?
They exist in collections.abc. Note that KeysView and ItemsView are (abstract) Sets, but ValuesView is not (you can have multiple equal values). -- --Guido van Rossum (python.org/~guido)
Guido van Rossum
i don’t think comments should ever have an impact on the code.
They don't. They also change the errors emitted by the static checker (which is not invoked when you run the code -- it is like a linter).
still, comments are in my eyes purely documentational. the only exception in python i can think of, doctest, only exists to ensure that no examples you give start breaking, i.e. doctests are ran to keep the *comments* up to date, not the code. and annotations are a syntax-level element. using comments forces the linter to use an alternative AST with comments preserved, not the AST CPython gives you for free. and i’m looking into the future: python interpreters/VMs could choose to optimize e.g. List[double] based on type annotations, instead of just linting. in which case comments would become part of the code.
i’d prefer “assert isinstance(...)” for type annotations on variables.
But that *would* affect runtime, by slowing down execution. Also for generic types the type checking could be either very slow (if checking List[int] would check every item of a list) or always fail (if a type variable is involved).
See discussion at https://github.com/ambv/typehinting/issues/35
optimized package builds strip out assertions, no? and if course isinstance([1], List[int]) shouldn’t fail. that would be very surprising! i’ll check out the issue.
But there is a lot of code that really needs the concrete types. E.g. list+list returns a list, but sequence+sequence may fail (as list+tuple exemplifies). Or list.sort. Etc.
i commented in the issue: we could specify that behavior in new ABCs or even add it as mixin methods. nothing prevents MutableSequence, which has .reverse, from having .sort, as well. will we be able to use Union[...] in places like an except clause, where
tuples are used to mean the same right now? would make sense imho
No. Remember TOOWTDI. (Also the PEP intentionally stays far away from exception handling.)
yeah, i just though that isinstance(foo, (str, bytes)) basically means “is foo an instance of either str or bytes”, or in other words “is foo an instance of the union type str|bytes”. the one way to do it would have been Union[str, bytes] if python had Union from the beginning, i’m sure! what are ItemsView and KeysView for? what makes them different from
FrozenSet or Sequence?
They exist in collections.abc. Note that KeysView and ItemsView are (abstract) Sets, but ValuesView is not (you can have multiple equal values).
ah, get it! they provide mixins powered by suppying a to-be-wrapped Mapping instance to MappingView’s __init__! this should be documented, else nobody knows *how* they mix in their methods.
--Guido van Rossum (python.org/~guido)
thanks for the explanations, and as always: thanks for this most beautiful language :D
On Jan 16, 2015, at 12:05, "Philipp A."
hi, nice work!
I like most of the ideas, except the semantic comments and some specific abstract classes.
so criticism first:
1. i don’t think comments should ever have an impact on the code. i’d prefer “assert isinstance(...)” for type annotations on variables.
The PEP already acknowledges that the comment solution isn't ideal, but says "If type hinting proves useful in general, a syntax for typing variables may be provided in a future Python version." And it also provides Undefined as an optional alternative way to type variables. But, more importantly, I think you've missed the key point that type annotations are not supposed to affect runtime or compile time at all; they're only there for a separate static type checker. (That could change in the future--at that point, I assume either Guido will come up with a syntax for variable types, or drop explicit variable types.) So this is akin to comments for a linker, docs generator, or test generator. Asserts, on the other hand, would be tested at runtime, possibly breaking working code and likely slowing things down.
2. also i don’t see why Dict, (non-abstract) Set, and List exist as long as we have Mapping and so on. Tuple is different because of its “Multiple return value” status and status as “is treated as Union[Class1, Class2] when used with instanceof(), except and friends”.
but checking for instanceof(..., list) is unpytonic! imho List and Dict really go against python’s data model and protocol-based nature.
This was argued in depth when the idea was first proposed. For the record, I agree that List and friends are more often going to be an attractive nuisance that people will use when they should have used Iterable or Sequence, but if you're going to reopen that argument, you'll want to read the old threads first. Also, that's clearly not just a request for clarification, so I think it belongs on the tracker, where the argument can be preserved permanently in a better format than list archives.l
i’d be for consistency: Set and MutableSet, Sequence and MutableSequence, Mapping and MutableMapping. no “abstract” (because all of this is abstract), no “frozen” (because frozen sets are just immutable sets and immutable behavior is a subset of the corresponding mutable behavior).
The builtin concrete type frozenset has existed for years. It's an instantiation of the abstract Set type, but (unlike set) not of the MutableSet type. You could argue that they should have been named more consistently, but only if you want to go back to whenever sets.ImmutableSet was renamed frozenset (2.5?) or when the ABCs were first added (2.6/3.0).
Questions:
1. will we be able to use Union[...] in places like an except clause, where tuples are used to mean the same right now? would make sense imho
Since the elements of a Union are all defined to be subclasses of the Union, it seems like this would automatically work in except clauses, isinstance and issubclass calls, and anywhere else that uses issubclass. But why would you want to?
2. what are ItemsView and KeysView for? what makes them different from FrozenSet or Sequence?
They've been in the collections module since 3.0. The documentation already explains why they exist and how they're different from Set. (Actually, I think it's explained more in the docs for the concrete classes associated with dict than in the ABCs, but it's there.) (You actually asked about FrozenSet and Sequence, not AbstractSet/Set. What makes them different from FrozenSet is that's a generic for the concrete type frozenset; what makes them different from Sequence is that they're not indexable, which is the fundamental thing sequences do.)
Guido van Rossum
schrieb am Fri Jan 16 2015 at 18:19:24: After a long editing process we've got PEP 484 (Type Hints) ready for your review. This is by no means final, and several areas are either open (to be resolved in a later draft) or postponed (to a different PEP altogether). But there's enough meat that I think we can start having the discussion. Please also see PEP 483 (The Theory of Type Hint; copied and reformatted from the original Quip document that I posted just before last Christmas) and PEP 482 (Literature Overview for Type Hints, by Łukasz). Those are informational PEPs though; the actual spec is focused in PEP 484 (the only one on the Standards Track).
As I said earlier, I hope to have a rough consensus before PyCon and working code (just the typing.py module, really) committed to CPython before the last 3.5 alpha.
Here is the raw text of PEP 484. Fire away!!
PEP: 484 Title: Type Hints Version: $Revision$ Last-Modified: $Date$ Author: Guido van Rossum
, Jukka Lehtosalo , Łukasz Langa Discussions-To: Python-Dev Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 29-Sep-2014 Post-History: 16-Jan-2015 Resolution: Abstract ========
This PEP introduces a standard syntax for type hints using annotations on function definitions.
The proposal is strongly inspired by mypy [mypy]_.
The theory behind type hints and gradual typing is explained in PEP 483.
Rationale and Goals ===================
PEP 3107 added support for arbitrary annotations on parts of a function definition. Although no meaning was assigned to annotations then, there has always been an implicit goal to use them for type hinting, which is listed as the first possible use case in said PEP.
This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and performance optimizations utilizing type information.
Type Definition Syntax ======================
The syntax leverages PEP 3107-style annotations with a number of extensions described in sections below. In its basic form, type hinting is used by filling function annotations with classes::
def greeting(name: str) -> str: return 'Hello ' + name
This denotes that the expected type of the ``name`` argument is ``str``. Analogically, the expected return type is ``str``. Subclasses of a specified argument type are also accepted as valid types for that argument.
Abstract base classes, types available in the ``types`` module, and user-defined classes may be used as type hints as well. Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined. In addition, the needs of static analysis require that annotations must be simple enough to be interpreted by static analysis tools. (This is an intentionally somewhat vague requirement.)
.. FIXME: Define rigorously what is/isn't supported.
When used as an annotation, the expression ``None`` is considered equivalent to ``NoneType`` (i.e., ``type(None)`` for type hinting purposes.
Type aliases are also valid type hints::
integer = int
def retry(url: str, retry_count: integer): ...
New names that are added to support features described in following sections are available in the ``typing`` package.
Callbacks ---------
Frameworks expecting callback functions of specific signatures might be type hinted using ``Callable[[Arg1Type, Arg2Type], ReturnType]``. Examples::
from typing import Any, AnyArgs, Callable
def feeder(get_next_item: Callable[[], Item]): ...
def async_query(on_success: Callable[[int], None], on_error: Callable[[int, Exception], None]): ...
def partial(func: Callable[AnyArgs, Any], *args): ...
Since using callbacks with keyword arguments is not perceived as a common use case, there is currently no support for specifying keyword arguments with ``Callable``.
Generics --------
Since type information about objects kept in containers cannot be statically inferred in a generic way, abstract base classes have been extended to support subscription to denote expected types for container elements. Example::
from typing import Mapping, Set
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]): ...
Generics can be parametrized by using a new factory available in ``typing`` called ``TypeVar``. Example::
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function return l[0]
In this case the contract is that the returning value is consistent with the elements held by the collection.
``TypeVar`` supports constraining parametric types to classes with any of the specified bases. Example::
from typing import Iterable
X = TypeVar('X') Y = TypeVar('Y', Iterable[X])
def filter(rule: Callable[[X], bool], input: Y) -> Y: ...
.. FIXME: Add an example with multiple bases defined.
In the example above we specify that ``Y`` can be any subclass of Iterable with elements of type ``X``, as long as the return type of ``filter()`` will be the same as the type of the ``input`` argument.
.. FIXME: Explain more about how this works.
Forward references ------------------
When a type hint contains names that have not been defined yet, that definition may be expressed as a string, to be resolved later. For example, instead of writing::
def notify_by_email(employees: Set[Employee]): ...
one might write::
def notify_by_email(employees: 'Set[Employee]'): ...
.. FIXME: Rigorously define this. Defend it, or find an alternative.
Union types -----------
Since accepting a small, limited set of expected types for a single argument is common, there is a new special factory called ``Union``. Example::
from typing import Union
def handle_employees(e: Union[Employee, Sequence[Employee]]): if isinstance(e, Employee): e = [e] ...
A type factored by ``Union[T1, T2, ...]`` responds ``True`` to ``issubclass`` checks for ``T1`` and any of its subclasses, ``T2`` and any of its subclasses, and so on.
One common case of union types are *optional* types. By default, ``None`` is an invalid value for any type, unless a default value of ``None`` has been provided in the function definition. Examples::
def handle_employee(e: Union[Employee, None]): ...
As a shorthand for ``Union[T1, None]`` you can write ``Optional[T1]``; for example, the above is equivalent to::
from typing import Optional
def handle_employee(e: Optional[Employee]): ...
An optional type is also automatically assumed when the default value is ``None``, for example::
def handle_employee(e: Employee = None): ...
This is equivalent to::
def handle_employee(e: Optional[Employee] = None): ...
.. FIXME: Is this really a good idea?
A special kind of union type is ``Any``, a class that responds ``True`` to ``issubclass`` of any class. This lets the user explicitly state that there are no constraints on the type of a specific argument or return value.
Platform-specific type checking -------------------------------
In some cases the typing information will depend on the platform that the program is being executed on. To enable specifying those differences, simple conditionals can be used::
from typing import PY2, WINDOWS
if PY2: text = unicode else: text = str
def f() -> text: ...
if WINDOWS: loop = ProactorEventLoop else: loop = UnixSelectorEventLoop
Arbitrary literals defined in the form of ``NAME = True`` will also be accepted by the type checker to differentiate type resolution::
DEBUG = False ... if DEBUG: class Tracer: <verbose implementation> else: class Tracer: <dummy implementation>
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.
Compatibility with other uses of function annotations -----------------------------------------------------
A number of existing or potential use cases for function annotations exist, which are incompatible with type hinting. These may confuse a static type checker. However, since type hinting annotations have no run time behavior (other than evaluation of the annotation expression and storing annotations in the ``__annotations__`` attribute of the function object), this does not make the program incorrect -- it just makes it issue warnings when a static analyzer is used.
To mark portions of the program that should not be covered by type hinting, use the following:
* a ``@no_type_checks`` decorator on classes and functions
* a ``# type: ignore`` comment on arbitrary lines
.. FIXME: should we have a module-wide comment as well?
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 if was declared, an ``Undefined`` placeholder might be used::
from typing import Undefined
x = Undefined # type: List[Employee] y = Undefined(int)
If type hinting proves useful in general, a syntax for typing variables may be provided in a future Python version.
Explicit raised exceptions ==========================
No support for listing explicitly raised exceptions is being defined by this PEP. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring.
The ``typing`` package ======================
To open the usage of static type checking to Python 3.5 as well as older versions, a uniform namespace is required. For this purpose, a new package in the standard library is introduced called ``typing``. It holds a set of classes representing builtin types with generics, namely:
* Dict, used as ``Dict[key_type, value_type]``
* List, used as ``List[element_type]``
* Set, used as ``Set[element_type]``. See remark for ``AbstractSet`` below.
* FrozenSet, used as ``FrozenSet[element_type]``
* Tuple, used as ``Tuple[index0_type, index1_type, ...]``. Arbitrary-length tuples might be expressed using ellipsis, in which case the following arguments are considered the same type as the last defined type on the tuple.
It also introduces factories and helper members needed to express generics and union types:
* Any, used as ``def get(key: str) -> Any: ...``
* Union, used as ``Union[Type1, Type2, Type3]``
* TypeVar, used as ``X = TypeVar('X', Type1, Type2, Type3)`` or simply ``Y = TypeVar('Y')``
* Undefined, used as ``local_variable = Undefined # type: List[int]`` or ``local_variable = Undefined(List[int])`` (the latter being slower during runtime)
* Callable, used as ``Callable[[Arg1Type, Arg2Type], ReturnType]``
* AnyArgs, used as ``Callable[AnyArgs, ReturnType]``
* AnyStr, equivalent to ``TypeVar('AnyStr', str, bytes)``
All abstract base classes available in ``collections.abc`` are importable from the ``typing`` package, with added generics support:
* ByteString
* Callable
* Container
* Hashable
* ItemsView
* Iterable
* Iterator
* KeysView
* Mapping
* MappingView
* MutableMapping
* MutableSequence
* MutableSet
* Sequence
* Set as ``AbstractSet``. This name change was required because ``Set`` in the ``typing`` module means ``set()`` with generics.
* Sized
* ValuesView
* Mapping
The library includes literals for platform-specific type hinting:
* PY2
* PY3, equivalent to ``not PY2``
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
The following types are available in the ``typing.io`` module:
* IO
* BinaryIO
* TextIO
The following types are provided by the ``typing.re`` module:
* Match and Pattern, types of ``re.match()`` and ``re.compile()`` results
As a convenience measure, types from ``typing.io`` and ``typing.re`` are also available in ``typing`` (quoting Guido, "There's a reason those modules have two-letter names.").
The place of the ``typing`` module in the standard library ----------------------------------------------------------
.. FIXME: complete this section
Usage Patterns ==============
The main use case of type hinting is static analysis using an external tool without executing the analyzed program. Existing tools used for that purpose like ``pyflakes`` [pyflakes]_ or ``pylint`` [pylint]_ might be extended to support type checking. New tools, like mypy's ``mypy -S`` mode, can be adopted specifically for this purpose.
Type checking based on type hints is understood as a best-effort mechanism. In other words, whenever types are not annotated and cannot be inferred, the type checker considers such code valid. Type errors are only reported in case of explicit or inferred conflict. Moreover, as a mechanism that is not tied to execution of the code, it does not affect runtime behaviour. In other words, even in the case of a typing error, the program will continue running.
The implementation of a type checker, whether linting source files or enforcing type information during runtime, is out of scope for this PEP.
.. FIXME: Describe stub modules.
.. FIXME: Describe run-time behavior of generic types.
Existing Approaches ===================
PEP 482 lists existing approaches in Python and other languages.
Is type hinting Pythonic? =========================
Type annotations provide important documentation for how a unit of code should be used. Programmers should therefore provide type hints on public APIs, namely argument and return types on functions and methods considered public. However, because types of local and global variables can be often inferred, they are rarely necessary.
The kind of information that type hints hold has always been possible to achieve by means of docstrings. In fact, a number of formalized mini-languages for describing accepted arguments have evolved. Moving this information to the function declaration makes it more visible and easier to access both at runtime and by static analysis. Adding to that the notion that “explicit is better than implicit”, type hints are indeed *Pythonic*.
Acknowledgements ================
This document could not be completed without valuable input, encouragement and advice from Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, and Radomir Dopieralski.
Influences include existing languages, libraries and frameworks mentioned in PEP 482. Many thanks to their creators, in alphabetical order: Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer, Raoul-Gabriel Urma, and Julien Verlaguet.
References ==========
.. [mypy] http://mypy-lang.org
.. [pyflakes] https://github.com/pyflakes/pyflakes/
.. [pylint] http://www.pylint.org
Copyright =========
This document has been placed in the public domain.
.. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
-- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, Jan 16, 2015 at 08:05:44PM +0000, Philipp A. wrote:
1. i don’t think comments should ever have an impact on the code. i’d prefer “assert isinstance(...)” for type annotations on variables.
But that isn't a compile-time type declaration, that is a runtime type check. The idea is to avoid runtime type checks by having assurance that by the time your code runs, the variable cannot be anything but the appropriate type. (The truly paranoid may choose to use both, but I expect most people won't.) Type declarations of any form are a kind of comment. They don't have a runtime effect (except incidentally), although they have a compile-time effect, and they are written for the human reader as much as for the compiler. So in this case, rather than inventing new syntax for compiler instructions, we're just using comments of a particular form which the type-checker can understand. This is an old and conventional choice, e.g. Pascal (and I expect many other languages) used comments of the form {$ ... } as directives to the compiler. [...]
i’d be for consistency: Set and MutableSet, Sequence and MutableSequence, Mapping and MutableMapping. no “abstract” (because all of this is abstract), no “frozen” (because frozen sets are just immutable sets and immutable behavior is a subset of the corresponding mutable behavior).
frozenset is not a subclass of set. It cannot be, without violating the Liskov Substitution Principle: there are things you can do with sets which you cannot do with frozensets, hence you cannot substitute frozensets for sets. -- Steven
One quick clarification question:
Can strings-as-forward-declarations be used inside non-string type annotations--e.g.. Set['Employee'] instead of 'Set[Employee]'? If so, do they have identical meaning?
I can imagine, say, a refactoring tool that didn't understand the full Python+MyPy syntax being able to do things with the former that it couldn't with the latter. And I think it might look more natural for simple recursive types. But those are just vague feelings, so don't take this as a suggestion.
Sent from a random iPhone
On Jan 16, 2015, at 9:17, Guido van Rossum
After a long editing process we've got PEP 484 (Type Hints) ready for your review. This is by no means final, and several areas are either open (to be resolved in a later draft) or postponed (to a different PEP altogether). But there's enough meat that I think we can start having the discussion. Please also see PEP 483 (The Theory of Type Hint; copied and reformatted from the original Quip document that I posted just before last Christmas) and PEP 482 (Literature Overview for Type Hints, by Łukasz). Those are informational PEPs though; the actual spec is focused in PEP 484 (the only one on the Standards Track).
As I said earlier, I hope to have a rough consensus before PyCon and working code (just the typing.py module, really) committed to CPython before the last 3.5 alpha.
Here is the raw text of PEP 484. Fire away!!
PEP: 484 Title: Type Hints Version: $Revision$ Last-Modified: $Date$ Author: Guido van Rossum
, Jukka Lehtosalo , Łukasz Langa Discussions-To: Python-Dev Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 29-Sep-2014 Post-History: 16-Jan-2015 Resolution: Abstract ========
This PEP introduces a standard syntax for type hints using annotations on function definitions.
The proposal is strongly inspired by mypy [mypy]_.
The theory behind type hints and gradual typing is explained in PEP 483.
Rationale and Goals ===================
PEP 3107 added support for arbitrary annotations on parts of a function definition. Although no meaning was assigned to annotations then, there has always been an implicit goal to use them for type hinting, which is listed as the first possible use case in said PEP.
This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and performance optimizations utilizing type information.
Type Definition Syntax ======================
The syntax leverages PEP 3107-style annotations with a number of extensions described in sections below. In its basic form, type hinting is used by filling function annotations with classes::
def greeting(name: str) -> str: return 'Hello ' + name
This denotes that the expected type of the ``name`` argument is ``str``. Analogically, the expected return type is ``str``. Subclasses of a specified argument type are also accepted as valid types for that argument.
Abstract base classes, types available in the ``types`` module, and user-defined classes may be used as type hints as well. Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined. In addition, the needs of static analysis require that annotations must be simple enough to be interpreted by static analysis tools. (This is an intentionally somewhat vague requirement.)
.. FIXME: Define rigorously what is/isn't supported.
When used as an annotation, the expression ``None`` is considered equivalent to ``NoneType`` (i.e., ``type(None)`` for type hinting purposes.
Type aliases are also valid type hints::
integer = int
def retry(url: str, retry_count: integer): ...
New names that are added to support features described in following sections are available in the ``typing`` package.
Callbacks ---------
Frameworks expecting callback functions of specific signatures might be type hinted using ``Callable[[Arg1Type, Arg2Type], ReturnType]``. Examples::
from typing import Any, AnyArgs, Callable
def feeder(get_next_item: Callable[[], Item]): ...
def async_query(on_success: Callable[[int], None], on_error: Callable[[int, Exception], None]): ...
def partial(func: Callable[AnyArgs, Any], *args): ...
Since using callbacks with keyword arguments is not perceived as a common use case, there is currently no support for specifying keyword arguments with ``Callable``.
Generics --------
Since type information about objects kept in containers cannot be statically inferred in a generic way, abstract base classes have been extended to support subscription to denote expected types for container elements. Example::
from typing import Mapping, Set
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]): ...
Generics can be parametrized by using a new factory available in ``typing`` called ``TypeVar``. Example::
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function return l[0]
In this case the contract is that the returning value is consistent with the elements held by the collection.
``TypeVar`` supports constraining parametric types to classes with any of the specified bases. Example::
from typing import Iterable
X = TypeVar('X') Y = TypeVar('Y', Iterable[X])
def filter(rule: Callable[[X], bool], input: Y) -> Y: ...
.. FIXME: Add an example with multiple bases defined.
In the example above we specify that ``Y`` can be any subclass of Iterable with elements of type ``X``, as long as the return type of ``filter()`` will be the same as the type of the ``input`` argument.
.. FIXME: Explain more about how this works.
Forward references ------------------
When a type hint contains names that have not been defined yet, that definition may be expressed as a string, to be resolved later. For example, instead of writing::
def notify_by_email(employees: Set[Employee]): ...
one might write::
def notify_by_email(employees: 'Set[Employee]'): ...
.. FIXME: Rigorously define this. Defend it, or find an alternative.
Union types -----------
Since accepting a small, limited set of expected types for a single argument is common, there is a new special factory called ``Union``. Example::
from typing import Union
def handle_employees(e: Union[Employee, Sequence[Employee]]): if isinstance(e, Employee): e = [e] ...
A type factored by ``Union[T1, T2, ...]`` responds ``True`` to ``issubclass`` checks for ``T1`` and any of its subclasses, ``T2`` and any of its subclasses, and so on.
One common case of union types are *optional* types. By default, ``None`` is an invalid value for any type, unless a default value of ``None`` has been provided in the function definition. Examples::
def handle_employee(e: Union[Employee, None]): ...
As a shorthand for ``Union[T1, None]`` you can write ``Optional[T1]``; for example, the above is equivalent to::
from typing import Optional
def handle_employee(e: Optional[Employee]): ...
An optional type is also automatically assumed when the default value is ``None``, for example::
def handle_employee(e: Employee = None): ...
This is equivalent to::
def handle_employee(e: Optional[Employee] = None): ...
.. FIXME: Is this really a good idea?
A special kind of union type is ``Any``, a class that responds ``True`` to ``issubclass`` of any class. This lets the user explicitly state that there are no constraints on the type of a specific argument or return value.
Platform-specific type checking -------------------------------
In some cases the typing information will depend on the platform that the program is being executed on. To enable specifying those differences, simple conditionals can be used::
from typing import PY2, WINDOWS
if PY2: text = unicode else: text = str
def f() -> text: ...
if WINDOWS: loop = ProactorEventLoop else: loop = UnixSelectorEventLoop
Arbitrary literals defined in the form of ``NAME = True`` will also be accepted by the type checker to differentiate type resolution::
DEBUG = False ... if DEBUG: class Tracer: <verbose implementation> else: class Tracer: <dummy implementation>
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.
Compatibility with other uses of function annotations -----------------------------------------------------
A number of existing or potential use cases for function annotations exist, which are incompatible with type hinting. These may confuse a static type checker. However, since type hinting annotations have no run time behavior (other than evaluation of the annotation expression and storing annotations in the ``__annotations__`` attribute of the function object), this does not make the program incorrect -- it just makes it issue warnings when a static analyzer is used.
To mark portions of the program that should not be covered by type hinting, use the following:
* a ``@no_type_checks`` decorator on classes and functions
* a ``# type: ignore`` comment on arbitrary lines
.. FIXME: should we have a module-wide comment as well?
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 if was declared, an ``Undefined`` placeholder might be used::
from typing import Undefined
x = Undefined # type: List[Employee] y = Undefined(int)
If type hinting proves useful in general, a syntax for typing variables may be provided in a future Python version.
Explicit raised exceptions ==========================
No support for listing explicitly raised exceptions is being defined by this PEP. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring.
The ``typing`` package ======================
To open the usage of static type checking to Python 3.5 as well as older versions, a uniform namespace is required. For this purpose, a new package in the standard library is introduced called ``typing``. It holds a set of classes representing builtin types with generics, namely:
* Dict, used as ``Dict[key_type, value_type]``
* List, used as ``List[element_type]``
* Set, used as ``Set[element_type]``. See remark for ``AbstractSet`` below.
* FrozenSet, used as ``FrozenSet[element_type]``
* Tuple, used as ``Tuple[index0_type, index1_type, ...]``. Arbitrary-length tuples might be expressed using ellipsis, in which case the following arguments are considered the same type as the last defined type on the tuple.
It also introduces factories and helper members needed to express generics and union types:
* Any, used as ``def get(key: str) -> Any: ...``
* Union, used as ``Union[Type1, Type2, Type3]``
* TypeVar, used as ``X = TypeVar('X', Type1, Type2, Type3)`` or simply ``Y = TypeVar('Y')``
* Undefined, used as ``local_variable = Undefined # type: List[int]`` or ``local_variable = Undefined(List[int])`` (the latter being slower during runtime)
* Callable, used as ``Callable[[Arg1Type, Arg2Type], ReturnType]``
* AnyArgs, used as ``Callable[AnyArgs, ReturnType]``
* AnyStr, equivalent to ``TypeVar('AnyStr', str, bytes)``
All abstract base classes available in ``collections.abc`` are importable from the ``typing`` package, with added generics support:
* ByteString
* Callable
* Container
* Hashable
* ItemsView
* Iterable
* Iterator
* KeysView
* Mapping
* MappingView
* MutableMapping
* MutableSequence
* MutableSet
* Sequence
* Set as ``AbstractSet``. This name change was required because ``Set`` in the ``typing`` module means ``set()`` with generics.
* Sized
* ValuesView
* Mapping
The library includes literals for platform-specific type hinting:
* PY2
* PY3, equivalent to ``not PY2``
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
The following types are available in the ``typing.io`` module:
* IO
* BinaryIO
* TextIO
The following types are provided by the ``typing.re`` module:
* Match and Pattern, types of ``re.match()`` and ``re.compile()`` results
As a convenience measure, types from ``typing.io`` and ``typing.re`` are also available in ``typing`` (quoting Guido, "There's a reason those modules have two-letter names.").
The place of the ``typing`` module in the standard library ----------------------------------------------------------
.. FIXME: complete this section
Usage Patterns ==============
The main use case of type hinting is static analysis using an external tool without executing the analyzed program. Existing tools used for that purpose like ``pyflakes`` [pyflakes]_ or ``pylint`` [pylint]_ might be extended to support type checking. New tools, like mypy's ``mypy -S`` mode, can be adopted specifically for this purpose.
Type checking based on type hints is understood as a best-effort mechanism. In other words, whenever types are not annotated and cannot be inferred, the type checker considers such code valid. Type errors are only reported in case of explicit or inferred conflict. Moreover, as a mechanism that is not tied to execution of the code, it does not affect runtime behaviour. In other words, even in the case of a typing error, the program will continue running.
The implementation of a type checker, whether linting source files or enforcing type information during runtime, is out of scope for this PEP.
.. FIXME: Describe stub modules.
.. FIXME: Describe run-time behavior of generic types.
Existing Approaches ===================
PEP 482 lists existing approaches in Python and other languages.
Is type hinting Pythonic? =========================
Type annotations provide important documentation for how a unit of code should be used. Programmers should therefore provide type hints on public APIs, namely argument and return types on functions and methods considered public. However, because types of local and global variables can be often inferred, they are rarely necessary.
The kind of information that type hints hold has always been possible to achieve by means of docstrings. In fact, a number of formalized mini-languages for describing accepted arguments have evolved. Moving this information to the function declaration makes it more visible and easier to access both at runtime and by static analysis. Adding to that the notion that “explicit is better than implicit”, type hints are indeed *Pythonic*.
Acknowledgements ================
This document could not be completed without valuable input, encouragement and advice from Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, and Radomir Dopieralski.
Influences include existing languages, libraries and frameworks mentioned in PEP 482. Many thanks to their creators, in alphabetical order: Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer, Raoul-Gabriel Urma, and Julien Verlaguet.
References ==========
.. [mypy] http://mypy-lang.org
.. [pyflakes] https://github.com/pyflakes/pyflakes/
.. [pylint] http://www.pylint.org
Copyright =========
This document has been placed in the public domain.
.. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
-- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, Jan 16, 2015 at 12:14 PM, Andrew Barnert < abarnert@yahoo.com.dmarc.invalid> wrote:
One quick clarification question:
Can strings-as-forward-declarations be used inside non-string type annotations--e.g.. Set['Employee'] instead of 'Set[Employee]'? If so, do they have identical meaning?
Yes. There's a FIXME in the PEP source to define forward references rigorously. See also https://github.com/ambv/typehinting/issues/34
I can imagine, say, a refactoring tool that didn't understand the full Python+MyPy syntax being able to do things with the former that it couldn't with the latter. And I think it might look more natural for simple recursive types. But those are just vague feelings, so don't take this as a suggestion.
Well, the most common use case is where you are referencing the current class (which is in the process of being defined but isn't actually created until the entire class body is finished). E.g. class Node(Generic[T]): def add_left(self, node: 'Node[T]'): ... -- --Guido van Rossum (python.org/~guido)
On Jan 16, 2015, at 13:34, Guido van Rossum
On Fri, Jan 16, 2015 at 12:14 PM, Andrew Barnert
wrote: One quick clarification question:
Can strings-as-forward-declarations be used inside non-string type annotations--e.g.. Set['Employee'] instead of 'Set[Employee]'? If so, do they have identical meaning?
Yes. There's a FIXME in the PEP source to define forward references rigorously. See also https://github.com/ambv/typehinting/issues/34
OK, thanks. I see that MyPy already supports this (List['A'] is valid per Jukka's comment, and from a quick test it seems to be identical to 'List[A]').
I can imagine, say, a refactoring tool that didn't understand the full Python+MyPy syntax being able to do things with the former that it couldn't with the latter. And I think it might look more natural for simple recursive types. But those are just vague feelings, so don't take this as a suggestion.
Well, the most common use case is where you are referencing the current class (which is in the process of being defined but isn't actually created until the entire class body is finished). E.g.
class Node(Generic[T]): def add_left(self, node: 'Node[T]'): ...
Sure, but in that case you're using a generic class being defined, with a declared typevar as a parameter. It's the other way around that's interesting--when the class being defined is a parameter to a declared generic type, like: class Node: def descendants(self) -> Set['Node']: ...
"On Fri, Jan 16, 2015 at 6:17 PM, Guido van Rossum
After a long editing process we've got PEP 484 (Type Hints) ready for your review. This is by no means final, and several areas are either open (to be resolved in a later draft) or postponed (to a different PEP altogether). But there's enough meat that I think we can start having the discussion. Please also see PEP 483 (The Theory of Type Hint; copied and reformatted from the original Quip document that I posted just before last Christmas) and PEP 482 (Literature Overview for Type Hints, by Łukasz). Those are informational PEPs though; the actual spec is focused in PEP 484 (the only one on the Standards Track).
As I said earlier, I hope to have a rough consensus before PyCon and working code (just the typing.py module, really) committed to CPython before the last 3.5 alpha.
Here is the raw text of PEP 484. Fire away!!
[...]
* Tuple, used as ``Tuple[index0_type, index1_type, ...]``. Arbitrary-length tuples might be expressed using ellipsis, in which case the following arguments are considered the same type as the last defined type on the tuple.
How would Tuple[...] or Tuple[t1, ..., t2] work? The PEP should say such shenanigans are undefined, rather than leave it to interpretation. Also, does the empty tuple match Tuple[int, ...]? [...]
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
I wonder what the use cases are here. Is this useful, as a dichotomy, outside of asyncio (or other low-level stdlib code)?
On Fri, Jan 16, 2015 at 1:08 PM, Petr Viktorin
"On Fri, Jan 16, 2015 at 6:17 PM, Guido van Rossum
wrote: [...] * Tuple, used as ``Tuple[index0_type, index1_type, ...]``. Arbitrary-length tuples might be expressed using ellipsis, in which case the following arguments are considered the same type as the last defined type on the tuple.
How would Tuple[...] or Tuple[t1, ..., t2] work? The PEP should say such shenanigans are undefined, rather than leave it to interpretation.
Good point. This needs to be elaborated. See https://github.com/ambv/typehinting/issues/30. (Wanna submit a pull request?)
Also, does the empty tuple match Tuple[int, ...]?
I think it probably does. At least the corresponding thing for lists works in mypy: x = [] # type: List[int] So I think we should allow a similar thing for tuples: x = () # type: Tuple[int, ...]
[...]
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
I wonder what the use cases are here. Is this useful, as a dichotomy, outside of asyncio (or other low-level stdlib code)?
The stdlib is full of things that work differently on Windows. Consequently application code will also often contain things like if <windows>: <do it the Windows way> else: <do it the other way> Typically each branch can only be type-checked if the stdlib for that particular platform is accessible. So it makes sense to standardize on a way of spelling the test in a way that static checkers can understand. -- --Guido van Rossum (python.org/~guido)
On Fri, Jan 16, 2015 at 10:47 PM, Guido van Rossum
On Fri, Jan 16, 2015 at 1:08 PM, Petr Viktorin
wrote: "On Fri, Jan 16, 2015 at 6:17 PM, Guido van Rossum
wrote: [...] * Tuple, used as ``Tuple[index0_type, index1_type, ...]``. Arbitrary-length tuples might be expressed using ellipsis, in which case the following arguments are considered the same type as the last defined type on the tuple.
How would Tuple[...] or Tuple[t1, ..., t2] work? The PEP should say such shenanigans are undefined, rather than leave it to interpretation.
Good point. This needs to be elaborated. See https://github.com/ambv/typehinting/issues/30. (Wanna submit a pull request?)
Yes.
[...]
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
I wonder what the use cases are here. Is this useful, as a dichotomy, outside of asyncio (or other low-level stdlib code)?
The stdlib is full of things that work differently on Windows. Consequently application code will also often contain things like
if <windows>: <do it the Windows way> else: <do it the other way>
Typically each branch can only be type-checked if the stdlib for that particular platform is accessible. So it makes sense to standardize on a way of spelling the test in a way that static checkers can understand.
So "unixoid" just means "not Windows"? Any new platform Python will be ported to (looking at Micropython for example) is expected to be "unixoid"? Can it just be spelled ``not WINDOWS``?
On Sat, Jan 17, 2015 at 9:37 AM, Petr Viktorin
So "unixoid" just means "not Windows"? Any new platform Python will be ported to (looking at Micropython for example) is expected to be "unixoid"? Can it just be spelled ``not WINDOWS``?
I've only just now figured out that the "oid" here is the suffix meaning "similar/related to", and has nothing to do with object IDs or other uses of the term OID. Could it be spelled POSIXISH or UNIXISH or something? The -ish suffix doesn't collide with as many computer-related concepts. ChrisA
On Fri, Jan 16, 2015 at 3:58 PM, Chris Angelico
On Sat, Jan 17, 2015 at 9:37 AM, Petr Viktorin
wrote: So "unixoid" just means "not Windows"? Any new platform Python will be ported to (looking at Micropython for example) is expected to be "unixoid"? Can it just be spelled ``not WINDOWS``?
I've only just now figured out that the "oid" here is the suffix meaning "similar/related to", and has nothing to do with object IDs or other uses of the term OID. Could it be spelled POSIXISH or UNIXISH or something? The -ish suffix doesn't collide with as many computer-related concepts.
The current plan is to spell it POSIX. -- --Guido van Rossum (python.org/~guido)
On Jan 16, 2015, at 13:47, Guido van Rossum
On Fri, Jan 16, 2015 at 1:08 PM, Petr Viktorin
wrote: "On Fri, Jan 16, 2015 at 6:17 PM, Guido van Rossum
wrote: [...] * WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
I wonder what the use cases are here. Is this useful, as a dichotomy, outside of asyncio (or other low-level stdlib code)?
The stdlib is full of things that work differently on Windows.
The stdlib is also full of things that work one way on Windows, another way on Unix, and another way (or, often, not at all) on (all, or specific) other platforms, I realize nobody is building Python 3.5 for classic Mac or Symbian S60 or MicroVMS, but do we need to bake in as a permanent assumption that Python will never again run on a non-Windows non-POSIX-ish system? You already have both WINDOWS and UNIXOID; why not just remove the statement that one is the negation of the other?
On Fri, Jan 16, 2015 at 4:49 PM, Andrew Barnert < abarnert@yahoo.com.dmarc.invalid> wrote:
On Jan 16, 2015, at 13:47, Guido van Rossum
wrote: On Fri, Jan 16, 2015 at 1:08 PM, Petr Viktorin
wrote: "On Fri, Jan 16, 2015 at 6:17 PM, Guido van Rossum
wrote: [...]
* WINDOWS
* UNIXOID, equivalent to ``not WINDOWS``
I wonder what the use cases are here. Is this useful, as a dichotomy, outside of asyncio (or other low-level stdlib code)?
The stdlib is full of things that work differently on Windows.
The stdlib is also full of things that work one way on Windows, another way on Unix, and another way (or, often, not at all) on (all, or specific) other platforms,
I realize nobody is building Python 3.5 for classic Mac or Symbian S60 or MicroVMS, but do we need to bake in as a permanent assumption that Python will never again run on a non-Windows non-POSIX-ish system?
You already have both WINDOWS and UNIXOID; why not just remove the statement that one is the negation of the other?
Because that's going to be the assumption that people will make anyway. -- --Guido van Rossum (python.org/~guido)
Forward references https://www.python.org/dev/peps/pep-0484/#id9
When a type hint contains names that have not been defined yet, that definition may be expressed as a string, to be resolved later.
Is it intentionally undefined when exactly "later" is? Silly example: from typing import Set def foo(bar: 'Set[Employee]') -> None: ... class Employee: ... def foo2(bar: Set[Employee]) -> None: ... class Set(Generic[T]): # oops, I didn't pay attention and redefined Set ... A conforming type checker could interpret the type of foo in two ways so that foo either takes a normal set (resolved right after Employee was defined) or an instance of the new class Set (what Set refers to at the end of the file). In the first case, the signatures of foo and foo2 are the same, in the second one, foo2(bar: Set[Employee]) and foo2(bar: 'Set[Employee]') mean different things. Of course, a good style checker should have slapped the user for redefining Set, so it's more of a academic problem and leaving "later" vague doesn't matter in practise, but I'd still document the fact that conforming implementations are free to choose what later means.
On Fri, Jan 16, 2015 at 1:39 PM, Dennis Brakhane
Forward references https://www.python.org/dev/peps/pep-0484/#id9
When a type hint contains names that have not been defined yet, that definition may be expressed as a string, to be resolved later.
Is it intentionally undefined when exactly "later" is?
I doubt it is. But specifying the exact semantics of every nit of the static type checking algorithm is beyond the scope of the PEP -- it would be as complex as the implementation of mypy. The intention is that this is a forward reference to something defined at some later point in the same file.
Silly example:
from typing import Set
def foo(bar: 'Set[Employee]') -> None: ...
class Employee: ...
def foo2(bar: Set[Employee]) -> None: ...
class Set(Generic[T]): # oops, I didn't pay attention and redefined Set ...
I think a static checker should always warn about such redefinitions.
A conforming type checker could interpret the type of foo in two ways so that foo either takes a normal set (resolved right after Employee was defined) or an instance of the new class Set (what Set refers to at the end of the file).
In the first case, the signatures of foo and foo2 are the same, in the second one, foo2(bar: Set[Employee]) and foo2(bar: 'Set[Employee]') mean different things.
Of course, a good style checker should have slapped the user for redefining Set, so it's more of a academic problem and leaving "later" vague doesn't matter in practise, but I'd still document the fact that conforming implementations are free to choose what later means.
Right. This doesn't really bother me much. -- --Guido van Rossum (python.org/~guido)
Very nice! Some comments/questions below. On Fri, Jan 16, 2015 at 09:17:36AM -0800, Guido van Rossum wrote:
Type Definition Syntax ====================== [...] An optional type is also automatically assumed when the default value is ``None``, for example::
def handle_employee(e: Employee = None): ...
This is equivalent to::
def handle_employee(e: Optional[Employee] = None): ...
.. FIXME: Is this really a good idea?
I think it is. It seems to me to be a form of type inference: you are declaring that e is an Employee, but inferring that it could also be None. Either the type system reports this as an error, or it infers that the type is intended to be optional. Given how common it is for arguments to be "X or None" having shorthand for that is a good thing.
Platform-specific type checking -------------------------------
In some cases the typing information will depend on the platform that the program is being executed on. To enable specifying those differences, simple conditionals can be used::
Since CPython isn't going to do type-checking itself, instead it will leave it up to external tools. Given that, I assume that you are specifying the minimum requirements for such a tool, rather than a declaration of what some specific tool will do. Correct? Hence, type-checkers should be able to deal with "simple conditionals" as in the examples given, and are not required to emulate a full Python interpreter in order to make sense of arbitrarily complex conditionals like: if (PY2 and WINDOWS) or (PY3 and POSIX): ... But a sophisticated type-checker may offer more than the minimum. Is this correct? There are a number of places where the PEP mentions "the type checker", e.g. the next paragraph. I think the PEP should clarify what exactly is meant by that.
Arbitrary literals defined in the form of ``NAME = True`` will also be accepted by the type checker to differentiate type resolution::
As above. How arbitrary are the literals? Only bool flags? If so, the PEP should say "Boolean literals" rather than "arbitrary literals".
Compatibility with other uses of function annotations -----------------------------------------------------
A number of existing or potential use cases for function annotations exist, which are incompatible with type hinting. These may confuse a static type checker. However, since type hinting annotations have no run time behavior (other than evaluation of the annotation expression and storing annotations in the ``__annotations__`` attribute of the function object), this does not make the program incorrect -- it just makes it issue warnings when a static analyzer is used.
I understood from early discussions that type checking would only be enabled if you imported `typing`. If so, then the easy way to disable type checks for a whole module was not to import anything from `typing`.
To mark portions of the program that should not be covered by type hinting, use the following:
* a ``@no_type_checks`` decorator on classes and functions
* a ``# type: ignore`` comment on arbitrary lines
.. FIXME: should we have a module-wide comment as well?
Rather than have to comment each and every line, can we marking entire sections with a directive? # type: off ... ... # type: on ... This will still allow `type: ignore` if you want to skip a single line, and allow module-wide control by simply starting your module with `type: off` and not turning it back on. Where will the @no_type_checks decorator live? In the typing module or as a builtin? I see from below that directives of the form `type: spam` are allowed. That could clash with `type: ignore` etc. Although it is not PEP 8 compliant, people might have classes called "ignore", "on", "off". How about we require keywords to the type directive to have a leading + sign? # type: +ignore # Keyword "ignore", don't type check this line. # type: ignore # Declare the type is class "ignore". Otherwise there is going to be the risk of clashes.
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]
Without the type declaration, x would be inferred to be of type List[Any]. Is that correct? Perhaps that should go into the PEP.
In the case where type information for a local variable is needed before if was declared, an ``Undefined`` placeholder might be used:: ^^^^^
Typo, should read "it was declared". -- Steven
On Fri, Jan 16, 2015 at 9:49 PM, Steven D'Aprano
On Fri, Jan 16, 2015 at 09:17:36AM -0800, Guido van Rossum wrote:
Type Definition Syntax ====================== [...] An optional type is also automatically assumed when the default value is ``None``, for example::
def handle_employee(e: Employee = None): ...
This is equivalent to::
def handle_employee(e: Optional[Employee] = None): ...
.. FIXME: Is this really a good idea?
I think it is. It seems to me to be a form of type inference: you are declaring that e is an Employee, but inferring that it could also be None. Either the type system reports this as an error, or it infers that the type is intended to be optional. Given how common it is for arguments to be "X or None" having shorthand for that is a good thing.
Agreed. I'm removing the FIXME.
Platform-specific type checking -------------------------------
In some cases the typing information will depend on the platform that the program is being executed on. To enable specifying those differences, simple conditionals can be used::
Since CPython isn't going to do type-checking itself, instead it will leave it up to external tools. Given that, I assume that you are specifying the minimum requirements for such a tool, rather than a declaration of what some specific tool will do. Correct?
Yes. A type checker is an immensely complex tool, and there are many gray areas where the PEP just can't hope to be rigorous. In the end the use of the type checker is meant as a linter, not as the rigorous definition of the language (which can do things no type checker can understand).
Hence, type-checkers should be able to deal with "simple conditionals" as in the examples given, and are not required to emulate a full Python interpreter in order to make sense of arbitrarily complex conditionals like:
if (PY2 and WINDOWS) or (PY3 and POSIX): ...
That already feels too complex -- not because the checker couldn't figure out the value, but because I don't want to support complex expressions in other places (before you know it, you're using conditionals in argument annotations, and I really don't want to go there).
But a sophisticated type-checker may offer more than the minimum. Is this correct?
Sure.
There are a number of places where the PEP mentions "the type checker", e.g. the next paragraph. I think the PEP should clarify what exactly is meant by that.
You're right. There's some language in the "Usage Patterns" sections but it makes more sense to explain this upfront. (Also that section hints at runtime checking, which really should be considered quite out of scope.)
Arbitrary literals defined in the form of ``NAME = True`` will also be accepted by the type checker to differentiate type resolution::
As above. How arbitrary are the literals? Only bool flags? If so, the PEP should say "Boolean literals" rather than "arbitrary literals".
Agreed. I think we need to work with Jukka on a spec of what the minimum complexity is that the checker should support for this purpose (even though in other contexts it of course must understand non-constant expressions, since it must type-check them :-).
Compatibility with other uses of function annotations -----------------------------------------------------
A number of existing or potential use cases for function annotations exist, which are incompatible with type hinting. These may confuse a static type checker. However, since type hinting annotations have no run time behavior (other than evaluation of the annotation expression and storing annotations in the ``__annotations__`` attribute of the function object), this does not make the program incorrect -- it just makes it issue warnings when a static analyzer is used.
I understood from early discussions that type checking would only be enabled if you imported `typing`. If so, then the easy way to disable type checks for a whole module was not to import anything from `typing`.
I changed my mind about that (and I don't think mypy has ever strictly followed this rule). The reason is that it's quite sensible to use built-in types (e.g. int) and user-defined classes as type annotations and expect the checker to do its thing. It's only when you need things like generic classes, unions etc. that you must import typing.
To mark portions of the program that should not be covered by type hinting, use the following:
* a ``@no_type_checks`` decorator on classes and functions
* a ``# type: ignore`` comment on arbitrary lines
.. FIXME: should we have a module-wide comment as well?
Rather than have to comment each and every line, can we marking entire sections with a directive?
# type: off ... ... # type: on ...
This will still allow `type: ignore` if you want to skip a single line, and allow module-wide control by simply starting your module with `type: off` and not turning it back on.
We don't have the definitive design. See https://github.com/ambv/typehinting/issues/16 and https://github.com/ambv/typehinting/issues/35 .
Where will the @no_type_checks decorator live? In the typing module or as a builtin?
In the typing module.
I see from below that directives of the form `type: spam` are allowed. That could clash with `type: ignore` etc. Although it is not PEP 8 compliant, people might have classes called "ignore", "on", "off". How about we require keywords to the type directive to have a leading + sign?
# type: +ignore # Keyword "ignore", don't type check this line. # type: ignore # Declare the type is class "ignore".
I'm not keen on the special meaning of +. We might using "# typing: ..." for directives, or we might just use some all_caps names, e.g. "# type: IGNORE".
Otherwise there is going to be the risk of clashes.
I think the risk of clashes can be made sufficiently low that we can say "tough luck".
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]
Without the type declaration, x would be inferred to be of type List[Any]. Is that correct? Perhaps that should go into the PEP.
Correct. (What else could it be? Although I think that if x has been given a type earlier, the [] is checked against that type.) If you think the PEP is insufficiently clear, could you submit a PR? (BTW, to anyone else considering PRs against the GitHub repo, please create a regular issue when you want to change the proposed behavior or syntax. PRs are good for clarifications, typos etc.)
In the case where type information for a local variable is needed before if was declared, an ``Undefined`` placeholder might be used:: ^^^^^
Typo, should read "it was declared".
Will fix. (I also think it should be "it is declared".) -- --Guido van Rossum (python.org/~guido)
The PEP does not mention MyPy style casts. Is this intentional? If yes, are casts not supported at all, or is there just no cast in typing and people should use type: comments instead? x = [1,2,3] # type: Iterable[int] y = x # type: Sequence[int]
On Sat, Jan 17, 2015 at 8:14 AM, Dennis Brakhane
The PEP does not mention MyPy style casts. Is this intentional?
No, we ran out of time for the first draft (and Łukasz needed some pushing to like them :-). The conclusion in https://github.com/ambv/typehinting/issues/15 is that we will add it.
If yes, are casts not supported at all, or is there just no cast in typing and people should use type: comments instead?
x = [1,2,3] # type: Iterable[int] y = x # type: Sequence[int]
The issue I linked to also explains the difference (it's subtle!). If someone wants to give describing cast() in the PEP a shot, send a PR! -- --Guido van Rossum (python.org/~guido)
On 1/16/2015 12:17 PM, Guido van Rossum wrote:
The following types are available in the ``typing.io http://typing.io`` module:
The following types are provided by the ``typing.re http://typing.re`` module:
As a convenience measure, types from ``typing.io http://typing.io`` and ``typing.re http://typing.re`` are
Something is over-eager in seeing urls and adding http: web links. -- Terry Jan Reedy
On Jan 16, 2015 6:18 PM, "Guido van Rossum"
<snip>
I apologize if I missed this, but is there any support for duck-type hinting, that is somehow defining a type hint by what attributes or methods it has rather than by what it is a subclass of? Or is this something that is being intentionally avoided?
On Jan 19, 2015, at 0:59, Todd
On Jan 16, 2015 6:18 PM, "Guido van Rossum"
wrote: <snip>
I apologize if I missed this, but is there any support for duck-type hinting, that is somehow defining a type hint by what attributes or methods it has rather than by what it is a subclass of? Or is this something that is being intentionally avoided?
I think you're asking about what's called the "protocols" sub-feature, which has been put off until post-3.5, and probably a separate PEP. See issue #11 on the tracker. As I understand it, the issue is unifying static typing protocols with ABCs that do the same thing with subclass hooks (and possibly other things like Zope's more complex feature).
participants (33)
-
Andrew Barnert
-
Ben Finney
-
Brett Cannon
-
Cem Karan
-
Chris Angelico
-
Chris Barker
-
Chris Barker - NOAA Federal
-
David Mertz
-
Dennis Brakhane
-
Devin Jeanpierre
-
Ed Kellett
-
Ethan Furman
-
Eugene Toder
-
Fetchinson .
-
Georg Brandl
-
Greg
-
Greg Ewing
-
Guido van Rossum
-
Ian Cordasco
-
Juancarlo Añez
-
Matt
-
Nathaniel Smith
-
Paul Moore
-
Petr Viktorin
-
Philipp A.
-
Skip Montanaro
-
Stefan Behnel
-
Stephen Hansen
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy
-
Todd
-
Yann Kaiser