back to indexHow 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
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: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.