Here are resources for more information about annotations and PEP 593:
Annotated Type Hints
00:00 In the previous lesson, I showed you the new syntax for decorators. In this lesson, I’m going to show you annotated type hints. Python 3 has had syntax for annotations for a while.
The original proposed use was to provide extra information about a parameter—for example, the units for a variable. In the code below, the annotation of
"seconds", and the return annotation of
"miles per hour" tells the developer what units should be used with the values passed into this function.
00:32 PEP 484 introduced the use of annotations for type hints. This goes beyond the case of what’s being shown here of units, and indicating the type of the variable being used. Type hints have become the most common use of annotations. Because of this, and because a lot of tools are now using it, the previous use has kind of been eclipsed.
Python 3.9 introduces the
typing.Annotated class, which allows you to use both kinds of concepts. You can provide the type hint as well as meta information—like the units—about the type hint.
Here’s an example from the
speed_units file showing the use of applying annotations to indicate the units for the function. This is a shorter version of the code I showed you in the slides.
Running this, I get the return value of 3.4 miles per hour, and I can look at the
.__annotations__ dictionary associated with the function, which shows the attributes that are in the function and the annotations for each.
Changing over to the
speed_types file, this is a typical use of type hints. Now, instead of units, it’s showing that the distance, the time, and the return are all floats.
The execution here is no different, but the annotations are. It shows that the attributes are associated with
float types. The new
Annotated class in Python 3.9 lets you do both of these things.
In this case, on line 6, I’m creating an annotation which is a combination of
float and the units
"feet". On line 7, I do this again for
"miles per hour". In the function declaration on line 9, I can use these annotations—
"miles per hours" as the return—or I can just put the class directly inside of it, like I have with
02:44 Like before, the execution’s the same,
but now the
.__annotations__ dictionary has these
Accessing the dictionary in order to get the
Annotated class and then using the
.__metadata__ attribute, you can get the annotation units.
You can also use
get_type_hints() function to get the information.
03:14 Running it on the function here shows the types associated with the function.
Calling it with the
include_extras parameter set to
True, you get the
Annotated classes inside of it as well. This means you—as the programmer—no longer have to make the decision between whether you’re annotating for types or annotating for units. You can do both.
03:41 If you’re finding that you’re doing a lot of type hints with units, a useful utility class would be an annotation factory, like the one shown here. This factory allows you to create annotation classes that are bound to a type, and then when you use them, you specify the units.
03:59 Each instance of the factory is going to require a type that is going to be associated with the instance. So in the constructor here, the type is being stored.
The annotation classes are accessed using square brackets, like dictionaries. The square bracket syntax triggers
.__getitem__() inside of a class, so this function is what will be called when the class is called with the square brackets.
There are two possible ways of calling the instance: one with a tuple and one without a tuple. Because
Annotated takes a tuple, the first thing you do in line 9 and 10 is check if what’s passed in is a tuple. If it is, you join the existing
.type_hint with the tuple that’s being passed in, and use that as the key to the
Annotated class. If it isn’t, then you just directly use the annotation and the type key. Lines 14 and 15 represent the class using the
.__class__.__name__, and the
.type_hint that is constructed.
On line 19, you can see the instantiation of the factory using a float. On line 21, that float is used for distance, time, and the return, passing in the units of
"miles per hour".
This code is far more succinct than what was done in the previous examples.
AnnotationFactory is a pattern that could be useful for you if you’re doing a lot of work that looks like this.
05:25 In the next lesson, I’ll be talking about the new parser inside of Python and changes to how generic type hints work.
@mrnirrozen Looking at the Python source code on GitHub, it seems that pydoc hasn’t been updated to leverage type hints. The few commits that appeared in recent months were cosmetics, while a significant portion of pydoc’s code is over two decades old. But maybe there’s some external tool I’m unaware of that could do that.
Become a Member to join the conversation.
mrnirrozen on Dec. 12, 2021
This new addition is very interesting, how does it integrate with pydoc ?