back to index

How to Use Type Annotations in Python


Chapters

0:0 Intro
0:55 Datatypes Example in C
2:53 Static and Dynamic Typed Languages
3:47 Type Annotations in Python
4:25 How to Define Simple Types
6:4 IDE Warnings
8:20 More Complex Types
9:53 dict[str, int]
11:38 Union Operator (Py 3.9)
12:34 Union Operator (Py 3.10)
13:21 Optional Operator

Whisper Transcript | Transcript Only Page

00:00:00.000 | Okay so in this video we're going to go through type annotations in Python. So before we actually
00:00:06.960 | get to type annotations in Python itself, I just want to explain a little bit of what
00:00:13.200 | type annotations are. So type annotations are also known as type signatures and they're used
00:00:22.400 | to indicate the data type of variables and the input and outputs of functions and methods in a
00:00:30.400 | programming language. So in a lot of languages data types are explicitly stated, meaning that
00:00:39.360 | if you don't declare your data type your code won't run. So I want to show you an example of a
00:00:48.320 | hello world in C so that we can see what it means to explicitly state our data types in our code.
00:00:54.880 | Over here I have this quick hello world script and you don't need to read or understand C to
00:01:06.080 | understand what is actually happening here. All I want you to focus on is that we have
00:01:11.280 | these two type declarations int and char. So what I'm going to do is first I'm just going to remove
00:01:20.720 | those. So remove the char from hello and this also those square brackets tell us that it's an
00:01:31.440 | array of characters which is what char is. So I'll remove that as well and also remove the int from
00:01:39.840 | main. Now if I save and exit and then try to compile this you have to compile your code when
00:01:50.240 | it is at C which we do like this. Okay we see that we get quite a few errors. So mainly it says
00:02:08.480 | hello is undeclared and the reason we're getting that issue is because we can't declare a variable
00:02:15.040 | without already initializing it with a data type. So it thinks that we're trying to access an
00:02:22.480 | existing variable. So let's go back into our script and we'll just add those two definitions
00:02:31.360 | back into our code. Okay and let's try and compile that again. You see okay it works perfectly. So
00:02:46.880 | we can also just run that code and we get hello world. So we can see that everything is running
00:02:52.560 | perfectly. So I just wanted to use that example before we get into the Python to just demonstrate
00:02:59.040 | that in these other languages we do need to declare types and Python is more forgiving because
00:03:07.680 | we don't need to define types when we create a variable. Now these two approaches to programming
00:03:15.520 | language and data types have a name. So with what we just saw in C where we have to explicitly
00:03:23.360 | define the types that's called a statically typed language. Whereas with Python where we don't have
00:03:31.760 | to explicitly state a data type that is called a dynamically typed language. But I think that is
00:03:42.240 | more than enough on discussing generic type annotations. Let's dive into type annotations
00:03:50.560 | in Python. Now the first thing I want to say is that type annotations in Python are
00:03:56.560 | not make or break like they are in our C example. They're optional chunks of syntax that we can add
00:04:05.760 | to our code to make it more explicit. So if we added type annotations and they didn't quite
00:04:11.920 | match up to what we wrote in our code we wouldn't receive an error. At most we'll get a warning in
00:04:18.960 | our ID. Okay so switching across to Python we define types like this. So this is from Python
00:04:30.000 | 3.9 onwards and what we do is say we have a string type here. We would define it using this syntax
00:04:40.240 | you see in the middle. Okay and then just like usual we would have our string there.
00:04:47.920 | So if we just compare that to what we normally do when we're not defining types
00:04:54.400 | all we get is this. So all we're adding is this little bit here.
00:05:04.800 | Now that's when we're assigning a variable and let's say that we want to do the same but for a
00:05:14.080 | function. So we're going to make this a very simple function where we take two integer values x and y
00:05:25.040 | and we're going to add those together. So what we do is return x plus y. Okay and here what we've
00:05:35.920 | done is specified the actual input types to that function. Now if we just come down here and let's
00:05:44.400 | try this out. Let's create a new value that of course is going to be an integer because that's
00:05:50.320 | what we're expecting from our function up here. And that will obviously be equal to add and here
00:05:57.680 | we put say four and five. Okay and everything looks good right. Now with type annotations in
00:06:07.920 | Python of course Python is a dynamically typed language so if we ran this and there was an error
00:06:15.120 | in our types unless it created an error somewhere else in the language like maybe we tried to add
00:06:22.560 | a string and an integer of course that would raise an error anyway but it wouldn't create an error
00:06:28.000 | due to type annotations. And adding these type annotations won't make it so that we get type
00:06:34.880 | errors that wouldn't already be there. All it does is it allows our IDE to essentially warn us
00:06:44.000 | when something doesn't quite look right. So let's say here we replace that four with a string four.
00:06:53.120 | Okay and I'm going to save this and then we get this little warning underneath so it's highlighted
00:06:59.680 | and if I hover over that we see that it says argument one to add has incompatible type string
00:07:06.880 | expected an integer. Okay now of course usually this would come up with an error anyway so we're
00:07:13.840 | not really doing anything special but say maybe we did both of these four and five.
00:07:19.600 | Now in Python we can add strings together so this wouldn't raise an error
00:07:25.680 | but using the type annotations we still don't get an error but we do get this warning
00:07:32.240 | so at least we're aware that there's something weird going on. Now another thing that I just
00:07:38.880 | want to add to this here is that we can also specify the output type as well. So here of
00:07:47.920 | course we're adding two integers together we'd also expect an integer there. So if we string here
00:07:55.120 | we'd get another warning because we would be expecting to return a string as we specified here
00:08:04.880 | but this returns an integer. So we're getting a lot of warnings now so let's put that all back
00:08:11.120 | to as it was before so this will remove the first warning then we do four and five.
00:08:18.800 | Now everything's good again. Now alongside these basic data types so we have integer, flow,
00:08:28.640 | dictionary, list we can also merge data types which is getting a little more interesting I think.
00:08:35.920 | So with both of these objects here we would define both of them as dictionaries and so if we wanted
00:08:44.320 | to sum over every value within those dictionaries and we also wanted to include type annotations we
00:08:49.920 | could write something like this. So sum dict we have our variable which is just our dictionary so
00:08:59.600 | this is a dictionary type. We could also put that we are expecting a integer out of that so we can
00:09:07.600 | include that in there as well and then I want to return the sum of each of the values within the
00:09:14.640 | dictionary values there. So we could just write that as say we're accessing the key for every key
00:09:24.080 | in the variable dictionary. Now this isn't the best way to write it but this is fine and
00:09:32.800 | now that we've put that function together let's try processing both of our dictionaries
00:09:41.360 | with that function. So we have this string int dictionary now just copy that and we have int int.
00:09:51.200 | Okay and you know everything's fine there's no no warnings there and we wouldn't expect
00:09:59.200 | there to be any warnings there but maybe the dictionary types that we would be expecting
00:10:05.200 | would contain a string as a key and a integer as a value. So we can actually specify that
00:10:13.120 | by adding a little bit more to this type here. So all we do is put in the square brackets
00:10:20.000 | now we have string which is the key type and integer which is the value type.
00:10:30.560 | Now let's save that and we see now we have this warning. Okay so the argument to this
00:10:39.440 | function has an incompatible type which is dictionary integer integer whereas we expected
00:10:45.280 | a dictionary with a string and integer for the key value. Okay so that's pretty cool so we can also
00:10:53.200 | merge different types to create more complex structures and what I want to do is return
00:11:00.160 | now to our previous example. So this is pretty good.
00:11:06.080 | So for this example do we really only want to allow integers and maybe in some cases we do
00:11:14.800 | but also it would be logical in this scenario to also allow floats. So to do that we can also
00:11:23.680 | use something called the union operator. Now I'm going to show you two versions of this I'm going
00:11:30.080 | to show you the Python 3.9 version and also the Python 3.10 version. So for the Python 3.9 version
00:11:40.880 | we need to go from typing import the union and then here we add our union
00:11:49.600 | and we say integer or float
00:11:54.720 | and that's all it really is. So if we add both of those we can now add 4.5 5.5 and we
00:12:10.240 | won't return any error. Now of course we'd also probably want it to
00:12:14.400 | be here as well and we could also specify that we expect it to return either of those.
00:12:22.240 | So that's how we would set up alternative integer or float data type annotations
00:12:32.320 | in Python 3.9. Now with Python 3.10 we can do this in what I think is a much cleaner
00:12:42.480 | way. So first we can remove this top import so we get rid of that and instead of having union here
00:12:53.280 | all we do is add a bar in between our data types and that is all we need to do for it.
00:13:02.960 | So we do that for each of these and that will give us our Python 3.10 syntax. Now let's
00:13:18.640 | just undo all of that and the final type that I want to show you or define operator
00:13:26.160 | is the optional operator. Now when we define a function with optional arguments
00:13:35.120 | we would use this optional function in order to allow us to actually do that.
00:13:42.960 | So this will allow us to say okay here I'm going to just make up another argument we're
00:13:49.600 | not going to use it but this will be optional string. Now I've imported optional here
00:14:02.880 | and now we're using it here so this will allow either non or a string in our code. So immediately
00:14:12.960 | you can see that this means that we don't have to specify anything here so that's great
00:14:19.840 | and if we do specify something we can say op okay no warning and we can switch that
00:14:31.280 | around for the non variable and we won't see any warning. Now as well if we didn't want to use the
00:14:39.040 | optional operator we could also just write this using a union and it actually produces the exact
00:14:44.560 | same type. So we could write string and non and this would work in exactly the same way
00:14:52.400 | there's no difference whatsoever. So that's everything that I wanted to cover for this
00:14:58.800 | introduction to type annotations in Python. I hope it's been useful and I will see you in the next one.