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

Function Annotations

00:00 As you’ve just seen, docstrings are one way that you can provide documentation for your function. Another way is through what are referred to as function annotations.

00:13 This is a feature of Python that begins with version 3, and it’s a way to attach metadata to your function’s parameters and return value. For a parameter, you create it by following the parameter name with a colon (:) and then providing whatever annotation goes with it. For the return value, following the parameter list you use an arrow (->) and then the annotation before the colon that ends the function header.

00:42 The annotation you create can be any valid expression or object within Python. That information is saved as a dictionary under the dunder attribute __annotations__, which, like other function attributes, you can access using the dot operator.

01:01 So, here’s an example. Create a function f(). It has a parameter called a. We’re going to provide an annotation for it, and that’s just going to be a string that says this is parameter '<a>'.

01:15 Do the same thing for b, and we’ll annotate that with another string—although these don’t have to be strings, this is just one example. And then following the parameter list, we can include an arrow to indicate an annotation for the return value, which in this case, is going to be another string, and then we follow that with a colon.

01:40 We’re not interested in doing anything. We want to access the annotations. So, this creates a dictionary and we can see it using the dot operator and the name __annotations__.

01:53 Remember, a dunder name begins and ends with a double underscore (__). And we can see that we get a dictionary where the keys have been either the parameters as a string or the word 'return', and the values associated with each is the annotation we provided for each one.

02:14 We can access individual values since this is a dictionary. If I want to know the annotation for the parameter a, I can specify it that way, and that’s its annotation.

02:29 And if I want to know the annotation for the return value, I type 'return' as a string for the key to look up in the dictionary, and it tells us the appropriate annotation.

02:43 We don’t have to use strings, and here are a couple of examples. I want to look at the first one right now. I can provide typing information. For example, I can say that a should be an integer and that b should be a string and that this function is going to return a floating-point value, which it does.

03:07 And so if I want to take a look at this…

03:17 There’s a second function in there, and we will look at that in a moment. As I am calling this function, the annotation information shows up to help remind me what types are expected.

03:32 And likewise, if I want to look at the dictionary more closely, I can use the dot operator to get it. Notice now that our annotations describe that they represent objects of type class int, str (string), and float.

03:54 Annotations can become even more complicated, where we can provide for each parameter, for example, a dictionary of its own, where we have, say, one key-value pair to indicate a description of what the parameter is and then another one to give us information about its type.

04:16 Here, we’re going to compute the area of a circle. An annotation for the single parameter r tells us that the description is the radius of a circle and that its type is float.

04:31 And its return annotation provides a dictionary where the description is the key associated with the value 'area of circle', and we get type information as well. And so,

04:48 I can look at this. I can say, “What is the area of a circle?” Notice all of the useful information that I am told about the parameter that will allow me to use the function the way it’s supposed to be.

05:07 I can look at its annotations. I can type it incorrectly.

05:16 I get that the annotation for the radius is this dictionary. The annotation for 'return' is this dictionary. This allows me to learn lots of information and to pin down what I want to know. So, for example, if I want the description of what parameter r is supposed to be, I can look it up. And if I want to know, let’s say, the type of the return value, I can look at the annotation for 'return', which itself is a dictionary.

05:55 One of the keys is the word 'type', and I can look at that information as well.

06:05 If you’re going to include a default value with the annotation, then you would put the value and the equal sign (=) after the annotation.

06:19 Now, annotations aren’t enforced. Whatever’s put in an annotation isn’t enforced anywhere. So, we have this function f() that’s expecting an integer and a string, but I can give it other parameter values

06:41 and the function works just fine.

06:46 They’re not used in any way during the execution of a function. There’s no checking, if we specified types, that the types provided match what the annotation should say.

06:58 They are just used to create the __annotations__ attribute.

07:05 But they do provide good documentation. It adds information right within the function header that might not be apparent if you’re relying on the docstring only.

07:17 And there are tools that can look for a function’s __annotations__ attribute and pull out the annotations provided to provide additional information in how to use that particular function.

07:33 Next, we will take a look at some interesting things you can do knowing that your __annotations__ is simply part of a dictionary, which you can access and manipulate.

Become a Member to join the conversation.