Creating and Modifying Annotations
Since you now know that the annotations you create are placed into a dictionary called
__annotations__, you can write code that manipulates the annotations dictionary directly.
Since it is a function attribute, you can access it using the dot operator. So if I define a function without any attributes, and then later I would like to add them, I can do it directly by creating the
00:27 Let’s take a look at this. I’m going to define a function, give it two parameters, and we’re not going to worry about what it returns. We just need the function created.
We didn’t create any annotations when we defined this function, but we can go ahead and add them now. It doesn’t have a
__annotations__ attribute yet, but we can create one.
Now, it’s supposed to be a dictionary and the dictionary is supposed to tell us what type, or any information we want about each of the parameters, and the return value. If we model this based on what we had in the last lesson,
a is supposed to be an
int to mean it’s an integer,
b was a
str (string), and for what it returns, that was supposed to be a
And so we can see that when we get the value of
__annotations__, it tells us exactly the same thing it did in the last lesson when we provided those using the annotations notation.
01:35 So those automated tools that look for this information about a function by reading its annotations will see exactly what it would have seen had we created the annotations directly.
Here’s another interesting example, where you can actually write an annotation that can be modified during program execution. We’re going to create a new function, so this is a new function
f(), and we’re going to say that its return annotation is
And so when the
__annotations__ attribute is created, there’s going to be one entry in the dictionary. The keyword is going to be the string
'return', and the value is actually going to be the number
02:16 And so here’s what this function is going to do: because we can access that value within the dictionary—
02:24 we specify the name of the dictionary and the keyword—
this is now referring to the
0, that’s the value that’s there, and I can modify that! I can actually increment that by
1 as the first line of this function.
And then just to see that things are happening here, let’s have an output statement. Let’s print that out using an f-string. We’ll say the function
f() has been executed.
And here we’re going to have the value of
'return' in the dictionary. So,
f.__annotations__, and we want the value associated with the keyword
03:14 And so whatever value is there in the dictionary is going to be displayed in that part of the string. And then just to clean this up, we’ll say how many times it’s been executed.
That closes the f-string, that closes the
and that concludes our function. So, when we define this function, we created the annotations dictionary that had only one key-value pair, the keyword
'return', paired with the value
But each time we run this function, we’re going to get that value and increment it by
1. And so if we call the function, we get that the function has been executed one time. If I call the function again, that value in the annotations dictionary is incremented again, and now we get that this function has been executed twice.
And this will continue to increment every time we call it, and so
__annotations__ is keeping metadata about the function. In this particular case, it’s keeping track of how many times the function has been executed.
Generally, because the annotations you create are stored in a dictionary called
__annotations__ as a dunder attribute, you can access it with the dot operator and use it however you need to.
04:39 Next up, we’ll take a look at how you can write code to enforce any types that you have written in function annotations.
Become a Member to join the conversation.
mp on March 24, 2023
I understand the content here, but what are the use cases where it would be a good idea to modify the annotations dictionary directly in this way?