Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

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.

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.

Avatar image for mrnirrozen

mrnirrozen on Dec. 12, 2021

This new addition is very interesting, how does it integrate with pydoc ?

Avatar image for Bartosz Zaczyński

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.