Annotated Type Hints
Here are resources for more information about annotations and PEP 593:
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.
00:12
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 "feet"
, "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.
00:54
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.
01:09
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.
01:27
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.
01:42
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.
02:00
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.
02:15
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 float
and "miles per hour"
. In the function declaration on line 9, I can use these annotations—feet
and "miles per hours"
as the return—or I can just put the class directly inside of it, like I have with time
.
02:44 Like before, the execution’s the same,
02:50
but now the .__annotations__
dictionary has these Annotated
objects.
02:58
Accessing the dictionary in order to get the Annotated
class and then using the .__metadata__
attribute, you can get the annotation units.
03:07
You can also use typing
’s get_type_hints()
function to get the information.
03:14 Running it on the function here shows the types associated with the function.
03:23
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.
04:09
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.
04:24
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.
04:59
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 "feet"
, "seconds"
, and "miles per hour"
.
05:14
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.
Bartosz Zaczyński RP Team on Dec. 13, 2021
@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 ?