|
|
12:44 |
|
show
|
1:34 |
Welcome to Rock Solid Python with type hints.
So happy to have you here in the course.
This is going to be a really, really fun one and I think you're going to get a lot out of it.
Maybe you're not familiar with Python type hints and annotations and how that works.
Or maybe you are, but you're not sure that you're using them correctly, or you feel like you could be doing more with them.
Well, that's the kind of stuff we're going to talk about in this course.
And we'll learn that Python type hints or type annotations as they're sometimes called are not just for readability.
We have tools like editors that are driven by them.
We have linter like things such as my pie as well as others will discuss that can check the structure and correctness of your code almost like a compilation step.
We have frameworks built on them, you know, FastAPI comes to mind, which will dive into a couple of the frameworks and see examples there.
And you can even use type hints for runtime type verification with a pretty low overhead as we'll see.
Finally, we'll close out the course with a little bit of patterns and guidance.
What should you be doing?
How can you work with Python type hints to be the most efficient?
So Python type hints are awesome.
They make your code more readable, they make you way more productive because the editors can tell you a lot more about what you're doing.
you won't find yourself in the documentation or on Google nearly as much.
I hope that sounds great to you.
I'm gonna dive into all those things and way, way more.
Let's go.
|
|
show
|
2:41 |
Let's dive right into talking about Python type hints by starting with the language.
Here's the Wikipedia entry for Python itself.
Let's look at a few things in this page here.
First of all, if we zoom in, you can see that Python is dynamically typed and garbage collected.
It supports multiple programming paradigms, including procedural, object-oriented and functional, and is often described as a batteries included language because of its large standard library.
All this is true.
The underlying part is what's relevant for us here in this course.
It is dynamically typed, dynamically typed languages generally don't have any type information in the language whatsoever.
It's just like, well, however you use it, that's how you use it.
You probably heard if it walks like a duck talks like a duck quacks like a duck, it is a duck.
Well, that's what they're saying right here.
But if you looked at the first page, when we were zoomed out there's a little sidebar on typing.
Let's check that out.
Typing discipline says you can have duck typing, dynamic typing, strong typing, gradual typing, all of these interesting things.
So strong typing, that is what we're going to bring to Python with the ideas that we're talking about in this course with type hints and other things that we can apply to our code, either externally or directly in the code that we're writing.
Also, you'll see this is gradual, which is really awesome.
That means when you want Python type hints, you can use them, but you don't have to languages like C#, C++, Swift, Dart, they are not gradual, you have to use typing, the strong typing from the beginning.
Always if there's ever the smallest mismatch, I said it was an integer here and I didn't specify it there.
I said it was an integer here and it's a a string there, whack, the program won't compile, it won't run.
There's a million lines of code, one line is wrong, you're done.
Python is not at all like that.
It's gradual.
Moreover, the CPython runtime ignores type hints.
That probably needs to be in the little bracket seven up next to that where it tells you when it ignores it and when it doesn't because there are times like for example, with fast API where the runtime behavior is driven by the types.
So the execution itself is not entirely ignored, but at the CPython level, it is.
So we're going to go and add stronger typing gradually to CPython to other code that we write.
And sometime it'll be ignored.
And that's an advantage.
Other times, we won't allow it to be ignored.
In that case, maybe its advantage as well.
|
|
show
|
2:42 |
In the opening to this course, I gave you some idea about what we're going to cover.
But let's just dive really, really quickly into the specifics.
We're going to start by doing a comparison of many different static languages, as well as dynamic languages to Python, both the typed version and the untyped original prior to 3.5 version of Python.
And so you can really see how does code look in Swift, how does it look in C#, how does it look in TypeScript, all these different languages.
We're going to compare them to how Python works to give us an understanding of where is Python typing coming from, how does it look the same as these static, strongly typed languages, as well as how is it different, what makes that better and worse.
Then we're going to dive deep into all the aspects of Python types.
We're going to talk about some of the peps.
We're going to talk about the syntax, how you use it, when you use it, how you detect those errors.
So this is going to be the really the meat of the course.
When we focus on Python typing in the language, they were going to see some of the ramifications of that typing in the language, what are some of the frameworks that are better or require Python type hints to operate, and actually see some running examples.
You know, think things like FastAPI, pedantic, but others as well.
Then we'll talk about tooling, our editors are really important tools.
When choosing the right editor, you don't want to choose one that doesn't understand Python types.
But if your editor is smart enough to do that, you'll see that's really powerful.
But we're also talking about mypy, pyre.
We're talking about some of the things you can do for runtime type verification, that kind of stuff.
We'll talk about what I'm calling orthogonal typing.
You might hear this considered or referred to as structural typing as opposed to nominal typing.
what you might know from Python type ints, how can we avoid some of the pitfalls of traditional object oriented programming, and still take advantage of all the cool aspects of dynamic language might offer, but at the same time provide a little type safety and information there as well.
We do that with things like protocol.
And finally, we're going to close this out with some guidance, when should you add Python types?
How should you go about approaching a large code base that already exists but doesn't have very many types.
What are some of the patterns for good things and even what are some of the anti patterns with Python types.
This is what we're going to cover in this course.
I think it's going to be really really valuable to you and I'm excited to dig into them all with you.
|
|
show
|
1:11 |
As we get into the code samples and actual topics of this course, I want to just set some expectations.
The goal is not to cover 100% of Python's type information.
For example, metaclasses.
We don't care about metaclasses.
They can be used to work with types in Python.
We're not going to mess with that.
There's also some really specialized stuff in the typing peps and specifications for Python that we're not going to cover, variadic types and other interesting things like that that may be relevant, certainly would be covered in a computer science course around it.
However, we're not going to cover it in this course.
And that's not because I don't want to, you know, take the time to write that up or put together an example.
It's because I want to focus on what is important, what I think almost everyone is who's going to use Python type hints needs to know.
So we're going to focus on maybe 85% or so of what is actually specified in Python typing because that 85% is what you need almost all the time.
And once we finish this course, the few little edge cases that we didn't cover, you'll be very well equipped to go and research them for yourselves.
|
|
show
|
1:07 |
Guess what, you need Python to take this course.
In fact, to take advantage of all the things we're going to talk about, you're going to need a pretty modern version of Python for this course.
For example, some of the features are only available in Python 3.9.
And a couple of them are only available in 3.10.
So if you either don't have Python, or are not sure which version of Python you have, I put together a guide that we have for all of our courses over at training.talkpython.fm/ installing-python.
Check that out.
It's kind of a choose your own adventure for your operating system and then how to figure out which version of Python you have.
For this course, Python 3.10 is a real minimum, but I'd recommend go ahead and going for the latest.
Right now that's 3.11.
very, very soon that's going to be 3.12.
So, Python is very good at being backwards compatible.
But that said, you're going to need Python 3.10 or above in order to do all the code samples and run the example projects from this course.
|
|
show
|
1:22 |
Some of the code in this course, we just start from scratch and start writing it.
You could easily follow along, but much of it, we're going to start with existing code.
Our FastAPI example is a really cool already existing web app that uses a database and things along those lines.
We're not going to recreate that here.
This is not a course about FastAPI.
Similarly, in the very next chapter, when we talk about static versus dynamic languages, We have some TypeScript code, we have Swift, we have C#, those different languages, we're not going to go and write that stuff from scratch.
Why would we?
So make sure you get the existing code.
And right now it looks like an empty repository.
But I can promise that when we're done with this course, I'm going to be continuously adding to this over time.
And you'll have everything you need.
So just check the readme on the root of the repo.
Make sure you start this repo so that you have access to it.
And you'll consider forking it as well if you want to have your own snapshot in time of the repo.
But be sure to go and download the materials if you don't get get is not your thing and you don't care about that.
Click that green code button.
There's a drop down it says give me this whole thing as just a zip file and you can just download that run.
So get clone it or just download it as a zip but be sure to get this code because you'll need it to be working with what we're doing.
|
|
show
|
1:37 |
Finally, as we wrap up this introductory chapter, let me just tell you a little bit about myself.
Hi, I'm Michael, I'll be your instructor and author for this course.
I'm super excited to tell you all about everything we're going to cover here.
If you want to read my essays and just generally keep up with my writing and other things like that, check out mkennedy.codes.
That's my blog and website.
I'm the creator and host of the Talk Python to Me podcast.
Many of the things we talked about in this course are actually covered in depth with the original creators over on talk Python.
For example, when we talk about Pydantic, I've had multiple episodes with Samuel Colvin about the creation and evolution of Pydantic.
So consider checking out talk Python to me, and maybe searching through the episodes for some relevant things if you want to dive way, way deeper with the people who create this type of stuff.
Python Bytes, another podcast.
This one's like a weekly newsletter.
Do this with Brian Okken.
So check that out as well.
And of course, I'm the founder and one of the principal authors here at talk Python training.
Many of the ideas that are in this course are actually in practice behind the scenes delivering the content to you as well.
So that's kind of fun.
I'm a Python software foundation fellow, really, really great honor.
And finally, if you want to carry on the conversation, find me online, probably the best place to do that would be on Mastodon.
I'm over on Fosstodon, so @mkennedy@fosstodon.org.
Great to meet you.
Looking forward to spending some time together with you throughout this course.
|
|
|
0:30 |
|
|
42:24 |
|
show
|
6:36 |
When we think about different programming languages and especially their type systems, the dynamically typed ones, strictly or strongly typed ones, and maybe things that live somewhere in the middle, like say Python, you can think of them as living on a spectrum.
So let's put some of the common languages that you might know about and that we're going to look at in just a moment on the spectrum to see where they lie on this dynamic versus strongly typed scale.
Let's start with the strongly typed ones.
You might think C++.
Now there's a very strongly typed language.
Not sure if you have experience with it.
Many people do.
You always have to say the type.
If you want to have a number, you have to say int x, or it can be an unsigned int x, or it can be a floating point or a more precise floating point in the form of a double.
You have a class, you have to say the type.
You're always expressing what the type is.
Those are a pointer to an integer or a pointer to a pointer of integer types.
Now, C++ is not all the way to the right here on this spectrum, as you can see, but it is certainly towards the strongly typed side, which is the right hand side of this picture.
On the far other side, we have languages like JavaScript.
JavaScript barely has a type system at all as a prototypical inheritance thing.
Yes, so there are kind of ways to have classes, but for the most part, you don't really have types over there.
You have kind of dictionaries of things.
And if you go and ask them, what are you?
They will say, I'm an object.
They won't say, I'm a car derived from a vehicle class.
No, they just say object.
Okay, so really super untyped in the JavaScript world.
We could throw Ruby over there as well.
Ruby's a little more typed, I believe.
I don't have a ton of experience, but it's certainly in the dynamic side of the scale.
Back on the strongly typed side, I would put C#, Microsoft's .NET language that's kind of a derivative of C and Java over here on this, and actually more strongly typed than C++.
For example, in C++, you can have a pointer, but then assign it to an integer because, hey, pointers are numbers, and so that's just that.
Or you can say if the pointer, And if the pointer is not falsy, it will become, you know, an evaluate to true.
Whereas in C#, things like if statements and while statements can only be true Boolean expressions, you can't assign a pointer to an integer, it would say these are not the same things.
In fact, there's some pretty interesting stuff about some things being values and some things being references.
But let's just say a little bit more strongly typed than C++.
Swift, Apple's programming language to replace Objective-C, is even more strongly typed than C#.
Because in Swift, you have the ability to say, this thing is a pointer, but this particular one pointer, it can have a null value.
This one is not nullable.
You can't even assign null or nil in their syntax, their language over there.
So there's some really strong typing over there, especially around nullability and whether or not you're allowed to set something to none from Python, null in most languages, nil in Swift.
Interestingly, Swift takes a lot of hints from how Python works, but is also extremely typed in this way.
So where does Python fall on this spectrum?
Well it actually falls way on the left by default.
Traditional, original, OG Python is right there with Ruby, just above JavaScript, right?
For example, if in Python I had something that was actually an integer and was actually a list and I tried to add them, I'll get an exception that says type integer and type list cannot be added together, unlike JavaScript, which will give you some weird, nonsensical answer and it'll just try to make them work anyway.
So in that sense, Python is more strongly typed.
The things know what they are and when they interact with each other, they use their type information to know whether or not that's allowed.
But from a programming language perspective, you look at both of them and they have no type information really going on.
So Python's basically over there hanging out with his friend JavaScript and Ruby.
The original one.
There are trends, there are languages that try to take some of the things on the left and make them a little bit stricter for building large scale applications for using tools that understand type information that can help you say, auto complete your, your code statements without actually having to go to the docs to figure out what the heck does this thing return.
One of them would be TypeScript.
So TypeScript is a strongly typed programming language that resembles more C#, less JavaScript, but is still also JavaScript.
And in some ways, like, not really a superset, but something that transpiles when you compile it becomes JavaScript, a very different looking thing than what you write.
But this has type information and is pretty strict.
For example, if the types mismatch, the thing won't compile, you don't get the JavaScript file, you can't run it.
So unlike Python, where it ignores the type information at runtime, this actually matters.
If you get the types wrong in TypeScript, the thing won't work, it won't compile, it doesn't just give you a warning or something along those lines.
We can also do the same thing with Python without transpiling it to something else, but to add this type information to the language that is previously the dynamic type dynamically typed version.
So back in Python 3.5, a bunch of features were added under this general category of type hints to allow us to put typing information right in the language.
And that brings Python way closer to TypeScript just to the left, because if you get the types wrong, it still runs.
It's not like TypeScript, where if you get the type wrong, or any other strongly typed ones, it doesn't work.
But it's in the same general category, we take this dynamic language, we add type information to it, it becomes much more strongly typed, the tools can know more about your code.
And you can also run linters that understand those types, you get a little bit better experience with this language.
So we saw it's optional and gradual, which is pretty awesome.
So moving Python from this super dynamic version towards but not all the way to the strongly typed languages.
Well, that's what this course is about.
|
|
show
|
3:26 |
Enough big ideas, let's start writing some code.
Now I've already git cloned the repo that we're going to be working with.
But let's go ahead and talk through setting this up for the very first time.
So here's the repo that we're working with.
Notice that there is no virtual environment, but we've got things like our code that we're going to write over here.
Now I'm going to open this up in PyCharm.
But the same thing would apply in VS Code.
you're doing that as well, it's good practice to have a virtual environment.
Now in the beginning, this is not going to really matter whether we have a virtual environment, because the first few steps don't require external dependencies.
But as we get further on, as we get to runtime type verification, or the different frameworks, then we're going to need to install them.
We want to do that in an isolated local way.
So let's create a virtual environment to get us started.
We'll open up a terminal.
If you're on Windows, make sure you're using the new Windows terminal, not because it's required but because it's better.
And let's create a virtual environment.
Now I have a shortcut venv that I always use.
So let me show you what that is.
So it's alias to several commands, we're going to run not just Python dash m venv, venv, but we're going to do --prompt is dot and that will make the name of the virtual environment be type hence course.
And then we're going to activate it on macOS and Linux that's venv/bin/activate with the dot to apply to the shell on Windows, drop the dot and it's VNV scripts activate dot bat.
And then finally, we're going to make sure that we don't have an outdated pip.
All right, so we'll run that.
And you can see we now have this virtual environment here.
We ask which Python three, and it's the one on my desktop here that we just created.
We want to open this now in PyCharm.
Again, if you want to do this in VS Code, that's fine.
I'm going to use PyCharm.
Now, PyCharm sometimes is awesome, finds that virtual environment.
Other times, no, not at all.
So notice down here it says 3.11.
That means the system one, not what we want.
So we'll add a new interpreter.
It tries to make a new one where we put it, but it says it's already, I don't know what's going on here, PyCharm.
But anyway, we'll switch it over to existing.
if you can browse to it, if it doesn't auto detect it.
But here you can see we're now running the right Python.
And we've got all of our code over here that we're going to be working with.
So there you have it.
You can tell that it is already set up.
You could try to run one of these files just to make sure.
Can't run the Swift, run one of the Python files.
Just to make sure that it's running correctly.
And also while we're here, just to save us a little bit of trouble, you can see that this is golden, this virtual environment, but that means it's not committed into Git and it's ignored explicitly in Git, but it's still possibly when we do autocomplete and things, PyCharm might think it's part of our project.
So we can right click, say mark directory as excluded.
Now it's both golden and orange, reddish with a orangish golden background, whatever color they call that.
That means it's gonna be excluded, which is gonna be helpful for when we're doing all the stuff we're working with later show how awesome our autocomplete becomes when type hints are enabled.
There we have our Python all set up and ready to go.
|
|
show
|
5:35 |
In addition to being really passionate about programming in Python, I'm also really into motorcycle writing and So we're gonna use Motorcycles as the basis for a lot of our demos in this course when I talk about our adventure motorcycle and other kinds of motorcycles This is great for typing.
This is great for inheritance You know motorcycles can be vehicles, but can the cars also fit into that object hierarchy?
We'll see kind of yes kind of no So we're going to go and write our first demo, this is going to be just plain old school untyped Python, you know, this kind you might have written before taking this course or certainly before Python three, five came along.
So here in PyCharm, we now have a pi no types.
So this is the old school original style of Python.
And what we're going to do is we're just going to write some code here, play with some ideas.
And then, as we go through this chapter, we're going to add types to it, compare it to TypeScript, C#, Swift, and those kinds of things.
So let's get started.
We have a class motorcycle.
And this class is going to have an initializer a constructor.
We need to pass some information to it because not all motorcycles are the same.
Some have some are off road, some are on road, some are both have different size engines.
So we'll start with a model, a style, engine size in cubic centimeters, and whether or not it can go off-road.
So first of all, let's just look at this here.
What do these things mean?
So model, that's probably clear.
We should be safe to go ahead and add that to our code here, to our class.
model.
This is like Yamaha Tenere KX250 whatever right KX maybe would be the right thing then 250 would be the engine size right.
Style though is that a string is that an enumeration.
We don't know.
This is just a dynamic language so we're just going to hang on to it there and that's fun.
Engine size probably probably an integer but it could it be a float.
Are Are there partial CCs measured in these things?
I'm not sure that there are, but maybe there are.
We don't know.
It's probably some kind of number thing.
Let's add that.
Off-road, yes, no, true, false.
I don't know.
We'll just add that as well.
All right, so here's our motorcycle, and it's something that we can create, right?
We could go and say MC equals motorcycle, and PyCharm will say, okay, it takes a model like a Tenere, the style be Adventure, let's say.
size of 700.
And true.
Now, now that we're using it, we get a lot more information about what the types we might expect here, assuming this usage is correct.
Okay, so string string number, Boolean integer.
Right, so but just looking at the code up here, you don't know that there's nothing at all about what's up there other than your expectations, hopefully some good naming, but you know, no guarantees there.
So we're going to go with this and fill it out a little bit more.
Let's ask the question whether it can jump.
Is this a motorcycle that can jump?
Yes or no.
Street bikes, while technically you might be able to make them jump is probably a bad idea.
So let's suppose a motorcycle jumps if it's off road.
So we can create a property here just can jump.
And let's just say it can jump if and only if offroad is true.
So we'll say self dot offroad.
If we know this is actually a Boolean, this is fine.
It's not we might want to, you know, wrap that into a Boolean condition or check offroad equal equal Yes, we don't know really what we need to do yet, because we're lacking the type information to be very clear about that.
But this is a decent start.
We're going to be printing out a lot of motorcycles.
So let's give it a stir representation.
We'll just say use a couple of strings model like in this case, 10 array 700 type adventure off road, I don't know, can jump true or false.
Okay, so that's going to be this piece.
And then the last thing let's because we want to work with different aspects, you can see here's a standard function.
Here's a initializer type function.
Here's a property, let's get one more type of functions that we might work with here and we'll make a class method and we'll call this createAdventure.
It's going to be a shortcut from what we normally pass up here.
So let's go along and copy that.
The style is always going to be adventure, so we don't need to specify that.
We'll just say return motorcycle of model and then what comes next, the style, so adventure.
I don't like that I have to keep typing this.
What if I misspell it?
Is this always uppercase?
lowercase, we don't know that's a problem.
Engine size and off road.
Yes or no.
Actually, I would say this is always true.
So we're going to pass just those two things in.
So it's a nice simple way to create a simpler one.
And it's a static method or a class method, which is like a static method effectively.
So this is our motorcycle class here.
Right?
Again, we already explored like we don't know really how it's supposed to be used, we might have a way in mind as we're creating it what these types are supposed to be, but there's not information that anyone consuming it would know about.
But this is standard, untyped, traditional, OG, original Python.
|
|
show
|
2:21 |
We have our motorcycle class up here, but let's go ahead and use it.
Let's come down here and say we're going to create a function called create bikes or motorcycles, like so.
And it's going to have bikes like this, and then somewhere it'll return the bikes.
And we're going to leverage our create adventure thing.
And we'll create a bunch of different kinds.
We'll create a Himalayan 410, a Tenere 700, a KTM 790, and a Norden 901.
All of these are different types of adventure motorcycles.
All right, so we've got them, and then let's go ahead and use them down here.
We'll use the standard dunder, if dunder name is dunder main, to say we're running this code, so let's actually do some action instead of just import it.
And I have a shortcut here, a live template in PyCharm that I created called name, that if I hit tab, it writes this, because who wants to type that all the time.
So we'll just say bikes equals create bikes and then for B and bikes, just print out B.
Great, now if we wanna run this in PyCharm, you can see it will run the current file, but kinda wanna force it to run just this one, even if I have the C# file open.
So let's go and say run, right click on this and say run.
And notice you can see the command is running our virtual environment Python.
again, over here, that file, as you would expect, here's some bikes, and you can see it did exactly as we would like, this is the stir method for the class, we're just printing out the bike there.
You can see as we start to use it, yes, there's some inference about where this might actually be useful, where this, what kind of information is required, like, okay, that looks like it really is an integer, not a string or anything like that.
So we're getting more information about how the types fit together, but it's just what we gleam from the code as we go through it.
One final thing is I called this bikes and that bikes and it's not happy saying.
This thing is shadowing this global one.
So let's just rename that real quick to motor cycles.
Check it still runs.
Awesome.
|
|
show
|
3:03 |
Before we move on from our untyped example, let's talk about duck typing.
We saw that in Wikipedia, Python has duck typing, but also strong typing.
Typically those don't go together, although we'll see there's some interesting ways in the orthogonal typing section that we're going to talk about near the end of the course that they kind of do.
But in general, we have two types of typing.
We have strong typing or strict, and then we have duck typing, which is just kind of how you use it.
like a duck quacks like a duck.
It is a duck.
Let's see how that works in our example here.
So right now we've got our class and it takes a model and it takes a style and an engine size but what are these right?
This is an integer is this a float is that a Boolean is that a yes no, we don't know.
Partial like it's somewhat off road.
It's not a great off road bike but it does technically support it.
So we don't have a lot of information Let's go ahead and just use our bike here for a minute.
We're going to print out a little bit of extra information.
We'll say the B.model can, and we're going to say can or cannot jump, depending on how, whether or not it can jump.
So we'll come in here and we'll say, in this case, just nothing if it can jump.
So we'll say if B.canJump.
jump, else we'll put here not and jump.
Let's go ahead and run this real quick, see what we get.
You can see the Tenere can jump with extra spacing, always fun there.
All right, the Tenere can jump, somewhat.
Over here we were saying that whatever a motorcycle is, it has a model and it can jump.
the can jump is truthy, not yes or no, because those are both pass as true strings, but it's a Boolean like thing.
So in the type specification, the duck type means a string model and a truthy can jump field property, it doesn't really know what our cycles are also spelled right.
we go.
So this is the duck typing, right?
If what do you really need?
Does it technically need to be a motorcycle to run this code?
When it comes back from crate bikes?
No, it just needs a list of things or technically iterable of things doesn't have to be a list could be a tuple.
But the things in there have to have a mo a model in a can jump.
So when we say you have duck typing, it's just like, well, that's how you use it.
And it then it must be one of those things, right?
If I can use it like a motorcycle, it must be a motorcycle, even if the type information says it's not really.
So this is an important idea.
This is how Python, the only way Python worked before, with 3.5, before 3.5, and when they added the typing, explicit type hints to the language.
|
|
show
|
4:40 |
Let's compare this motorcycle application, if you can call it that, that we wrote in Python without types to some of the other languages that we saw on the spectrum.
So the first one on our list over here is TypeScript.
So let's just start from the top and go through TypeScript.
So in Python, we have a class Motorcycle.
In TypeScript, we have a class Motorcycle.
Remember, this is not JavaScript.
This is TypeScript.
The JavaScript is quite intense.
But luckily, we don't have to write that.
So we have a model, a style, engine size, and off road.
Model style, engine size, off road.
By charm kind of added them in reverse the way we were doing that there.
Over in TypeScript, you can see the model is a string, the engine size is a number and the style we've gone and added something more explicit enumeration.
Yeah, we could have done that in Python as well.
But when it's really untyped, it's not very common to do that.
We'll move to that when we get our typed version potentially.
We got different types of motorcycles, one of them being adventure, that we can use and it's always going to be consistently the same.
So that's pretty cool.
And then we have a constructor just like the initializer there.
We have a model, string, style, motorcycle type, engine number, and off-road boolean.
But otherwise, this versus self, it's the same thing.
a can jump return this off road, create an adventure, just like before, pass in the model and the engine size.
Otherwise we're using adventure and off road right but you can see this type information here if I was to pass for offer type yes, come back here you immediately it says string is not a Boolean.
You've done it wrong.
There you go.
As opposed to...
Yes.
Okay.
I mean, I guess they can still jump.
But if I said no, I guess they can still jump.
That's weird.
Right?
There's no help over here.
Right?
It's untyped.
Also, let's scooch this over a little.
down here and create some motorcycles.
So it looks really, really similar.
I would think for you at the very top, I put the steps that you need to actually compile this or get it to go.
So you have to have node installed to run this one.
And it says CD code, chapter two.
Here we go.
And in this we have our package dot JSON.
So then we can just try to run this it'll say, ""Hold on, hold on, hold on.
In order to do this, you have to have the TypeScript compiler, the TSC, so I'll let Node do that.
npm install should probably do it, actually.
And we'll just double check TypeScript.
There we go.
npm install TypeScript.
That's all good, and now we should have our Node packages up here, as we do.
Now we can run mpx tsc motorcycle.ts.
No output.
I guess that that's good.
But if you go up here, notice a little chevron appears and we get the red pill of TypeScript, if you will.
So you saw what we wrote.
But look at this bad boy.
Look how you define enumerations in pure JavaScript or a class, which is a function and the whole evaluation is the constructor.
right, you can see the prototypical inheritance stuff going on, there's the prototype, for example.
Alright, so that's kind of funky there.
But you know, it's okay, we don't have to write it.
That's the compilation step.
And now we have this, what does it say to run it, we just say node and the JavaScript file that was created there, some bikes.
And And look, it's exactly the same other than capital T versus lowercase t.
So there's the difference between untyped Python, right, this version, like this.
And we got our TypeScript like this.
The TypeScript is pretty clean.
But as we saw, the JavaScript is super gnarly because they're wrapping runtime behavior that models what we created in TypeScript into a language that's super, super not built for that.
But you don't have to look at it, I guess it's okay.
|
|
show
|
5:01 |
Next up in the list here is C# and .NET used to be just a Windows thing, but now it's a cross platform thing with .NET cores.
That's what we're using.
So we can install it here on the Mac or whatever it is you're running.
There's a preview for eight, but we don't want that we want to run the stable one.
So kind of make sure you go over here and get the right version.
And then we can install that real quick.
All right, that's good.
Let's open up a new shell and type dotnet.
See what happens.
Awesome.
So it says what we got to do is run .NET new console app called MC on this version.
There's a whole bunch of stuff that happens.
I don't want to mix it into our source code here.
So let's just go to the desktop.
Run that doing all the things.
You can see it's got the various CS prods and the program.
We're going to copy this over here.
Let's just give it the name program like that.
It says, ""Okay.
Then we're going to go into the directory and call this right here, which is this one.
We'll just say .NET run.
Hey, look at that.
First time it's kind of slow because that's a compile.
Then it should be a tiny bit faster.
That's how you run it just so you can play with it yourself.
Let's look at the code again in enumeration, sport, naked, terrain, adventure, and so on.
We've got our public class.
So in C#, you have not just the type information, but you have visibility.
This one, motorcycle, is available to be exported out of this application.
Whoever wants to use this library, they can use motorcycle.
It could be internal.
It could be private.
But it's public.
Doesn't really matter.
No one's using it, but that's how it works.
So you've got the types.
This time the types appear in front, not behind.
That's less like Python, more like C, as well as the type modifiers.
But here we've got the constructor.
Same thing comes in.
These are all getters and setters like our canJump.
These are properties, not actual variables.
So capital one there, a little bit funky instead of just being the type, which otherwise you would put like that.
Can jump is a little lambda expression over to just whether or not it's off road.
Creative Venture, right, you can see they're pretty, pretty similar again.
Over here we also have everything lives in a namespace explicitly not just the module file name and within a class right even down here the public static void is part of a class.
So that's kind of interesting, right.
Right, so this is the C# version.
Decent, but look at how many more words appear on the screen.
Let's put it side by side again real quick with our Python one here.
If I can get it to go to the side, there we go.
So let's just look at some of these sections.
Here's some of the drawbacks of type information.
Right, I've been talking about how it's good.
If it's good, why don't we just want it everywhere?
Right, what was wrong with Python?
Why didn't they make the right choice?
Well, we'll see.
So for example, look at all of that stuff there.
Public string model curly get semicolon private set semicolon curly.
Ooh, and then down here, you've got like your model and then your model is equal to your model.
And this, this is the same thing, right?
There's the model, there's that.
I know it's not a property, but I put that aside, right?
'Cause we're not using anything interesting about the property.
Look at how much cleaner and simpler this is.
Then all of that information is all captured right there.
So that's pretty cool.
This property can jump.
You know, it's kind of on par with our property, maybe even a little cleaner, but I don't know, it's also a little funky.
Create Adventure.
Let's go down there and look at that.
Class method, Create Adventure.
I'm gonna have to scroll for this one.
Public, Static, Motorcycle.
Whew, Create Adventure.
String model, string engine size.
And then new motorcycle.
Okay, so the internals are pretty similar.
All right, once we get down to actually using it though, like our create bikes, pretty straightforward, other than you've got a list of motorcycle is what this method returns.
Interesting, you can see that the type information, while helpful, is definitely cluttering up the readability and the understand, the ability to have just concise readable code.
And we saw right in the open in the Wikipedia, Python is all about trying to make things as readable, easy and straightforward as possible to take the code in as a developer.
I think here's an interesting contrast that we can see here.
|
|
show
|
4:04 |
The last language that we're going to look at is Swift.
Now, Swift, unlike .NET, does require you to have a Mac to run it, but you know what?
You already know what all this output looks like.
It looks like this, plus or minus, true being uppercase or lowercase.
You don't really need to run this.
Just let's look at it.
So Swift, I said, I put that even farther to the right of C++ and farther to the right of C#.
That wasn't just because that's where it fit on the screen.
That's because I think it is even more intense on the type information.
So over here, there's a couple things to notice again, class motorcycle.
We say let model be a string.
So colon string.
It turns out this is basically the same syntax as Python.
I told you Swift is very Python inspired in lots of ways like notice no semicolons.
There's none of that business because turns out you don't need it.
So why have them?
Self not this again like Python, but over here we specify the type again an enumeration a motorcycle type I'm not gonna get tons of autocomplete.
I get some actually because I installed the Swift Intelligence plug-in for pyjarm, but whatever so we've got our engine and then our off-road off-road is different Well, it's somewhat different.
It's expressed differently than say that for example the C# one says it's not just a boolean, but it's a Bool, right?
If it were like this, it means it can be true or false.
Right, but when you put the question mark there, it's a nullable Boolean, right?
And bool, is it there?
I don't know.
If it's there, it's a bool.
But if it's not, then it's going to be nil in this thing, right?
In Python, that would be equivalent to none.
So this is one of those more intense levels where you have to explicitly say Whether or not it can can be not there right it can be set to none or nil Okay, so we go through here again here we're passing in a strongly type one that can go into the that can go into this because Boolean also can always be a bull right and Here though when we talk about Is this a boolean the off-road here?
We say can't jump we have to say you know explicitly cast this or explicitly pull the value out, we should probably do a test and say if it's no, we're going to return false.
Otherwise, we're going to explicitly pull this out, but we're kind of shortcutting it here.
And after that, everything looks pretty similar.
You know, you've got the return type on the end.
That takes a while to get used to, but I'm a fan of that these days.
We've got our description, which is like the string, the dunder stir.
And then the use cases again, look really, really similar like this.
Boy, that almost looks like Python, doesn't it?
Looks a lot like Python because Swift took a lot of its inspiration from Python itself.
And it says in order to run this, just so you can see we go over and we run Swift C against that in this particular folder.
There it is, we run Swift C.
And you'll see that it'll take a while because it's compiling.
There we go.
If we go back up here, there's a MC executable.
So we'll just say slash MC.
And there it is.
There's our native Swift application we've created that, you know, does exactly the same thing true is lowercase in this world, but pretty interesting.
If we switch back to our Python one, still more information over, you got to sort of take it in a big bunch over here compared to this, but the type information is there.
The symbols are a little bit cleaner than say the C# one as well.
Not so many semicolons and curly braces between these two, right?
A little bit simpler, again, like Python.
All right, so those are all the different comparisons about what different static and dynamic languages might look like when creating something similar in Python.
|
|
show
|
7:38 |
It's good that we saw that Swift one last because it's going to inspire us to create our Python, modern Python, strongly typed, somewhat strongly typed because it still run even if you get it wrong, right?
That's the gradual progressive stuff.
We're going to create a typed version of the Python application here.
And what we're going to do is kind of gloss over, just give you a quick high-level view of what stuff looks like.
And then the next chapter is all about diving into the details and the nuances and all the language features.
So we're going to skim on this one just to give you a sense of like, well, how does this come out?
How does this change in the Python world?
So there are no types, we have types, run it.
Same output should always be getting the same output.
Alright, for this one, we're going to edit the types right now there are no types.
And like I said, Swift took a lot of inspiration from Python.
So we have the colon type, as opposed to the traditional C wave where you have type, then the definition, right, this will be a string model, or is this is a model, which is a string, and it gives you the more important thing, what is it before it tells you details about it.
So over here in Python, we're going to say that this is a string, the type name is different.
But other than that, it's the same as Swift, right?
It's not capital S string, just str.
Style, just leave that alone for a minute.
Engine size, this is an int, so we say colon int.
And this is a boolean, so we'll say colon bool.
And interestingly, like Swift, this one cannot be set to none.
Bool, as opposed to bool question mark, which is not a Python thing, but the same idea.
In Python types, this can't be none, it has to be true or false.
That's it.
We saw all the other ones.
Let's go grab from this one here.
We saw all the other ones had this cool enum that gave us more structure.
And I'm not sure that made sense when we had no types, but now it certainly does.
So in Python, we can say there's a class of this that derives from enum.
Let's import, no, won't let us, but there you go.
Now it imports it.
.enum of that type.
And what are we going to have?
We're going to have a sport.
There we go.
Fill them all out.
The only one we're really using is adventure.
And we can do a little better, say, not this is just an enum, like a number, but this is a string enumeration.
So all the values in here are strings.
And once we have this type up there, we can now say that our style has to be one of these.
So when we say self.style, you can see our list of adventure, trail, naked sport, motocross, etc.
And there's no other options.
That's it.
Pretty cool.
We also do this to the parameters.
So this is a string.
This is a motorcycle type.
This is an int.
And that's a bool.
Okay, switching back over to the Swift one here.
This is kind of the information we got there.
And this is what the new one looks like.
And a little more verbose, but still quite good.
It does tell us when we say things like engine size that these are integers, right?
See all the integer information coming over there.
Okay, so that's the constructor.
Now our can jump, well, it's returning a bool, but this is already a bool.
So it turns out this call there, while maybe interesting and useful, is not really required.
But we somehow want to know when you go and work with the canJump property.
So we could say canJump down here, what actual value is that here?
If we go to this, we can say, as we saw in Swift, this is a bool, canJump, true or false.
And back down here, I guess bools.
It's showing us the bools are really in Python.
Okay, but expressing that the return value, which tells you what type the property is, is cool.
This, we could go ahead and say returns a string.
No one really calls it explicitly.
Just let the runtime call it, but let's make that real clear as well.
This is going to be a model, which is a string and an int.
And here, and notice this is, there's a problem here.
You just typed the word adventure, didn't you?
That's not choosing from the list.
Where's the refactoring support and those kinds of things?
Where's the auto complete?
Doesn't tell me what I can do.
It tells me what strings are.
We can say motorcycle type dot adventure.
That's what was expected there.
Perfect.
And then down here, these are all the same.
Looks like it's pretty much ready to go.
Let's run it.
get exactly the same output and here's our are true from this can jump there.
I'll take that away for now.
Anything we're missing, we could be a little more explicit on the create bikes.
This is going to return a list of motorcycle.
So we'll say list bracket motorcycle.
Right when you have a collection that then contains things you say the collection or container bracket the type that goes in there.
As opposed to I said type.
You see now there's an error down here, it says, No, no, no, we are getting motorcycles in our list, not motorcycle types.
There we go, that goes back, we can even be really explicit here that this is a list of motorcycle, although often the type inference from from the return value to there, even without this, you'll see.
So it knows it's a list.
And if we grab an element, it knows it's a motorcycle.
But sometimes it's helpful to go ahead and just be explicit there, which we can do like that.
Finally, if for some reason, like B didn't know that it was a motorcycle, PyCharm is pretty smart here.
And it does, but you could pre declare, you could say B colon motorcycle, because it's a thing coming out of a list of motorcycles.
So one of them is a motorcycle, you could pre declare it like that, see the still runs like this.
And now for sure when I say B dot, it's going to be a motorcycle.
Maybe this thing that came up here was not clear.
So if we said this is a list of any, for example, out of typing, we'll talk about that later.
I go down here and type B dot, it's like I have no idea what this is.
Are you crazy?
But if we pre declare it above outside the list, now it's a motorcycle.
Okay, so that's when you would use it.
But we already have enough type information elsewhere that this is not really needed.
Here we have it.
This is our typed version of our Python code.
It's pretty comparable to the Swift.
To me it still feels even a little bit cleaner and it's certainly cleaner than the C# code which is got, you know, this is not that different.
But it's somewhat different.
It's somewhat more verbose over here with symbols.
So there it is.
This is the standard typing stuff that we're doing in Python.
We're going to dig way into a lot of the details here when you use them and so on in the next
|
|
|
1:21:41 |
|
show
|
0:33 |
This is the chapter that we dive into the Python syntax for adding typing to the language.
We're going to look at how we work with variables, functions, classes, things like constants that don't really exist in Python, but they do in the typing space.
We're going to look at nullability, all of those things we've talked a little bit about before when we introduced typing and static versus dynamic languages and so on.
We're going to dive deep into that syntax here.
So, get ready to get your Python typing on.
|
|
show
|
1:14 |
Let's go way, way back to 2015.
Here you can see the PEP for Python type hints.
Sometimes they're called Python type annotations, but you can see officially they were introduced as type hints, and that's PEP 484.
This was originally submitted by Guido van Rossum, as well as a couple of other folks here, supported by Mark Shannon, and it was introduced in Python 3.5.
So that's when I was...
that's how I'm...
how come I'm talking about, you know, if you're using 3.5 or above, then these things apply to you.
All right, really quickly, we just scroll down to the motivation.
So this PEP is a little more comprehensive.
And it aims to add standard syntax for typing annotations.
Now using the alternate phrasing here, opening up Python to easier static analysis and refactoring.
Interestingly, they already call out potential runtime type checking, we will talk about that later in the class.
And some of the the time, possible code generation utilizing this type information.
So this is the definitive source of where Python type hints come from and the motivation you can look through.
It's quite a long PEP as you can see from the scroll bar there.
|
|
show
|
3:23 |
Well, we're on to another chapter.
So now we got a new section in our project in our code here, we're on this 03 typing in Python, you can see that we've got our virtual environment selected, although until next chapter, it won't really matter.
But that is set up.
And we're going to explore this in six big sections.
And the first section we're going to talk about is variables.
I don't know file here.
You want variables.
And the first thing we're going to talk about in this section is how to work with creating and typing a variable and what the implications of that are most importantly.
Now let's go ahead and set this to run straight away.
Nothing's going to happen yet, but just so when we press run, it's running the right file.
Now we're going to see that there's two ways you can create variables in Python and variables in Python are no nonsense sort of thing.
So we can say x equals seven.
Done.
We've created a variable.
does have a type integer right now, but it could be anything.
We haven't restricted it in any way.
And that's about as simple as it can get in a programming language, isn't it?
So we could come down here and we could later on decide that this is gonna be something like happy numbers, right?
Talks about numbers, but it sure isn't a number.
Now you might see a warning on the screen here.
It says this is redeclared before it's used.
This is just the linter in PyCharm trying to be helpful saying you basically created a variable twice with doing nothing.
So we're just gonna print it out just to make that error go away.
This is not a typing error.
This is just like, hey, you didn't use a variable.
Print it out one more time.
We've got X is seven.
Now it's happy numbers.
Excellent.
We could go and explicitly define a variable though, as we have a little bit in that quick fly through on the typing, we could say we want a variable and its colon type is an integer and that is equal to 10.
So just like our X, we have a variable Y, it has a integer value, but unlike the X down here, if we try to say this is sad numbers, PyCharm is going to say, whoa, whoa, whoa, there is a problem here.
Look at this, we wanted an integer because that's the only thing you should be able to assign to Y, and yet here we have a string and definitely, definitely strings and integers don't mix.
So if I just tried to say, do not mix with numbers, plus seven, and I try to run that you can see crash cannot concatenate string and integers.
It's true, they don't mix.
So PyCharm is right to give us this warning.
There's this other warning about it not being used, but that's the same as we saw before.
So let's just do that real quick.
But here's the interesting thing.
Remember, Python types are optional and gradual.
So if we run this, you can see happy numbers, sad numbers, even though there's this warning.
Sure enough, it still runs just fine, because the typing does not affect the runtime behavior yet, at least in this context, it doesn't.
It just helps you write better code, it helps you understand this code better.
So in this case, it says, Look, you're trying to use a string where an integer was expected.
Don't do that.
It's not going to work out.
Well, it's not technically a syntax or a runtime error, but it probably will lead to one if you keep assuming it's a string when it's not.
|
|
show
|
3:05 |
Continuing on, we're now going to do a quick survey of the core types in Python.
You've already seen that we can have integers like 27.
We also can be more explicit about the type of numbers we can work with.
We could say we have a floating point, something approximating the square root of 2.
We could even say we want a complex number, where we specify the imaginary and real parts.
So for the imaginary part we could have 0, and here we could have -v, for example.
So -sqrt, let's print some of these out, u, v, c.
They use j, come on, we all know it's supposed to be i.
But there's our numbers, right?
We got those back.
We can have some text, like this.
This could be some text.
We could also have bytes and bytes and strings used to be real real similar.
So we could say this is equal to B of bytes text.
So the raw here, there's a lot of ways to express different strings as raw strings and other types of strings.
But bytes is a separate thing here.
Just, you know, the bytes representing those ASCII codes.
We have a bunch of container types.
we have a LST, which might be the list one, one, two, three, five, eight, just randomly picking numbers out of the air.
Or it could even be a set, which would be a set of or you could express it like this, these same numbers, which will remove the duplication and be one, two, three, five, five and eight.
Let's go and print out all these new things.
Here you go and see the set is remove the duplicates here you can have the bytes as B.
Now, none of these have explicit types.
So let's go and add them.
And if we get them wrong, PyCharm should let us know like, ""Oh, we expected an int, but we got a floating point number."" Okay.
And here, it's a little more obvious because it says the type right there, isn't it?
And this is going to be a string or as Python represents it, just str.
And this will be a bytes.
So be a list.
We'll talk more about what goes in the list.
This This would be a set, right?
These are the alternate ways the class types for these things.
One more we could add in here, maybe it'd be fun would be truth, which could be true or false.
And that's going to be a Boolean.
Super, super important.
And where's it go in the list right there.
Truth.
So those are our different types.
And you can see, if we were to try to assign one to the other, lst equals s, we know that one of these variables of type set the others list, they don't go together because there's no type hierarchy between those two types there.
All right, so these are the core types in Python.
There are many, many more types in the standard library and nearly unbounded many if you start looking into the external packages on PyPI.
But these are the ones you're going to be working with all the time.
|
|
show
|
5:22 |
Now let's look at nullability.
Even though the word doesn't appear in Python, I think null is kind of the standard idea of a reference thing, reference variable pointing at nothing across the languages, right?
Swift has nil, Python has none, most languages have null, but pointer at nothing.
Remember that when we said that spectrum of languages, I put things like C# and Swift to the more strict side, even then C++ because partially because of its their nullability or their ability to check and make sure that certain data types can never be set to none, even if that would be potentially a meaningful value in the implementation like zero might be the value for null for a pointer, but you still can't assign it to an integer if that integer can't be nullable.
In Python, in its type system is exactly like this.
It's super strict.
It's more strict than many of the languages that have concepts of reference types or pointers.
So let's look at that as well.
When I have a variable called z, and it's going to be an int.
And let's set that to our favorite number 42.
So what if we were to try to set z to be none.
So right now z is 42.
But sometimes maybe we have no value for it.
So we're going to set it to be none.
And remember, there's there's always this weirdo, you reassigned it error.
So let's get that out and see the real error.
And the error is, we expected a type int and we got none.
That is because not just integers, but every single thing in Python, unless in its type system, unless you say explicitly, it can be set to none.
It cannot, it will not be allowed.
So here you might say, well, okay, I'm familiar with languages like C# they have value types and reference types, and it doesn't make sense for a value type.
That's not what's happening here.
We could have another z2, which is a string, which is equal to never nothing.
We tried to set z2 equal to none.
And we printed it out.
We still get exactly the same error.
So even things that are reference types are allocated on the heap, they cannot be set to none in the type system unless you explicitly say so.
So again, this is an error.
So what are we supposed to do about it?
Well, we declare the type to be possibly nullable or in Python's parlance, we say it's optional.
The value could be there, but it doesn't have to be.
So we'll have Z3, which let's keep going the int example, I suppose, which instead of saying in an int, which is 43, we want to say this is optional.
And so this is where we have to use the typing namespace.
So far we've gotten away mostly with just using native types to express the type system here, but there's a whole typing module or a namespace which we can import.
And in here we have optional.
Now there's different ways in which we can say this.
Here's my favorite way.
We'll talk more about the different ways in which it can be expressed, But I like to say it this way because I find it very explicit.
Although I typically don't import the typing namespace and say typing.optional.
I'm just doing that to make it real clear.
More likely what I would write would be this and just import.
From typing import optional.
And I would just write it like this.
Okay.
But to be super clear so you know, oh, it's coming from that namespace.
Optional of this.
And down here now, we can go through and say Z3 is equal to none.
That's fine.
I got to keep printing it out or something silly, right?
To get those errors to go away.
We can say Z3 is none or we could say Z3 is a new string.
Sorry, a new number.
44.
That's totally fine.
But what we can't do still is we can't set it to be equal to ABC.
Even ignoring that little print error, it says it's an optional integer.
So we can get either integer or none.
And you can see one of the alternate popular ways to express that would be we could say same thing as Z4 and pipe none.
So this is another option here on how to express.
These two things are defined to have the same type.
You can even see that PyCharm interpreted the optional event as this int pipe none.
This is a 310 syntax.
Talk more about it later.
So depending on your background, this view of nullability may surprise you.
If you've come from a language like C++ where sure they have this concept of pointers and even value types, there's no pointers that cannot be set to none unless you explicitly allow them.
However, this does appear in Swift as well, some of the other languages, but Python's types, you by default are not allowed to set them to none.
They must have a value unless they are explicitly set to be optional of that type.
Takes some getting used to, but I think I really like it.
|
|
show
|
3:11 |
Next, I want to talk about something called unions.
And unions allow you to express that a variable could be one type or another type.
So let's look at a couple here.
We have a thing called UN1 and it's going to be the number 1 and UN2, which is going to be the word 2.
And for whatever reason, in this scenario, we want to explicitly say it could be a number or it could be a string, but nothing other than that.
All right.
we express that is traditionally we would say typing.union of int and str.
However, in Python 3.10, they added some syntax that we just saw in the nullability section that is a simpler version using the pipe symbol.
So we could say int pipe str.
And these two things are defined have exactly the same type.
It's just newer syntax.
Remember, I told you you need Python 3.10 as a minimum to do the examples in this course.
Well, here's one line that will only work in Python 3.10 or above.
If you're using older versions of Python 3.5, or above, at least, you could use this.
This syntax has generally been replaced with this one and I'm pretty, pretty much on board with that.
So let's put this as three here.
I'll put that as two so we can just do something we could print out you one, you and one plus you and two.
And if we run that you can see one plus two is three.
Excellent.
In this case, we're using them as numbers, we saw that if it was a string and an int it wouldn't work.
Down here later we could say u n one equals one and u n two equals two.
Again, the type system says that's totally good.
We run it again.
Now we get one two.
Okay, use a space but we don't have one.
But if we were trying to do something that didn't match the int or string like you in one is now a list of numbers.
We get this error and it says, Oh, no, no, no, it could either be an int or a string, but it can't be some unrelated thing like a list of integers.
So we'll put this as error.
Now, finally, when you look at the auto complete and the things that you can do, you can do count, or you can do case Case fold, these are string things, or you could do the real section or the imaginary section that are numerical type things.
Ask if it's a decimal, that's a string thing.
But if you do something like case fold, which only happens to appear on strings and not numbers, I believe in some of the linters, you're going to see that as an error.
It'll say the operation you're trying to apply to the union only exists on one of the elements.
So probably the safest way to think of this is you can do the things that happen to appear both on integers and on strings.
So you should think of taking a union here as something restrictive.
It's less than an integer and it's less than a string.
|
|
show
|
2:03 |
So far, we've been really sure about what types we want to work with.
But sometimes you want to just be able to say, I don't know what this type is going to be.
And that's okay.
Python has worked that way for its entire life until 3.5.
And still, it's an optional add-on gradual type of thing, this typing, right?
What if you don't know the type?
Maybe I have an unknown type of thing, and it could be A number, it could be a list, it could be anything.
How do we express that it could be a number here, but there's no guarantee it's going to be a number next time?
We use typing.any.
We look at typing.any, it's a special type indicating an unconstrained type.
It's compatible with everything.
Any assumed to have all the methods that you might want to call on it.
It has all the values.
Basically, you can do whatever you want with it.
So typing that any often you would just write it as any like this.
Maybe I'll comment that one out because this one runs.
We'll let you do anything.
So we can print out unknown just so we get rid of the warning if I make it something else.
Now it's going to be 78 again but another time.
Now maybe it's the set 7 and 8 and 8 which is really just 7 and 8.
All of these are completely fine.
You see no errors, no warnings because we said, hey, this type is unconstrained.
Let it do whatever it wants.
What do we get in terms of autocomplete?
Well, if it can be anything and you intersect all of those possible things, what do you get?
You get basically nothing.
So if it can be anything, you don't get much editor help.
And obviously you don't get type checking if it was never meant to be a set.
Well, it can still be a set.
Here's how you express that a type can be anything with the typing.any type.
|
|
show
|
3:37 |
One area where Python is a little bit lacking is in constants.
That is, I want to create a variable whose value never changes.
Maybe I want to create a variable for pi.
You don't want people to reassign pi, do you?
I think even in the early days, you could reassign true and false to be like the opposite of each other if you wanted to cause trouble.
All sorts of weird things like that.
So Python is not very strong in terms of its constant, its ability to express constant values that can't change.
So I'll have a const one here, or how about not const one.
This is gonna be some value.
And again, we're just gonna print out these things so they don't have any issues.
Now we can say not a const is other value.
This is regular variable behavior.
However, in Python, there's a convention that if you want to have a constant value, you have a way of expressing that, sort of like the underscore field represents a protected one you shouldn't use, but you can still see, right?
There's some conventions around constants as well.
We'll have this constant two.
This will be fixed value.
And let's say that this is an implicit conventional constant.
So if we set it to no longer fixed, what do we see?
Not very much, right?
There's no warnings that this has happened.
Should have a warning, I guess, here, even though it doesn't, but it doesn't kind of follow the convention, right?
Well, in the Python type system, you can express something as a constant.
Remember, the typing doesn't affect runtime, generally speaking.
So even doing so won't actually make it a constant, but you can detect it with some of the linting tools as well as PyChart.
We'll call this const three and we can express this as just saying final.
So typing that final here, and what values are going to have really a constant sort of so it's an explicit constant in the type system.
Let's print out the thing again, just so we don't get that false warning here.
go, this should not change.
And here we can see that there is an error, the little action thing goes away.
It says const3 is final and could not be reassigned.
Well, when you say could not, what exactly?
What about could not do you mean there?
It looks like it did change even though it's really a constant sort of should not.
Let's change this word to final as a constant and should not be reassigned.
But you know, whatever, this is now an error in the type system, right?
Pretty much all the time when you see in the type system, that means, well, it's still going to run along with its valid Python.
But here's a really nice way that we can go and express constants using typing not final, as usual, I would normally write this, but just to be super explicit for everyone that it's coming out of the typing module, I'll write it like that.
So we can see that constants get a little bit of an upgrade.
You can pair the naming convention of all caps with the typing of final, kind of bring those two things together and get a little more support to say you really shouldn't be messing with this.
And you can see awesome editors such as PyCharm will say, and you shouldn't be messing with
|
|
show
|
6:02 |
This last feature is really powerful for security situations, especially around injection attacks.
So I've titled it ""Beware Little Bobby Tables"" and this comes to us from PEP 675.
Arbitrary literal string types.
And so the idea is that some strings should not have arbitrary input.
So let's imagine we have a database situation where we want to write a query.
We want to say the query is going to be select star from students where, notice how awesome PyCharm is, it's decided that this is a SQL string inside and so it will auto-complete things like where, order by, and so on.
where name is like percent, percent something, what or even is exactly equal to, let's do exactly equal to, is equal to, and we'll change our quote so I can put single quotes in here around their name in case there's spaces, student name.
All right, well, where does student name come from?
Let's ask the user who is running our app.
Hey, user, we can be real careful.
This is a string and we're gonna say input.
What is the user's, what is the student's name?
Right, now we could do this, but let me comment that out and just explicitly set this to, you know, Robert Tables.
So we'll print out the query and let's go ahead and run that.
Select star from students where name is equal to Robert Tables.
Fantastic.
This is good, right?
And let's go ahead and be super clear here that this is a string as well, 'cause it is.
And we've got our highlighting here for SQL.
There's a few warnings here that we can change our global dialect to, let's say SQLite.
We could also connect a database and it'll actually auto-complete the columns and the tables, which is amazing, but we're not gonna do that.
So here we've got a really good example of how we can create a query and send it off to a database, and it's great.
Let's try again.
I won't redeclare the types, 'cause you shouldn't.
Let's say, imagine of having the input, having them type Robert Tables.
What if they typed something way more creative?
What if they said, ""Well, the first thing we're gonna need to do is make sure this query is valid, and it accepts things like spaces and stuff, So they're surely going to put a single quote here in order to make that valid.
So we want to close off that quote like we did here.
Then we want to enter a new statement, a totally unrelated statement like drop table students or users or you know whatever.
That's one statement.
Another one we want to run.
And finally, let's comment out everything that comes after.
Let's try that one.
How's that going to work?
So run this valid but meaningless query, returns nothing.
Now we run the query drop table students, yikes.
And then we're going to comment out the closing dash or order by or whatever happened to follow that we don't care about.
And it's a little bad, a little bit bad.
And check out little Bobby tables from xkcd.
It's amazing.
So what we would like to do in Python is express that this is not allowed.
You're not allowed to take arbitrary input from somewhere and combine it with this string because this string is protected against injection.
Let's comment this out here.
And put that back as well as potentially this in a minute.
So now we can come down here and say that we get what the string is, we want to say that this is a typing dot literal string.
And what that means is, it cannot take arbitrary input.
So down here, if we were to have in this case, like so, we should have an error here.
However, you can see that there's no real errors.
I don't believe PyCharm or I don't know whether any of the other editors as well actually run the check to make sure that this input is fixed.
Rather, it should be another literal string like for example here.
This is fine if this is another typing dot literal string which just a constant string is, you can do this combination.
So this is allowed, but if this comes from the input, whereas it's like this, it shouldn't be allowed and it'll show up as an error in the type that you shouldn't be able to combine a literal string and a non, like a mutable user input string.
You got to use something like mypy, one of those things to actually check for this error, but you know, if it'll help you, why not go ahead and put this in here as a literal string, especially for the queries where you need it.
Because we all want to avoid this situation of little bobby tables.
|
|
show
|
5:12 |
Next up, let's talk about typing and functions.
You saw actually in the pep, the very first hint of type hints in Python was around a limited subset of syntax for functions.
And of course, there's really great things we can do here.
And they're really actually super important.
Functions are ways to break complex code into a little box, a little Lego piece that you can use in your other functions in your overall application.
And having that Lego piece describe what goes in with types and what comes out with types is incredibly helpful for conveying how the pieces fit together as you put them together in your application.
So what we wanna do here is we're gonna start with just something real simple.
Now I'll just print typing functions like that.
What we're gonna do is we're gonna create a couple of different types of functions that we could use.
So let's start by creating one called fib or Fibonacci.
And at first we're not going to do any typing on it.
Let's just write it out real quick, then we'll add types.
So the Fibonacci algorithm in Python is really, really concise because you have tuple unpacking and creation on one line.
That's cool.
So we can say next, say current and next.
The full spelled out next is a keyword built in.
So let's just do like this.
It'll be zero and one.
And then we just say for nothing in a range from range of n, we'll do current next is equal to next and then next plus current.
That's that awesome tuple creation and unpacking, tuple creation and unpacking.
Then we'll just return current.
And let's go over here and say n1 equals fib of 1, 2, 2, and 3.
3 and we'll print out in 1 and 2 and 3 And you can see we get 1 1 2 I guess do one more just to show So pretty sure that we're getting the right thing which We are not because I didn't put the 4 there alright 1 1 2 3 5 8 13 etc So that looks like it's working great, but when I go to call it when I say hey, what about this fib?
What does it take?
It takes an n.
And what does it return?
I don't know.
You can actually see that PyCharm has done some work to say it takes whatever, but we're sure that it returns an integer because the way the algorithm works, they were able to trace through and say either of these are always integers and they always become integers and the thing you're returning is an integer.
So the editor is helping, but you shouldn't rely on the editor's guess.
You want to be more explicit than that.
So we can be more explicit and say, instead of it saying it takes an any, right, when you saw here, fib in colon any, we can say, no, no, no, that has to be an integer.
So if somebody calls fib of seven, get the error, no, no, no, not a string, a number, not any number, an integer.
And then we can also express the return type as an int as well.
So this is our first Fibonacci.
Let's suppose we want to protect against somebody possibly calling this Fibonacci with a small number, you know, like a negative one.
What is the Fibonacci negative one?
We could raise an exception and say it has to be natural number 1, 2, 3, etc.
That's possible.
Or maybe we want to just return an optional integer.
And say if you give us the wrong one, we'll test for small, just give it a different function name.
So we have a slightly different version here.
I'm gonna pass in an int.
And remember, when you say we could return an integer or possibly nothing, what we have to do is say optional and import that from typing of the type.
So in this case, instead of raising exception, I'm gonna say return a negative, I'm gonna return none if they give us a non-natural number, not one, two, three, and so on.
We'll say if n is less than or equal to zero, return none else return fib of n.
Now of course you would just add this check to this one here, but just to skip re-implementing it.
So let's do a one, a two, and I'll put that as five.
And now you can see we get none and five over here as you would expect.
So typing works pretty similar to variables.
All the rules we've applied, you could pass in literal strings, you have optional, you have unions, you have return types, they have to either be concrete or explicitly optional.
But when we come over here now, and we say dot, you can see exactly you have all the integer operations there because the type system is telling PyCharm this function returns an integer, so all the stuff you work with here should be treated as if it's an integer.
|
|
show
|
2:43 |
Next up, let's look at functions in a way that maybe you haven't thought about before.
So we could say def sayHello, give it a name, which is a string and it will print out, ""Hello there, name?
What goes here?
Well before we put any type there, let's go over here and use this.
say x.
First we'll just say say hello Michael.
That's cool.
What about say hello Sam.
And let's capture the value here.
See if that runs.
What does this even do?
Well, didn't crash.
That's positive.
But what is x?
We can ask for its type like this and we can actually print its value like that.
And it turns out to be a none type in none.
That's weird, isn't it?
Because PyCharm is saying it doesn't return anything and surely enough down here there is no return statement.
It's not like we said return none, which would make that error go away even though it's still returning none all the time.
PyCharm's checker or linter doesn't have a way to distinguish, we just use even the word return, we still get the same value of none.
So if you have what in some languages is called a void method, a method that doesn't or function that doesn't return anything, you might expect there to be a void keyword or something like that in Python.
But Python functions, even if there's no return keyword used whatsoever, always, as we just always always return none.
So if we explicitly say that here, we're setting up the type to be correctly so to be correct, but here PyCharm still says, even though you set the type, it doesn't return anything, you shouldn't be doing this.
Okay.
But functions always return something.
And if if you don't return anything, they still by default return none.
And so the way to express that is not an optional string or an optional something, but just explicitly none is what's returned here.
I suppose you could do none type if you really wanted.
You could make your PyCharm error go away because it's not testing for that, but it says you're not actually returning that.
None is what you want to put here.
Okay, so that's how you handle expressing what the return type is when there is no return
|
|
show
|
5:33 |
The last thing I want to look at in this function section is what do you do to express when the function itself is a parameter.
Functions are what are called first class objects in Python.
That means you can pass them around, you can create them as lambda expressions, inline, all sorts of cool things.
So let's suppose we want a function here called use function that receives a function.
So we'll say def use function, and it's going to get a function f and it has no type in the beginning, but it knows, it assumes it's a function.
Say something like what is your favorite number or whatever.
And up here we can say use a function.
We need to give it a function, let's give it a lambda expression for p1, p2 for the parameters.
And then what's going to happen when you call it, passing Michael and 42 or whatever you do, we're going to do a print statement for an f-strings, we'll say, he won.
He to P ones favorite number is P two, and then we're going to pass that function down to use function, it'll be passes F, we call it, Michael will go to p 142, we'll go to p two, we'll go to print statement.
Let's give it a try.
Michael's favorite number is 42.
Well, that's really nice.
But do we know we're using it correctly?
Like, let's see.
What will pycharm tell us?
Okay, it takes a function.
And it literally just says it takes f is it actually a function?
We don't know.
Let's hover over here.
It says it's an any and it returns none.
Okay, well, that really means nobody knows what to do with this, not even pycharm.
So let's see if we could be more explicit about that.
What if we could describe the type of function that was allowed here?
So in the typing namespace, we have what's called a callable.
Not just functions, but these could be classes with the dunder call.
There's even, I think, a PEP out to make a module itself callable.
It doesn't really matter.
It just means you can do this to it.
You can execute it in some sense, right?
And not just can it be called, but what are its arguments and what are its return types.
For this, it's a little multi-layer, you put a list of the arguments and the arguments are going to be a string and an int.
And then you say the return type, which is none.
So now if I come over here and I say f of, guess what, it takes a string and an int.
And if I go over here and I want to pass something, you see, well, it's going to require something.
Let's define, let's just try a lambda function.
I'm not sure PyCharm will catch that.
But let's say lambda of A goes to return A, or just A times A.
Nope, it should be checking that.
Apparently it's not.
Let's make some other functions.
We'll just create them down here.
We'll say def usable func one.
This is going to be, takes a name and a number and returns none.
Make this correct for the moment, say int.
Robbed the implementation here, we'll say name's favorite number or call it common number in this case.
So you can tell there's some kind of difference.
And now we can use func and pass usable func1, not calling it but passing it like that.
Let's try another one that's got a different signature.
See if that'll get caught in this case.
2.
And we'll just say ""Hello, name!
See if we can get it to complain if I get that wrong there.
No, but if I run it, sure enough, you can see there is an error here.
So I guess our editor is not catching this one, but that's okay.
Still helpful to have it around, right?
So if you really want to be explicit about saying, I take a function just like this, you say typing.callable of string comment, those are arguments, string int, and I expect it to return nothing.
And at a minimum, you can see it didn't catch the error where we're using it wrong, but at a minimum, it tells you, well, unnamed variable string is the first thing, unnamed variable int is the second thing you have to pass.
We can probably get it to show us that if I had another number here that know that number doesn't go there, yes, it does.
And if this is 4.2, it'll say, The second thing must be an integer, not a float.
So that's really excellent.
We get some help from knowing what the type is right here.
If we say probably x equals, well, it says all you're getting out of there is none.
So that's not going to be super helpful for you, but we don't quite get the same errors we saw before.
So typing not callable is how you express the signature of a function when you're actually passing it as an argument or storing it as a variable for that matter.
|
|
show
|
7:21 |
Collections and data structures in general are really interesting in Python typing land.
So we're going to talk about a couple of ideas here around collections and how we express their types and the type of things they contain.
Now in Python, it's a super dynamic language.
It has no explicit types traditionally.
So we can do things like smush heterogeneous types together into a given collection.
Let's create some types and play around with that idea just for a second.
So we'll create this person here.
And let's make a list of things.
Standard old simple list, things.
And maybe the first thing that's going to go in there is that.
We'll have a person.
Their name is Michael and their number is 42.
Oh, I have those backwards.
Let's swap those.
I'm kind of wrong to say the number first.
Don't want to go to Iron Maiden, shout out to Iron Maiden.
I'm not a number, I'm a free man.
All right, so my goal number there, and let's see, 7.14.
We also have seven here like this.
We have a sane, Carpe Diem, all those things, right?
So there's nothing wrong with this in Python.
Oops, let's make sure we're running the right one.
We run this, what do we get?
A bunch of output.
We got seven, we got some person object that a memory address, 7.147 in another way as a string, seize the day, all that saying.
We can have, you know, we could even have a list within the list, right?
Put our start of our Fibonacci numbers in there.
There's nothing wrong with this in Python.
We can completely have these heterogeneous types just like we define a number and a name on the person type, But then later we could come along and go, you know, person.ssn is 434, right?
Let's just, instead of printing, let's just put just there.ssn in here.
And even though there's a warning like, hey, this doesn't exist on this type, we can do that, right?
So Python is super dynamic in this way.
We can take the types and change them at runtime.
We can, our collections can have lots of different things.
Why do I bring this up?
'cause even though it's possible, it's not really how we use our code, it's not how we write programs.
Typically, when we define a class, the things we put in there, that's what we want to be in there, we just use it.
This, let's say, nah, probably not, okay?
Similarly for the things we got here, like yes, you can do this, but just 'cause you can doesn't mean you should.
So let's look at the right way, you know, right as according to Michael, but also the way I think I see people doing things all the time.
Take that away so it doesn't crash 'cause commented out the addition of the SSN.
So how do people really use these things?
Well, they actually have the same type of thing.
If you want a bunch of numbers, maybe we want just the prime, well, see my memory here, and so on, right?
Maybe we want to start at the prime numbers here, right?
Everything in here is going to be the same.
Similarly, if we want people, could be, put our Michael in there, but we could also put Sarah, and you know, Sarah, she just loves the number three.
It's amazing.
And Zoe loves 100.
Don't know why.
Right, so these things are homogeneous types of containers or when you define the type, they're set and fixed, right?
So in the typing system, you can't really do a lot about this, but also when you're programming, that means as you're interacting with the thing, sure, you can print them out, but what do you really do with them, right?
It's not a great way to work.
Here, you know that these are not just a list, but a list of integers.
Here we know we have a list of not just a list, but people, person objects.
So now we can go and set an explicit type on these.
We can say this is a list.
Now, traditionally, you may have seen this.
From typing import list, capital L, this is very important, and then you would see capital L is for this.
And then maybe, as we already saw, when you want to say what is in here, kind of like the callable, we'll say this is a list of integers.
This is totally fine, but this, I'll put this like so, you have it.
This is how it was when it was introduced.
And everyone was like, this is super annoying, I got to keep importing list.
And we have a built in thing called list.
And that's actually what the type is, it's actually a list, not one of these, right.
But you weren't able to do this to it until I believe 3.9 or above.
The typing system has evolved in a lot of the recent releases of Python.
And the collections is certainly one of them where you have the capital L list, the old school way, and the new lowercase list.
All right, same thing for here.
We have a list of this time person.
That's our type that we defined above.
And now when we work with numbers, hit dot, what do you see?
All the options, all the things that you can do for list because guess what?
We know for sure it is a list.
But more importantly, if we go to an item of it and we say dot, we know that the things within it are guaranteed to be integers.
So I could call two bytes.
I don't want to, but we get autocomplete for it to help us with that, right?
Similarly for people, list, of course, but we have a person object and the person has a name and a number out of its strong type, right?
So let's, we can just print these out, I suppose.
So you see something interesting happening.
You can see there's the bytes of whatever that number was there, which is two.
And there's the two.
And then Michael is the name we printed out of the first item.
Okay, so this is how we do, how we work with collections.
We say the collection type bracket, the contained type.
And again, this style of programming where you have a homogeneous, consistent style or type of thing contained in these collections is not just a recommendation.
It's the way that I see almost all code written.
These are possible, but they're just they're kind of not really that useful.
And they also don't have a great representation other than we could say a list of any, right?
import that from typing, which means, of course, you can do things dot and get list operations, but on T, we have no idea what T is.
Good luck with that.
|
|
show
|
8:28 |
Well, what about more complex data structures?
I mean, list is interesting, set is kind of like a list, but we also have dictionaries, tuples, all sorts of things.
We have maybe a list of tuples.
These things can combine in interesting ways.
Now, let's imagine for a minute that the person's number, remember up at the top here they have a name and a number, that their number is distinct.
It's unique.
You can't have more of them.
So we've got 42 for Michael, 3 for Sarah, Zoe, 100.
This is something like, let's say, a user ID for the moment, right, in a database.
Let's create a dictionary that will allow us to do extremely fast lookups, kind of like a database, almost with a primary key, which would be that ID, that number.
So I'll call it user lookup by ID to be super explicit, no types yet, and wrong symbol.
And what we're going to put in here is their number.
we can use our people to do a dictionary comprehension over here.
So we can say p.id.
Now here's the thing that drives me crazy about comprehensions and generators in Python is you say the elements before you say what the type is.
So notice I say p.
I get nothing.
But watch this.
but watch this, for pn people, p dot, hey, look, there's what I was looking for.
Ah, wish those were another order.
Anyway, p dot number is the lookup and then the value is the person itself.
So this dictionary would look like 42 colon 3 colon this, right, but we'll just generate it from the data above.
Okay, so we got our people and our user lookup here.
Let's see how much PyCharm knows already.
pretty smart about putting these things together.
So it does know it's a dictionary, let's say, whatever, but it doesn't know what's contained into it.
Okay, so that's pretty interesting that it didn't figure that out.
But the type system knows nothing about this, right?
Just like above, it could contain a bunch of random things, the keys could be sometimes a number, sometimes a string, you don't even know what to put in there.
That's a problem, right?
But again, like this example above, some sometimes this happens in Python when you're matching JSON data, but if you're doing a lookup, like given, say an email, I want a user or given their ID, I want a user and I've got a bunch of those in there.
Well, you want this to be a consistent type.
Exactly.
So let's go and say the type right here, we'll say, this is going to be a dictionary.
So dict.
And just like before, we say of, but instead of having one thing, which is int, right?
That's the key, this p.number is an int, and PyCharm helpfully tells us that.
But what is p itself?
The p is a person.
So we say dict of key value gives us a person, and that gives us all sorts of awesome stuff.
So we could try u1 equals user lookup of, let's say, and notice right away that PyCharm says, no, no, no, no, no, no.
You're trying to index with a key, maybe with a string, maybe like an email, but you forgot that this is by ID because we didn't name it well, we just called it user lookup, so what the heck do I use a lookup by, right?
I gave it a good name, but still this takes ID and this kind of tells you the ID is a number, not a string.
Right, so this is gonna throw an exception.
We could also come over here and say get seven, And this version is going to return none, or whatever default value you put here, we could put, you know, default and get the string default back if it doesn't exist.
But let's do it like this.
I will print out you one at the end real quick just to see comes back with none, right?
Let's see another way which this typing for the dictionary helps us you to let's imagine what we were getting back is their name, or their email or some attribute about them, right?
like dot name.
So given their number, we'll get the name back.
And that's all we're storing.
It's not what it is.
But imagine so we thought that was the case.
And we said, YouTube is going to be string equals user lookup dot get.
And let's actually get somebody back here.
Notice again, it says, in the other direction, you think what you're getting back here is a string.
But what you're actually getting back is a person because the dictionary given a key, correct key in this case, gives you back a person or none.
So an optional person, right?
So this is also now again, if we print out you to what we do actually get back is the person, Michael here, because the code works, it's just the type system is out of place, right?
So we'll keep that.
Technically, it runs, it's just the types are becoming inconsistent and the editor's letting us know that that's the case.
Finally, let's say U3 equals user lookup by ID, get three.
Now we're not even saying what U3 is, right?
We've said what U1, we didn't say anything.
U2, we tried to say it was a string and it's not, we're getting an error.
But if I just go down here to U3 and I say dot, check it out, it knows already by type inference, this returns an optional person.
So all the things that should be happening here are going to be what's available on a person.
Now, the final thing we could check here is you for, let's say you for as a person.
Now, there's no errors here, right?
If you look at, there was an error, there was an error, but PyCharm is not showing us an error around you for, I think that that is a bug or maybe that's a little harsh limitation of PyCharm because what should it be?
An optional of person, right?
And if you hover over it, PyCharm knows it's an optional person.
It's a underscore VT pipe none.
Okay, great, the value type, but the value type is person, right?
So it's given us that back.
All right, so that's it for this type.
We can do similar things for tuples.
Like if we had a tuple and it had a number, a number, a person, and a string.
And you wanted to express this as a tuple of four things, int, int, person, string.
You can do that by saying this is a typing, not tuple, of int, int, person, str.
Okay, perfect.
And if this was an int, it'll say, nope, you were expecting to have an int int person int and what you gave me is an int int person str, all right?
So it'll keep those things together, also really nice.
This is handy when you have a function, say that returns some kind of well-known tuple, right?
Maybe it returns two or three different pieces of information all bound together and you can express that as a function that will return that, so say def getData, it's gonna return one of them.
So here if somebody calls this function, and they know they're getting this tuple back, I could say t2 equals get data, t2 dot, I'll say, what is the last item?
So that would be bracket three dot, it's a string.
What about bracket two?
Person, right?
So that gives you a ton of good information.
Okay, so this is how we work with more complex containers, right?
We saw the callable was callable of a list of, say, int int str, right?
Similar things here.
We say int int person str for this.
For our data types, we have a key value for dictionaries.
Up here we just have the contained type.
And the main idea is that for many of these, the typing works best if it is a consistent, homogeneous set of things.
So it's always integers in the list, or always persons in the list, and so on.
All right, there it is.
|
|
show
|
6:26 |
Now, let's talk about classes and Python types.
I want to go back to our motorcycle example we did from chapter two, because most of what we need to talk about is already there, and I don't want to overdo it in terms of time and starting over.
So, we've got our class motorcycle, and we've already played with classes like our person just previously.
And we've got our initializer, which plays a really important role because this is often where the fields of a class are defined.
So these are the four fields now, off-road, engine size, style, and model that are part of the motorcycle.
So we can say the type of it just like this.
Self.offroad colon bool equals off-road.
And we can also say at the call site what type it is.
Off-road is a bool, right?
So when we create a motorcycle somewhere down here, we could say this is true, remember?
and it's gonna say, no, no, no, the thing you have to pass is a Boolean, you gave us some weird string thing.
We talked about that previously.
Right, so that's pretty standard.
When you have methods, even properties, you just like we do with our function conversation, we return, we say the type that returns, that's all good.
Here we've got a list of motorcycles, it's just a regular function actually.
This is the end of the class.
But you might have noticed a couple of things.
Sometimes our motorcycle, sometimes classes, can have class wide fields that can be overridden on an instance, but typically are not.
So for example, over here, I could say number of wheels.
Right, so we could have wheel count equals two.
Do all motorcycles have two wheels?
Well, I suppose that depends on your definition.
We could be freaks and have something like this with two wheels on the front.
I don't know, like it's one interpretation of a motorcycle, but let's in our example say the motorcycle, all motorcycles have a wheel count and the wheel count is set to be two, unless I guess you could override that in here.
But this is a class level field where these are instance variables, right, fields.
So let's go over here and when we do the same thing, we could just say that that is an int and down here at the bottom, we could print B dot.
Now we have wheel count and if we run this, you can see that two there on the end is the one that's coming from there, right?
So if you wanna have a class level field, same thing kind of as here, but just put the type up there.
The more interesting thing is you might, maybe you caught this, maybe you didn't.
I didn't put a return type on createAdventure now, did I?
So for that part, what does it return?
It returns a motorcycle.
Obviously, Michael, it says right there, new motorcycle with these values.
Cool, well, let's just say that.
Let's say it returns a motorcycle.
Wait a minute, PyCharm says that's an error.
That must be something stupid of PyCharm.
We'll just ignore that.
Nope, it really, really is a problem.
The new error messages in Python 3.11 are nice.
Motorcycle, did you mean motorcycle type?
No, I meant motorcycle.
What is going on here?
we've got to go back to how Python works.
There's no compiler in the traditional sense of Python.
There are PYC files and the stuff kind of gets compiled, but it doesn't do a pass through to look at all the types.
And then another pass through to say, now that I know where these are located, let me actually click these pieces together.
So for example, in this class here, we're defining motorcycle and Python just runs line 16 to line 37.
And as the lines run, that defines the things.
is okay, we're gonna start defining a class.
That thing has a wheel count, it has a function called init.
Okay, great, it has a property called this.
And literally going step by step is defining it.
And it's not till line 37 that it even knows what the class motorcycle is.
But how do I address the thing itself when I'm on line 34, but Python, the way it executes, doesn't actually have a thing called motorcycle until the whole block of code runs.
It's like a weird catch-22.
So early days of typing, what you used to be able to do is you would say this.
It looks like an error, but it's not quite the right type.
It says, okay, that's a class motorcycle.
No, it's not, it's a string.
But if I go to motorcycle.createAdventure, I hover over it.
Well, once I get the warnings away, hover over it.
It says it returns.
you can see not in quotes, but an actual motorcycle.
If I say dot, I get motorcycle auto-complete.
So that was a way to kind of fix this so that you don't really, 'cause you couldn't say motorcycle, right?
The thing wasn't defined, so you put it in strings.
But what if there's two motorcycles somewhere in your program, right?
It could get weird.
So to be more concrete about this, they added something called typing.self, right?
we're familiar with self here, even though this is a class method, it's still use the typing.self.
And what you say is, I don't know what, I can't, because of this limitation, I can't say what class I'm in by name, like a regular programming language like this.
And I don't wanna use that string thing.
I just wanna say whatever my name is, 'cause what if you change the name of the class, then it's no longer accurate, right?
So I don't care what it's called.
I just want it to be, I return one of me.
And the way you say I return one of me in the objects is typing.self.
So we're already used to having self to refer to the instance of an object.
And so regardless of its class method or an instance method, static method, we say typing.self inside a class, that's what gives you back the thing.
So again, if we say motorcycle.createAdventure, We hover without warnings, we hover over it, it says, now it returns a self, but it's coming out of the class motorcycle, and you hit dot, you can see, it's got all the motorcycle goodness here because it knows it's the self parameter or the self type coming out of a motorcycle class, so that's the motorcycle class.
Make sense?
|
|
show
|
4:26 |
One of the real powers of Python is its insane external set of packages.
Here on PyPI, we can see there's almost 480,000 separate libraries that do who knows what, you know, you name it, there's probably a library that will do it here, right?
How maintained are these?
How turned on to the type system are they?
There's a bunch of packages that for a long time tried to straddle the Python 2 and 3 world.
And I already talked about, well, in 3.9, you can use this functionality for the typing definitions.
But before that, you've got to use something else.
Well, before 3.5, there was none.
So a lot of this wasn't able to be applied to those ones that were straddling the fence, so to speak.
And if they haven't been updated, well, what do you do?
They're not your projects.
It's not your code.
you can't go and make changes to it or specify the types, right?
So what are we going to do in that world?
In the Python type system, there's a way to have a separate file that says, It looks like that package, but it actually has only type definition.
So if you come across a library, a package, code, whatever, itself does not have type information, and for whatever reason, you don't want to change it, you could do this to your own code, you know, at your company, if they say this part of code we're not touching, but you still want type information, there's a way to add that to it.
Now let's start by looking at this place called type shed.
Type shed has a bunch of things for the Python standard library.
So for example, if we talk about asyncio, you can come down here and here's the futures not dot p y file, but futures Let's look and see what's in this.
So it's got all the standard imports you would expect, but here's a function given an object.
You can ask, ""Is it a future?
Well, it can be anything, so object kind of is another option there.
And it tells you this is going to return a type guard of future of any.
Similarly, the class future is a weightable of whatever the type here is, the type that that we've defined right there as a type variable.
So it's a waitable of that type and iterable of that type.
Here's another function.
It takes a self and returns a base exception or none or so an optional exception.
And these dot, dot, dots, this is not the editor being collapsed.
This literally is the implementation of that code, right?
So these PYI files allow us to externally add type information to existing Python code without changing that existing Python code.
So why bother showing you typeshed at all?
Why not just do that for our own code?
Well, we will, but what's interesting is it's not just for the standard lib, it's also for other things.
So you may be familiar with Pillow, a library for working with images in Python.
Well, here's Pillow's definition for its types.
So the image plugin, Its format is a class var of literal of that.
Its format description is a class var of string and so on.
Interested in CalDAV, there's its information.
Docutils, DocOpt, Flask, Flask things over here, Flake 8 stuff.
So you can see there's a bunch of third-party things that you can find the type definitions for.
You can just pip install typeset element.
So for example, up here it says things like mypy, pycharm, pyrite, these all come with a copy of typeshed bundled with the standard libraries or bundled with themselves.
So you don't need to do anything.
But if you want things for third party packages, you can install them.
For example, if you're using six and requests, you can install types-six and types-request to get those stubs installed into your project.
And then things like PyCharm or mypy will automatically pick them up.
Okay, so typeshed is a whole bunch of work that many people put together to already define types for not just Python, the standard library, but also for these popular external things like requests.
|
|
show
|
3:56 |
All right, so we're going to come up with a function here, and it's going to do who knows what, and it's going to have-- we'll give it its boilerplate stuff here, tell it to run.
And let's imagine we could have a calculator class, and maybe just a calculator module.
And it can do things like add one and three.
And we can print out the sum of that, right?
Well, obviously, since this doesn't exist, PyCharm can't even help us work with it.
So let's go ahead and imagine we had one of these.
It's got a really creative implementation like that.
We'll import it.
No, no, no.
Now notice PyCharm is not suggesting that we can import calculator because what I have to type here is I'd have to type from code dot, I can't do 03.
Well, I guess too bad.
You can't work with those unless I rename these folders to like ch03 or whatever.
So the problem is PyCharm and Python in general, if it runs with that as the working directory, it doesn't see calculator as a thing.
So what we can do is just come down here and say mark directory as a sources root.
That adds this to the Python path, which now means that we can say import calculator like that.
And notice the X and Y pop up.
Looks like things are making sense here.
Okay, great, does it run?
Yes, four, one plus three is four.
All that work to figure this out.
We could have done that.
But now let's imagine we're in the scenario where we have this super useful calculator app, but for whatever reason, we don't have the ability to change it.
It's an external package that doesn't have type information or it's our own code and we're just in the maintenance.
We will not be touching this or I guess even, I hope not, but even possibly it could be the case that you need that to run in Python 2 as well.
What do you do?
You check typeshed.
Typeshed doesn't exist for our code.
So what we're gonna do is create another file and I'll just call it a blank file.
I'll call this calculator.pyi for information.
And then we're just going to define a function with exactly the same signature.
Copy and paste is allowed here like this.
Right, so you get it exactly right.
And then we add the type information.
That's not valid code as it is.
You can see the error here.
There's like a warning.
It expects something.
You could type pass, But Python actually has a meaning for the triple dot.
Like let's just really quick print that and then let's print, watch this, type of triple dot.
Look at that, ellipsis, class ellipsis.
Like that is a thing, but it also stands in for placeholder.
So you can put it even like this if you prefer.
So now we can come over here without adding types to this and we look at what goes here And it says, oh, it takes an integer, right?
So if I come down here and try to call it with a 1.1, error.
Oh, that's crazy.
So pretty cool, right?
All we have to do is define this PYI file and the same like next to the PYI file or somewhere available within our project and then put structure that we want to type into it and then put your ellipsis here to just say there is no implementation.
We're intentionally leaving it empty because of this.
|
|
show
|
4:12 |
Now, you might have caught a little issue here.
Notice there's an error when I tried to use a floating point number.
It says we got an int.
We expected an int and we got a float.
Seems totally reasonable to use floating point numbers in a calculator, doesn't it?
So what do you do?
Well, let's look over here.
One option, I'll give you three options and we'll get to the best one last.
So first option is, sorry, you only add integers.
I'm not a fan.
Option number two is to say, well, instead of that, what we can do here is we can say this is an int or a float, right?
Union of int, float, or we have the simpler version in 3.10, right?
And it can return any of those.
Whew, okay.
Another option.
I have four.
is to say real number equals...
Maybe make this a little explicit, int, float, right?
Maybe make it real number, how do you feel about this?
And then down here we can do this as well, put a real number, define this somewhere for ourselves.
Can be more complicated than just two things, of course.
And notice, now the error is gone.
If we say what type is this, it knows it's an int or a float.
That is okay, and I'm actually kind of all right with that.
I guess that was another one of the options.
I'll put this here so you have it as a progress through time.
So we could explicitly write in float everywhere, or we could define our interesting own type and then refer to it later.
That's pretty excellent.
But finally, I will show you what we probably should do.
If you go over to the PEP for type ints, they talk about this thing called the numeric tower.
So numbers in Python are pretty rich.
There's a numbers module, which defines things like rational or integral numbers.
We have real numbers, we have complex numbers, we have integers, complex, but we've talked about float and int, we're just talking about.
And it says, rather than requiring you to go to the numbers and import a bunch of things here, or coming up with what we've done so far where you pipe them all together, it says, look, in the typing definitions, which is all we're talking about, right?
At least for now, when we get to the runtime stuff, this might make a difference.
But for this, it says, look, having float is what we're going to do when an arbitrary numerical type, arbitrary real number type is acceptable, right?
A decimal type or an integer representing, you know, a true integer, right?
So you're just gonna use a float if you wanna be a float or an int.
It says, look, we realize that does not account for like fractions and rational numbers and some edge cases, but people are rarely working with those and if you are, I guess union away if you like, right?
Or just be real specific and say it only takes fractions.
Right, so the fix, even though we went through this interesting flow of like, oh, we get to find this thing and it's a union and it's an int and a float and it could even be an int float complex.
So the fix is just to say float, float, float.
And back over here, you can see integers accepted as well as that, right?
If you see what it takes, just says float, float, run it again.
Sure enough, of course it works.
The other thing to note over here is it says, if you specify something as having the type float, an argument of int is acceptable.
Similarly, if it's annotated as having a complex number, then float and int are both acceptable.
So if it really could take complex numbers, or it could take floats, or it could take ints, then complex is what you want.
If you don't wanna work in complex number land, which I don't blame you, float is probably the most common reasonable answer here.
|
|
show
|
1:44 |
Now coming in Python 3.12, this section here is part of that 15% when we said not 100% of the stuff is covered.
Python is going to have PEP 695 is accepted and is coming in 3.12.
And basically, the name is type parameter syntax or generics or templates, and depending on the language you're familiar with.
So we scroll down a little bit, you can see some examples.
You can do that now with some pretty complicated ways.
from typing import generic and type var.
You can do this, is it covariant?
And what's its bound?
There's a lot going on here.
The new syntax would simplify this.
So now your class instead of deriving from generic of T, we just say it has bracket T for the type, much like you would say list of int, here we say class of T, and then whatever goes there, in this case it's a string that comes out here.
Maybe more relevant is a function.
So here's how you would do a generic function today.
Create the variable and then use it.
Here you just say it's a func of T, which takes some type and some type and whatever those two types are, it gives you another one back.
Like it takes a A which is an int and a B is an int, so you get an int or A which is a float and a B is a float and you get a float back.
Something along those lines, right?
So this is a really long, complex, and deep HEP.
It looks like a lot of work for the folks that put it together.
Check it out if you're interested.
And it's gonna be nice to have here, but at the same time, I don't think a ton of people are gonna be using it, maybe consuming it.
It's in the type system in 3.12 coming up soon, so put it on your radar.
|
|
show
|
3:10 |
Let's close out this chapter by talking about gradual typing.
Now, instead of me showing you a really complex example of this, I'd like to refer you to a talk done by Lukas Lange when he was at Instagram.
He's now the developer in residence at the PSF for Python, which is awesome.
But previously, he was at Instagram and talked about how Instagram used gradual typing and mypy to over time reduce the number of bugs that they had in their system.
And it sounds like everyone over there, maybe not everyone at first was on board with it, but by the time they started seeing real results, it was really paying off and everyone was into it.
So what is gradual typing?
Gradual typing is mostly about the tool mypy, which we're going to talk about later.
But it's the idea that if I define the types for one part of my code, it starts to trickle over into other parts.
We saw that with the type inference where we returned a person object, and I didn't say what its type was, then it started having auto-complete features for the type that was returned because that thing, that function that was defined had its type information, so that would trickle over to things using that function.
Then if we take that value and pass it to another, and if it has types, well, that can be checked.
Instead, well, that's a person, you expected a string.
Did you mean person.email?
All of those things start to build up over time.
So this is the design pattern where you start at small blocks of your code, you add some types, run type checkers as much as possible to give you information about well, so far what you know is that working because Python typing information is both ignored by the runtime and optional, you can use this process of building up the type information as you want to understand a part of your app better or get more type safety, you can just add that typing gradually.
So this is I think a 45 minute or 30 minute talk.
You can listen to Lukas talk about that.
That'd be great.
One of the important pictures that came about it out of it is how much type information, how much typed Python code did they have versus untyped Python code.
Here's the human written as opposed to generated percentages over time.
So it started out really, really low.
You can see mid April of 2017, it was like half a percent or 0% depending on the lines or the functions that you're talking about as a ratio.
For a couple of months, it was only a few percent.
And then people are like, okay, this is really awesome.
Let's add a bunch more where we might get some value and it jumped up to 15%.
And it's just continuing to climb.
Remember, they had something like a million lines of Python code that had zero type information whatsoever.
and they started bringing this type information into their code base.
So the people at Instagram got a huge benefit from Python typing.
You probably picked up a lot of reasons why already in this course.
But if you want to have a concrete example that you can bring to your team and talk about, go check out Lukasz's talk here.
See a lot of what he talks about some of the best practices and ways of working retroactively with a large codebase and applying types to it after the fact.
|
|
|
43:27 |
|
show
|
0:46 |
The last chapter, we dove into the syntax for Python typing, how to add types to our existing and new programs, and how it all fits together.
And in this chapter, we're going to take that knowledge and apply it.
We're gonna talk about a bunch of different frameworks that use Python type information as first class aspects of their frameworks.
We're talking FastAPI, Django, Ninja, Pydantic, bunch more.
So I'm gonna give you a few real world examples and some pretty realistic applications that you can take some of these frameworks and run with.
And I'll show you some more as well.
There's quite a wide variety.
And I think you're gonna enjoy applying what you learned in the previous chapter to some really cool frameworks
|
|
show
|
2:24 |
Call me a sucker for awesome lists, but I think they're pretty awesome.
There's an awesome Python typing awesome list that you can check out.
You can see the URL in the address bar here.
And this one is a collection of Python types, stubs, plugins, tools, and frameworks that work with types.
So much like the point of this chapter, here's an awesome list designed to aggregate all these frameworks and these tools.
Now, one thing you will see making a wide appearance, or a frequent appearance in these lists and these frameworks is Pydantic.
So Pydantic came along three or four years ago, and it really took the world by storm.
It's kind of like data classes if you've seen those, but it's all about using, deeply using Python type information to pull in unstructured data.
Usually, I think the genesis was around APIs, right?
right?
Like somebody submits a JSON document to your API, and you say it has to match all of these aspects, Pydantic will look at that, parse it, validate it and even do some automatic conversions when possible for you.
So Pydantic is an awesome, awesome framework, we're going to see a lot of things in this chapter are built upon it.
It's kind of used a lot, actually, Check this out.
Back in August, the Pydantic team announced that they just surpassed 1 billion downloads of Pydantic.
So congrats to the team.
And sure, it's not a popularity contest for whether or not you should use a framework.
But the more it's used, the more other things are going to work with it, support it, editors are going to have special understanding and tools to work with those frameworks and so on.
So having Pydantic be such a popular framework in the foundation for many of the other things we're gonna look at is generally a positive.
Pydantic is the foundation so much so that it has its own awesome list.
So awesome Pydantic, again, you can see that in the URL bar here and go explore it if you like.
So there's a lot of things based on Pydantic itself.
So check out this list if you wanna get a more full featured, more broad look than what we're gonna cover in this course.
but I pulled out what I think are the most important frameworks when we're talking about Pydantic that are based on Pydantic.
|
|
show
|
1:00 |
For our first hands-on exploration of one of these frameworks, we're going to start with the one that is the foundation of many others, which is Pydantic.
So you come over here to Pydantic, the website, and they have a couple of examples.
A huge thing to pay attention to is there's Pydantic v1 and there's Pydantic v2.
The team spent probably almost a year rewriting this and refactoring it in a way that we could keep our code running without much or any changes at all, but it's five to 50 times faster, which is pretty awesome.
So what we're gonna do is we're going to install Pydantic and use it to parse a few samples of data using Python type information.
We're gonna start with a simple one and then a way more complicated one, and I'll show you some cool tools to make that maybe even the easier option as we'll see.
So we're going to start working with Pydantic in our code, it's gonna be awesome.
This is one of my favorite frameworks.
I'm sure you're gonna love it too.
|
|
show
|
3:16 |
Now to get started, we need to install Pydantic into our virtual environment.
Remember at the beginning, I added this virtual environment called Python type hints.
If we open our terminal, you can see that it is active right there and which version of Python it is.
Now, of course I could just say pip install Pydantic, that's how it works, but I want to make sure that we have a repeatable and safe way, a stable way of adding requirements and dependencies 'cause as we go through this chapter and subsequent one, there's gonna be a lot of things we're going to be working with.
So I'm gonna use this thing called pip-tools.
Now let me give you a quick preview of how this works.
So typically, you might go to the PyProject toml, you might go like with flit or poetry.
I like to keep things more simple and straightforward, just the built-in stuff.
So I'm gonna use requirements.txt.
And over here, you would have something like Pydantic.
Now to be safe, you need to say Pydantic, you know, whatever version 2.0.4, whatever the current version is.
And you probably also need to figure out the dependencies of Pydantic.
Set those in here as well.
So instead of going through this process, I'm gonna use something called pip-tools.
And I'm gonna call this pip-tools.
It could be called anything, it doesn't matter.
But I like to call it pip-tools, and then it generates the, pip-tools will generate the requirements.txt.
Check this out.
Make sure we have this installed.
It does, if you don't have it, just pip install pip-tools.
So the command we wanna run is pip compile, giving it this input that says, here are top level dependencies, generate the requirements files pinned, and hey, if there's a new version of not just identic, but one of its dependencies that matches, that still satisfies the requirements, you know, like whatever Pydantic restrictions might have on them, then also upgrade those.
So I'll run this.
You do not need to do this 'cause the file you need will already have been created right here.
So notice it says we're gonna require Pydantic 241, which is apparently the current version because we set it here.
But look, we also need annotated types because Pydantic needs it.
And here's its pinned version.
The core is needed for Pydantic.
That's why here and this one is required by both of those and it's pinned and so on.
So the last thing to do is pip install -r requirements like so.
You'll see me sometimes write PIR, I have a alias for that, but you can type it out if you wish.
All right, now this warning should go away and if you go over here and say pip list, you'll see things like Pydantic, okay?
I wanna walk you through that Just so you know what the deal is with these files, again, you do not need to have pip-tools, you do not need to run pip compile, you just need to pip install -r requirements.txt.
But now you know where they come from, why it's here, so on.
This is a primo way to manage your requirements.
Wanna update them, just run that --upgrade and it'll figure out what the newest version of everything can be.
Highly recommend pip-tools.
|
|
show
|
8:57 |
Now that we have Pydantic installed and everything it needs to run, we're going to go over here and create a new thing.
I'll call this D1 for demo or example one here.
Let's just call this parsing with Pydantic.
Of course, we need the main method if dunder name is main.
I'll hit it with this little alias I got here.
And the first thing we've got to do is say import Pydantic.
Okay, so let's start with some data.
I'm going to go over to the weather.talkpython.fm website.
And this thing is actually using Pydantic behind the scenes.
So it's kind of pretty meta there, which is awesome.
And I'll pull up some data that we can parse.
And let's just take a real simple piece like, let's say this.
And I'll just go here and say data equals that.
And Python and JSON are so similar that I can just copy from one to the other, at least from JSON to Python.
The other way, if you've got single quotes for strings, JSON is, I don't know, doesn't like that.
It should, but it doesn't.
So here we have this data and let's put, let's change this feels like to location.
So this can be some other data type.
Let's say this is Portland, Oregon, and let's just change this to be temp range.
And I'm gonna put this in, I wouldn't really normally do, but just so we can show some stuff off, I'm gonna put this in as a list.
So lowest and then highest, not a great data model, but let's say we don't get a choice.
This is what we're given, right?
So imagine we have this data and we want to model it, right?
We want to model with strong typing, because right now the way I work with it I say get location and it's like, well, I don't know what you're going to get.
We're not real sure.
It looks like that might be a string.
If PyCharm is guessing that, I will be blown away now.
Not really.
It could be a string, it could be an and.
We don't really know.
What we want to do is work with this in a structured way and we're going to use Pydantic to do so.
So over the top, let's just call this a class.
We'll call it weather, forecast, whatever.
like that.
And it's going to have things that are the names here.
So we'll have like a temp location, these types of things.
Now, you can see Python doesn't love this, we could make it work by setting them to be none or something.
But let's not do that.
Right?
What we're going to do instead is we're going to say each one of these has a type.
So what is this type here?
It could be a float or presumably an int.
Remember your number tower or numeric tower we discussed.
So this would be a float here.
And as soon as we specify the type, the warning goes away.
So that's pretty awesome.
This is going to be a string.
This is going to be an int.
Let's say it has to be an int, it can't be a float.
This will also be an int.
And this is going to be a list of int.
All right.
So this is a perfect classroom modeling this data structure.
The types expressed here match this exactly.
But how do I go from this to that?
Do I go w equals weather, w dot temp equals, what do you want?
Float of data get of temp, like that.
You know, do the same for location and so on.
Nope.
No, we're going to use Pydantic.
So Pydantic says you're going to create a base class here, or derive from a base class, called BaseModel.
And by doing so, our weather-- notice there's a little warning or an error here.
It says humidity unfilled, location unfilled, pressure, all of those things.
Your editor may not have that.
Mine does, and I recommend that yours does as well, because I added Pydantic support to it.
So it understands what goes in here.
So one thing that we could do is we could specify temp equals whatever we get from the dictionary.
Location equals whatever we get from the dictionary.
But dictionaries have this cool way to explode them into keyword values.
So I could say temp, if I could spell it, equals data dot get of temp comma location equals data dot get of location, etc.
You don't want to type that that sucks, the bigger it gets, the worse it is.
So all you do is you say star star data, and that expands out to be temp equals value, location equals value pressure equals value, okay?
So if you see this, that means turn everything into keyword arguments that were top-level values in the dictionary.
Now watch this.
We print w and we run this.
Bam!
Look at that!
How awesome is that?
So, we said, ""Hey, Pydantic, figure out what goes here.
So the fact that we already got this, really cool.
It parses it, really cool.
But that's just the start.
Like, what if I said the location was a string and what if it was a instead?
Let's imagine instead that location was a tuple, yes indeed, a tuple of city state like that and we run it again.
Not only do we get the parsing, we get really good error checking.
It says, okay look, the problem, there's a problem with the location field when you tried to parse it.
In your class, you said it's a string, right?
be a string string type.
But we actually got was a tuple with this particular value.
It's not supposed to be like that.
So it parses it over and says, No, but this is it will put that back.
It also would say that this one that's supposed to be an int.
What's going happen if I put a one here, it got a number with a fractional part a float instead of an int.
But if I put point zero, if it actually is a float, fine, but it says, you know what, no data will be lost converting from 1019.0 to 1019.
Because they're basically the same number.
That's pretty cool.
So there's some flexibility in this, right?
The other thing is, what about this?
But if this was 60, I run it again, it says the temp range in the index one field.
So that is actually the second element in the list has a problem.
What is the problem?
We tried to make it an integer, but it got a string instead, and its value is 60.
But look, if it was like 64, as a string, it's still going to run and notice there's no quotes, that's not a string, that's an integer.
So it's tries to look at the data and go, well, it's not perfect, but could it be converted over?
If yes, it'll just do it.
If no, then it won't.
The final thing is it'll have data that's supposed to be there.
So like this crash, the required field of war, missing the location.
We could say that might be allowed.
Remember, to have something set to none or null, you have to have it explicitly stated as nullable.
So over here I could say, remember the way to do that is typing optional.
So optional, import that, and this is a string.
So it's either there or it's optional.
And in this case in Pydantic 2, not in 1, but in 2, you have to set this explicitly here.
Showing it again, now notice location is none, but that's not an error because it's not required, it's optional.
reason that I really, really, really like this and not string pipe none.
Yes, they mean the same thing.
This one says it's optional.
It doesn't have to be in the data.
This one, well, it could be these two values, which the implication is such and such, right?
Anyway, I've gone on and on about that.
You can make your own decisions, but I really love optional.
It's optional in the data.
It's not required.
So this is Pydantic.
The final thing is that we can have nested things like this could be a pressure reading if we had a class called pressure reading that had multiple values that was itself a Pydantic model.
So these can nest in a hierarchy in that way.
Pydantic is awesome.
Look how cool it is working with all of this data here.
Excellent.
|
|
show
|
6:03 |
Let's look at a more realistic example.
So remember the weather data we got here, I just grabbed that little bit and said, here's a JSON piece made up of primary data types, strings and numbers.
But this whole thing is a little more complicated.
For example, weather is itself a class made up of different things.
Same for the wind.
Units is just a fundamental type, right?
And turns out in order to go back, I think we could go over here, just like before and go, okay, well, the data looks like such and such.
And so we're gonna start working out the classes.
It turns out, even in this simple example, there are seven different classes we have to create in order to fully parse and validate this data.
So that is possible manually, but let's not do it that way.
Let's copy it and I'll show you something super cool.
First of all, let's get a copy of this.
I'll call it ""Parsing Weather"" and just run it real quick so it's the next thing to run.
Excellent.
And instead of this data, I'm going to take that data over here and give me just a sec to format this better.
There it is.
Python obviously doesn't care if it's formatted in a pretty way or not, but just so we as humans can read this without going crazy.
And you can now clearly see these are the different sections that have basically what will need to be nested classes.
All right, so again, we could take this data and create this type of model ourselves, but we're not going to do that.
Instead, I'm going to introduce you to the JSON to Pydantic converter.
Now check this out.
It has a very simple JSON example with foo and bar baz.
I personally hate those meaningless vacuous examples, but here they are nonetheless.
It's still cool.
Here's some JSON and look what it does.
It goes in, it says, all right, we're gonna create typed identic models against that data, kind of like a human would, and it's pretty good.
So it knows that that's an integer and that that's a string, right?
Check this out.
Let's put that data we just had.
Didn't even pretty print it, don't care.
still valid JSON.
And look, it gave us the weather which is this nested bit here at the description and a category.
Those are strings that give us the wind which is a float and an int.
So on and then this model down here is the top level thing which has a weather wind forecast class and so on.
So let's just copy this.
We don't need to take the from the future because we don't live in the past.
We're not working on crummy old Python.
We're working on modern Python, so we can just use this.
Now that's a lot of code, and I don't want it all in one file.
So I'm going to go over here and add a new Python file called weather models.
pasted format it.
Now this thing called model is really what we work with.
So it's called a say weather forecast like that.
Now I'm going to go over here and use that.
So I could say from weather models, import this, oops, not this, a weather weather forecast and it looks like it might be working.
But this is only working because PyCharm is setting this as the working directory.
So if you don't have PyCharm, you're going to make sure you need to make sure that this is the working directory so that when Python says look at the module called weather models, it's right there with it.
Okay, we can be a little extra safe and PyCharm.
We unselect, just go to the directory, say mark directory as sources root and it turns blue.
You want to make sure that you're doing that.
And then down here.
Just like before, it doesn't matter how complicated this gets.
We can just say weather forecast now and watch this.
Oh, yes.
The weather part is this weather class, which is exactly as you expect.
The wind is this wind class, which is as you would expect the the units as a string.
Look at this, it parsed the whole thing perfectly.
And again, if some bit of data up here like this was a, this was a 64, doesn't matter.
You know, it says, hey, down inside of this forecast subsection, this is supposed to be an int, so let's try to parse it.
We can, it's good to go.
How awesome is that?
So here's how we use Pydantic to parse and validate data.
again, if this is missing, we try it.
Even in this complex example, we get fields are required.
And if it's the wrong type, you get type validation based on Python typing.
We said, I is contained within this, what did it call it?
This forecast class and down here's the high and that is an int.
So it used the for class type information in the high being a type of int to make sure that everything is hanging together.
Awesome, awesome stuff.
I love pydantic.
It's such a great way to work with code.
And let's just show you now how simple it is to use this quote complicated data.
Right now it's w dot.
Whether I'm not loving the name, but that's okay.
Description and...
Right now it's whatever the weather type is, cloudy, sunny, whatever, and the temperature is something Fahrenheit.
Look at that.
Right now it's broken clouds and 60 degrees Fahrenheit.
I'm recording this video right now.
Well, five minutes ago when I copied that JSON data out of the API at least.
Pretty awesome, right?
|
|
show
|
4:57 |
Continuing our exploration of frameworks and tools and libraries built on Python typing, I want to look at three different web frameworks, two of them built on Pydantic, one that has optional Pydantic support.
So first of all, we have FastAPI.
This is an incredibly popular framework.
Notice the 63,000 GitHub stars at the moment.
That puts it right on par with Flask and Django, even though it's only a fifth as old as those frameworks, something along those lines.
So really, really popular framework.
This is based on pedantic.
It makes deep use of type information or also a gold sponsor of them.
So that's awesome.
Now, if you look down here, you'll see a quick example.
So for example, we could create something that responds to a get request to forward slash and it just returns this Hello World.
But we can come over here and say you could also get an item passing the item ID which goes here and it's an integer.
So this is pretty awesome.
And when you say slash item slash something, just like with Pydantic, if everything on the web is a string, so it looks and tries to parse it to an integer.
And if it can't be, it's gonna not even let this function run.
It'll say there's an error because it's deeply based on this typing.
Again, we have a query string.
So question mark Q equals something, And that could either be a string or none a union here.
So I'd rather again, say optional string, but you do you.
And here we get that data passed in.
See if there's a slightly more complicated example, you get awesome documentation.
We're gonna look this in more details.
Yeah, here's the final interesting thing is, we could have a Pydantic model that has rich information here, this item.
And we can say this actually takes an item right there to be updated.
And so this is gonna be either a put or a post, something where a rich body of data is posted to it, like a JSON post, and it'll actually do that Pydantic parsing before it even lets your code run, all the validation, everything we saw about Pydantic.
Lots of fun stuff about FastAPI.
We'll look an example at that, like I said.
If you don't want to pick a totally new framework, but you love Django, check out Django Ninja.
Yeah!
Fast to learn, fast to code, fast to run, very awesome.
Based on Pydantic and both FastAPI and Django Ninja have async support as well.
So this is kind of the Django equivalent.
And here you have another example.
Gonna API get int int, convert some to ints automatically.
Again, here's an item for a rich type.
And here's some operation that's being passed in, automatically parsed.
This is a schema, but I'm pretty sure that schema is going to derive from a pedantic type as well.
So quite popular, not as popular as FastAPI 'cause that's kind of meteoric, but here you go.
Final one to look at, this one's pretty new, coming on strong, it has, let's see, 3000 GitHub stars, but it's quite new, called Litestar, Litestar is pretty excellent.
We come down here, we'll see some examples.
Let's go further down.
You can have method-based views or class-based views.
Here's one that's a class just for the heck of it.
And notice this one has some data pass here and it's called a DTO, kind of like the model in FastAPI.
And this data is going to be some kind of rich type, very much similar to a pedantic, the item thing we just saw, and quite neat.
Again, supports async like you would expect.
that are all about the optional return type as well.
Somewhere they have a user, yeah, here we go.
This post to create a user, the data for the user is passed in, and then the one that's created with, say, the primary key and created date set is returned to the user as well.
So, awesome, awesome framework.
Definitely interested in checking this one out more.
I haven't done too much with the second two, but FastAPI I've done a lot with.
Recommend pretty much all three of them, okay?
Very cool and you can see they each have their own slight variation.
For example, if we go over here and search for Pydantic.
This has support for data classes, typedex and both versions of Pydantic, adders and message spec, which is actually a really cool alternative to Pydantic, I suppose.
Maybe a good way to put it, but yeah, so they have their own way of working with typing, but they all have typing at the core of how they work.
So give these a look.
You can use your type knowledge that you have now to really build some awesome web APIs in surprisingly little amount of code.
|
|
show
|
3:40 |
We looked at the web layer.
Let's look at the database layer.
So I'll give you two examples here that are excellent We'll talk about beanie and SQL models.
So if you love MongoDB, I love MongoDB so much It's such an awesome framework Talk Python Training and the podcast are all powered by Mongo Love it.
It's been awesome and Beanie is a really cool object document mapper not an ORM because there's not a relational Well, there's just documents and so similar acronym but not the same an asynchronous Python ODM for MongoDB based on Pydantic.
So all the queries, all the modeling of your data you do, you do in Pydantic.
It's excellent.
Check out an example here.
Look at this should look super familiar.
Here's a category derived from Pydantic dot base model.
And here's a top level entry into a MongoDB collection called a document.
And you can say, look, just like you would do with Pydantic, here's something that has a name, it has an optional description, it has a index field, which is the price, and it has a nested part of its document, as you would in a document database like MongoDB category.
And then to do a query, we'll skip around, you can just go product.find1 where the price is product.price is less than 10.
Amazing.
It's all async, so everything has an await, but super, super cool.
So if you work with MongoDB, and you wanna use Type-B and Pydantic and all those things, this is a great one.
It's what's powering what you're watching the course on right now, actually.
If you're like, no way am I doing NoSQL document databases, I'm a Postgres person or some other relational person.
Well, then you should find your way over to SQL model.
Built by Sebastian Ramirez, same guy behind FastAPI and Typer, by the way, which we'll talk about next.
This is based on top of SQLAlchemy.
Basically think of this as SQLAlchemy plus Pydantic and Async.
Base SQL model is based on Python type annotations and powered by Pydantic and SQLAlchemy.
Let's go see an example.
So here we go.
It's a class called hero, derives from SQL model, which is probably also a Pydantic model.
And does it map over to a table?
Yes, it does.
And look at this, we have an ID, which is field information, default is none, primary key, all these things, right?
Name, secret, down here, just create them.
And from there, it's pretty much standard SQLAlchemy.
So for example, with session from the engine, you'll add, add, add, and then commit.
It'll do that insert into the database for those new records.
Here's your select.
This is the new SQLAlchemy2 syntax.
Honestly, kind of yearn for the simple SQLAlchemy1 ORM style but c'est la vie.
So select hero, where hero name equals this, and you're gonna go and execute that statement.
This one's not asynchronous, but you could create an async session and then you would await this, right?
you get the option with SQLAlchemy.
Anyway, the core takeaway is identic and types at the core.
And I guess another one that's pretty neat here is this is based on SQLAlchemy, which has been around and tested heavily.
It's been around forever.
So it's super stable.
So this is a pretty neat example as well.
10,000 stars in terms of its popularity as well.
|
|
show
|
2:08 |
This last one just to show you it's not just databases and web frameworks What we're talking about is typer.
Typer is a fairly popular 12,000 get up stars Framework.
Yes again.
It's a built by Sebastian Ramirez FastAPI FastAPI fame and the idea is you build great CLIs Based on Python type hints.
All right.
Well, let's see what that looks like.
What does that mean?
So here we want to have a couple of sub commands for our app, but we can say app.command hello, right?
Give it this decorator and it has an argument name, which is a string and goodbye name.
And notice a formal bool with a default of false.
Okay, so I can run my code space sub command, either hello or goodbye, and I could pass a name.
And this bools, you'll see these turn into just flags flags that either exist or don't exist.
So if you scroll down a ways, there's a bunch of stuff going on, but the most interesting one is probably right here.
It says, how do you run this?
So if I just run my program, however you do that, it could be an entry point in a package or explicitly with Python, give it the sub command goodbye, and here you can ask for help.
It says, okay, here's your options.
You're gonna have to pass in name, which is text 'cause that's a string.
It's required because it's a string, not an optional string.
We could also pass in formal or no, no, dash, dash, no formal.
Remember that's the Boolean.
So just its presence of this --formal is true because the default is false.
You don't have to say no formal.
You just leave it alone.
All right.
Isn't that cool?
And they have a little example of it running.
Hello, Camelia.
Goodbye, Camelia.
And if you say --formal, it says goodbye, Miss Camelia.
Have a good day.
So pretty cool framework and you can see, like for example, the Boolean behavior coming right from Python type information there.
Good stuff.
I don't build many CLI apps like this, but if I do, I'd certainly give Typer a look.
|
|
show
|
3:43 |
For this last example, let's take some of these things and put them together.
Pydantic, Beanie, FastAPI into a somewhat realistic web app that I'll give you to work with and we can play with some of the things going on there.
All right, now this is a complex application that I built up in another course, the one on Async MongoDB.
That would take an hour to build at least.
So we're not going to do that.
We're just going to start from having the code here.
I'm going to call that web example.
And in this, we need to set this as a sources route as well.
And we're going to run this.
But in order to run this, we have to update our dependencies.
It is based on things like FastAPI, and UVicorn and others.
So let's go down to our pip-tools.
Again, you don't need to do this, just need to run the requirements.txt.
But I'm doing it for you.
And you can use it to update things along the way.
So this app uses passlib, I'll put this up here like that.
It uses passlib and the argon2 password hash for its account management.
It uses FastAPI for the web framework.
It uses beanie to talk to MongoDB.
More about how to work with that in a second.
It's going to use Jinja2 to show HTML and uvicorn to run.
So let's go down here and I'll do pip compile, pip requirements.pip, or requirements.pip tools upgrade.
And now this has been updated and you can see there's a lot more stuff here like argon2c, ffi, beanie, and others.
So we got a pip install -r, a bunch of things.
Now, if we go over here, all those errors should go away and we should be able to run this code.
Let's try it, see what happens.
Yes, look at that.
We're talking to our MongoDB server on localhost for a database called PyPI.
Let's just click here and see what we get.
Now, this data, this example is an API that lets you get information about a subset of PyPI data.
That is the data out of the Python package index.
I took some of the popular, their first top 1,000 or top 5,000 packages and put them into MongoDB in a way that we could work with.
So if we wanna go over here and get the five most recent packages, here they are.
And by the way, if you're working with this kind of data, Firefox is such a better way to do it.
Why you ask?
Well, look at that view.
You have the raw view, which you can pretty print or you can look at this straight away.
And I just wish some of the other browsers like Vivaldi, the one I really like, had a better view of it.
But we can come over here and it lets you ask for most recently updated packages.
Here we have Beanie, Boto and Pydantic.
And if we go back here, You can see I want the details for say, FastAPI, and there's pretty much everything you would see in terms of releases and description, read me type stuff on the page.
Okay, so there's also stats.
I guess here's how many packages I was able to load in that I thought were relevant, popular ones.
So very, very cool.
This is all done and powered with, as I said, FastAPI, Pydantic, and Beanie.
|
|
show
|
4:26 |
So you saw it in action.
Let's just look at some of the code here.
There's not too much going on for FastAPI.
We basically have a package, API section of routes and a stats over here.
So these are over an API and we have a package and stats.
So right away, you can see the response model is a stats model, which is a Pydantic model with these three things.
So that's pretty cool.
we've we've got this right slash API slash stats as you saw right here with data that looks like this out of our database out of somewhere and we don't really see exactly where it's from yet we will in a moment but not only does this stats model control what data is sent back and what its format is right here we're setting up that model and returning in it, but if we go over here and we say docs, check this out, go to the stats, it comes over here, it says it's a void sort of thing, but here's what a response looks like.
It has a schema with these three things that implicitly are specified as numbers.
So that documentation is also driven by Python type hints and this model right there again, which is just a standard Pydantic model.
Okay, super, super cool.
So that's the FastAPI layer.
We're talking to beanie, which requires us to do which talks to Mongo, which requires us to do async code.
So this is an async API endpoint and a weight here.
These are pretty simple, we'll look at them anyway.
So how do we go and say, well, returns an integer, obviously we say that.
How do we do this?
We call package.count.
That is, we look at what is package.
Package is a beanie document with Python, type hints, and even overriding things like how do we create the default values?
Well, you call the function datetime.now when you need a value if it's not specified.
There's some optional ones and so on, right?
So here's our beanie document, which is really a pedantic document.
And then we just call count.
And we await that because it's async, talks to MongoDB, pulls that information back.
But down here a little bit more, we can do a more interesting query, you can say I want to get the most recently updated, maybe this should say int.
We want to get a count, which is an integer, because we specified by five there, it assumes that's always an integer, but right by default, it's five, but we could pass in however many and we get a list of package.
So how do we do that we go over here, we go to the class, this is the beanie pedantic model.
And we can say find find all find one find many, there's a lot of different finds, but find all that means just give me everything.
But then let's sort them and do a limit on how many there are.
So if we sort them, so the very newest is first and the second newest is second.
And then we say just show me five.
Well that gives you the five newest ones because everything after that is older call to list.
We await that call, which does the async stuff, turns it into a list of package objects again Pydantic models.
And those get used over here.
So for example, this is the packages recent or you saw get the count passed in by fast API converts that to an int instead of a string, make sure it's there.
And we call this function get the packages, we come up with this transformation for the return model, because we don't want to show all the data, it turns out there's way, way, way too much data.
If you pass all the releases, all that over, so we kind of transform it to this response Pydantic model that we're going to return, set it up here so it drives the documentation and also controls what can be returned by FastAPI itself and boom, off it goes.
So there it is, Pydantic, Beanie, FastAPI, all working together, all using this Python type information in many, many interesting ways.
|
|
show
|
2:07 |
If you want to run this example yourself, you have to A, have a MongoDB server that you can talk to.
MongoDB is free and open source, so it's easy to set up, but you do have to have it set up and running.
And then you've got to add the data.
It'll actually run with no database set up or installed, but it'll have zero interesting things.
There'll be zeros all for the user and other stats.
There'll be an empty list for the recent packages and so on.
So I've added this import data markdown and let's look at it in TypePort.
It looks nicer over there than instead of PyCharm.
Over here you can see this course uses custom data from PyPI sources over on pypi.org.
And it says, here's what you gotta do.
You gotta install MongoDB.
Here's the steps from the MongoDB people themselves based on your platform and your OS and so on, as well as the management tools because the command you're gonna need to run comes from there.
And then you download the data here.
This is the top 5,000 packages at the time when I created them and their data that I got from the IPI.org API.
And then here's the steps that you go to do them.
And then you should see just an output that says, restoring a bunch of tables and you got them.
Now, I don't believe I added the indexes for that one, but I think over here, let's see.
Yeah, when you run the code the first time, it'll actually go and create the indexes.
So it should run nice and fast.
But the most important thing is, if you actually want to run this example, you have to have MongoDB installed and you have to want anything meaningful to happen, you have to import the data.
And so from here on out are the steps you got to do to get that data downloaded and imported.
Completely optional, right?
You don't need to actually run this code to appreciate how typing goes from completely the very top to the very bottom of this web application.
But if you do want to play with it, if you do want to run it, these are the steps import_data.markdown
|
|
|
31:43 |
|
show
|
0:41 |
We've seen the frameworks that we can use that leverage Python types to build our applications, like we could build a new API with FastAPI or a new data access layer with Beanie.
But now we're also going to talk about some tools that you can apply to analyze the code that you're writing, regardless of whether it uses one of those frameworks.
If it has Python type information in it, you can apply these tools for things like static like analysis, making your editor better, making your code even run faster, or do runtime type verification.
So all of these things are gonna be awesome, and that's what we're gonna focus on in this chapter.
So let's get to it.
|
|
show
|
2:26 |
Now the first one you've already seen in a sense, and that's our code editors.
I strongly encourage you to use one of the modern editors that understands Python type information, as well as autocomplete refactoring like 21st century, the sort of thing, you know, and primarily, you know, the, the 2023, well, the results for the Python Community survey on how people are using Python that comes out in 2023, even though it was actually the survey from last year.
That said basically what editor using and the editors were VS Code and PyCharm and pretty much nothing else, right?
It was like very high numbers there and then three, 4% for anything else.
So most of you are doing this, most of you are using these types of editors.
So obviously in this course we're using PyCharm, but VS Code has a bunch of functionality as well.
But your editors are super, super important for this.
And you've seen a ton of that, right?
We've written a lot of code and we get little errors and even sometimes some auto fix correction options to work on what's wrong with our code with regard to typing.
But I got one more trick up my sleeve with regards to the editor to show you how you you can do more than just, oh, I was scrolling through and I saw a squiggly line, so I worked on it.
And that's full project inspection in PyCharm.
So here's the final example code that before I created it, the starting code that I wanted to sort of use to write this course.
And you can go through and go to the very top level with all of the code, all the different files and all the different pieces, and you can say, I wanna inspect the entire project.
Now by default, what this is gonna do is it's gonna run all the rules, like do you have your commas set correctly?
And are you have any unused imports?
Well, we can tame that back, turn that down some and sure you can run all that inspection if you like, but notice here it says inspection profile type checking only.
And so this is something that I came up with it is a limited set of these inspections that we can run against everything.
So regardless of if you have the file open, you can get basically whole project analysis of the type information.
So we're gonna do that next.
|
|
show
|
6:11 |
Here we are in our source code from what we've got so far.
Notice I have a tooling chapter that we're going to be working with.
And I'll go ahead and mark that as sources root here.
And we're going to be working with this.
We're not doing it yet, but that's to come.
The first thing we want to do is just inspect this.
And now keep in mind, some of these may have errors.
I put in here on purpose to show you here's a thing where the error is being caught.
So if you see errors, that doesn't mean something's wrong, But the goal is to say, I want to, in this editor, we've got many, many files.
In real projects, you've got hundreds of files, hundreds of Python files that are interconnected through implicit links via imports and that kind of thing.
We're going to use PyCharm to say, okay, tell me about that.
And notice here, we've got like our exercises and potential solutions and stuff like that.
So I'm just gonna run it on this folder, but you could run it just one step higher if you'd like.
So what you can do is you can right click over here and you can say inspect code, or you can hit the hotkey to do that if you like, but I'll just say inspect code.
It says, like I said, you can do a whole project.
Just because this is kind of weird, I'm gonna do just the code project.
And if I hit this, it's gonna tell me all the errors no matter what, like even PEP 8 violations and the line's too long.
And sure, you may care about that again, but with regard to typing, you don't.
Okay, so let's configure this.
Come over here and say, we can configure our inspections.
So notice we have our default, right?
You don't wanna, don't mess with your default, okay?
So what I wanna do is I want to go and say I want to duplicate this.
First of, yeah, we'll say duplicate.
And notice it's being stored in the project, not in the IDE.
The ID is a machine setting.
The project is something that could potentially be committed back to GitHub.
So I'll call this project type checking only.
Now that's not true because it's checking CSS stuff and so on.
So let's collapse these like this and turn a bunch of these off.
And this is why you don't want to do this on the default one.
So everything is off.
And now what I'm gonna do is I'm gonna go to Python.
Well, first I'm gonna search for type.
Now this is a bit of a heuristic.
I think what you should really do, you wanna be very serious about that, is go through and read each one of these here and say, okay, does that seem like something that applies to typing?
But I'm gonna take a shortcut here and just say type, and we'll go down to the Python one.
Now we have these.
Now let's see if this will work.
If I check that, and then I clear it, We have all the Python stuff selected.
Now it worked perfectly.
So look, here we've got incorrect CLI syntax, incompatible stub, that's typed, incorrect type, sure, that's a good one.
Invalid definition of typing named tuple, we'll go and throw that in there.
We've got protocol usages, haven't talked about protocols yet, coming up soon.
Invalid use of class vars.
This, I mean, this kind of looks like type, as far as kind of a type thing.
But even if you just left it with just what you got from the types, I think that's good.
So these are all looking pretty good to me.
So I'm just gonna go with that and leave it.
I'll hit OK.
Now notice we're going to do type checking only, which is stored in the project against the directory code and all the sub directory things.
Hit it.
So awesome.
Here's the report section.
We got 20 things going on.
Let's look at this one.
Remember I told you some are supposed to be errors and notice right here that it says error in editor right there.
Awesome, right?
It is an error.
The editor does say it's an error.
If I hover over it, does it tell me?
Yes, expected and got a string.
But I didn't have that file up and I have zero files open.
It's just one of many files and it went through and inspected all of them.
So here's another one.
Many of these are in this P1 variables 'cause I was showing you like that's supposed to be an error, another error, right below another error, right?
I told you those are like there to kind of highlight that the error is being caught.
All right, so close that up.
And what about collections?
Again, error, wrong key type, error, not a string.
This is an error 'cause it's supposed to be a string and it's not, same thing there.
And you can come over here, you can even suppress, you know, suppress these with, as you could normally do like up here, I can hit enter and say, suppress first statement.
And it'll just add a comment that'll tell the type checker to not do that.
So you've seen already that when you have a file open, we get these types of things that are awesome, but it's super common.
You open up the project for your day.
It's got 500 files in it, Python files, perfect.
One of your colleagues makes a change.
They're using Vim, they don't have a type checker, they don't care about this stuff, they just check it in.
You don't even open that file, you're carrying on.
What do you do?
How do you know that there might be a problem that was introduced?
You certainly don't wanna open up all these files.
So here you go.
Inspect code, type checking only against the relevant section, go.
And then it's off to the races.
So editors, editors are awesome.
And this total project inspection, It's like kind of a next level thing that you should definitely know about.
If it's not something I run frequently, but every now and then I'll be like, well, how's the whole thing look.
Right.
But usually, usually I'm just getting it as I go.
Cause I'm not writing too much new stuff or not, not opening it.
If it is new from some other place, but this is a really cool feature in a way to take your type checking to the next level in your editors.
|
|
show
|
1:32 |
What do you do if your editor doesn't have this whole project inspection?
More importantly, what do you do if you want to put it into something like continuous integration where it happens on every commit and every PR and that has nothing to do with an editor?
Well, there's this program, this tool called mypy that is officially under the Python organization.
organization here, you can see github.com/python/mypy, and it's an optional static typing checker for Python.
So we talked about the gradual typing, and if the type information is there, you can see if it links up, but if it's not there, then hey, don't worry about it.
After all, it's not like TypeScript.
So this is one of the tools that makes that gradual optional typing possible.
So we're going to apply mypy to some code in our project in just a second.
But just to kind of round out that discussion before we do, there's also pyre, which is from MetaFacebook that is a high performance type checker for Python 3.
And there's also pyright, which is a static type checker for Python, this time from Microsoft.
So check out all three of these, see which one makes you the most happy makes you feel like is right for your project, but mypy being officially under Python, that gives it a little bit of a hat tip in my book anyway.
So we're going to use that one for our example.
|
|
show
|
5:14 |
Here we are back in our code.
And I'm just going to copy in a couple of things here so we don't have to write them because they're not super relevant.
First of all, you can configure my pie through this ini file, any file, you can set certain settings like this.
So when you run it, it's going to look at this config file and decide what it should do what it should skip what it shouldn't skip.
That's nice.
We have this basic program here that just does stuff like has a string and works with it.
Sometimes incorrectly, sometimes correctly.
It has a constant under the final category and sometimes it treats it right and sometimes it abuses it by trying to change the constant.
Notice before I think final wasn't caught as an error in the earlier videos I did, but somehow now I PyCharm updated itself along the way.
now PyCharm also catches this lack of a constant.
I actually submitted this to them as a bug and wow, if they fixed it that quick, well done PyCharm team.
So really, really cool.
But yeah, that's a little bit of a bonus, but let's assume we don't have an editor like this.
Most importantly, around continuous integration and GitHub actions and those kinds of things, not so much like I just don't want to use this editor, but there are really valid locations like CI/CD that you just don't have editors in the mix.
So we're gonna use mypy, and to do that, we're going to need to install mypy.
So mypy, like that.
Remember, you don't have to do this step to generate the TXT file, you just have to install them.
So I'll say pip compile.
There we go, requirements, pip-tools, upgrade, might as well upgrade all the other things if there's like, say, a new beanie that just came out.
And here's the thing you gotta do, pip install -r requirements.
Sure enough, we got our mypy, but Pydantic also got updated and so did FastAPI.
Good stuff.
So now if we go and open this in the terminal and we just type mypy, the very, very first run of it, it's a little bit slow or whatever.
I think that's just because maybe the Apple Silicon thing.
Let's try again.
Yeah, notice it's super, super fast here.
So what we do is we say mypy test, you can hit the T, we're gonna run it against this.
You can see that there's three errors that PyCharm knows about.
List none and str z is a constant.
Let's see what we get.
Awesome, mypy found all of those problems.
So there's an incompatible return type.
We got none right there on line 10 when we were supposed to be returning list, but we didn't specify, so it could be list of any.
Right, so the type of error is invalid return value.
And sure enough, that's true, and PyCharm is also catching it.
Down here on line 13, this print a number takes an int, but if you look at what x is, it's a string.
So those don't go together, right?
Here's a little error.
And down here it says, print a number has an incompatible type being passed to it, string, where it's supposed to be an integer, that's an arg type error.
And finally, z, I wish this was phrased differently, z is a constant and should not be changed.
Well, okay, cannot assign to vinyl name z, that means it's a constant, don't change it.
Python, its language doesn't have support for a true constant that cannot be changed, but the type checker as well as your editor can.
So what you can do is you can set this up as one of the steps in your continuous integration, somebody checks in something that has an invalid type.
This is kind of like a compile step.
Now, we also have, we could say like this, we could say def other, go down here and just go other x.
If something about this is, let's see, like upper, call upper, and put a z here.
Wow, look at that, PyCharm did find it.
But there's nothing about this description here.
Let's see if mypy.
No, mypy doesn't actually find it.
So from a mypy perspective, only when the type information is present does it try to check.
PyCharm on the other hand is like, Hey, I got your back.
This isn't gonna work.
I know ints don't have an upper.
Yeah, so mypy is super easy to use, super easy to set up.
I basically pip installed it.
I dropped in an ini config file so I can control some of the settings if I want.
And then I just said, mypy the name, I could do like this, I think.
Yep, just apply it to the directory here and say, get all of them.
I'm sure you can do it to recursively as well.
So this is a really awesome tool to have for when you're working with things like continuous integration, get pre-commit hooks, you name it, or you could even just run it yourself and just use it as a tool that's kind of like PyCharm, But not PyCharm.
|
|
show
|
1:55 |
So far, throughout almost all of this course, what we've seen is that Python type hints are gradual and optional.
The exception being Pydantic, basically, that says if something's wrong, we're going to use that information at run time.
But generally what you see when we add type hints, that's for mypy, that's for the editor, that's for the person reading the code, not so much for enforcing.
In a static language, remember we saw several examples of those at the start, if we compile C#, C++, Swift, Dart, all of those, they check, make sure that things go together before it'll even build the program, and unless the types line up correctly every time, it won't even build the program, which means you can't run it at all.
Python typing is not that way.
Python typing allows you to work through these issues and it gives you a sense of what it should do.
But if it doesn't do that, you know, we gave it a good shot.
What if you want to have that level of verifiability?
Maybe not at compile time in the closest you'll get with that, I guess is my pie, but at runtime to make sure that things at runtime always work and always hold together kind of like a compiled language.
Meet the bear, bear type, the bear metal type checker.
So bear type is a pretty interesting project.
It's really easy to use.
You just annotate your code with typements as you have been throughout this course and then you add a decorator and those hints become runtime type enforcements.
Now, Pydantic had this thing called validators and they had a runtime type of validator thing.
I'm not sure if it ever made it out of beta.
out of beta, it was in beta for a long time.
So this one is super fast and super interesting.
Let's dive into it.
|
|
show
|
6:43 |
In order to use bear type, we're going to need to have it installed in our environment, aren't we?
And so I'm going to go through this.
And we'll add it.
Let's add it down here in bear type.
Now again, once again, you do not need to run the pip-tools command.
Just pip install -r requirements one time because it's going to be all preloaded when you get started.
I passed the update flag.
So it's possible it's going to make some updates as well.
But the most important thing is in here we have bare type.
And we're going to pip install.
Our bare type, excellent.
So over here I wanna do two things for examples for working with bare type.
I wanna do first just a simple introduction of how does it work?
What kind of information will it catch?
What won't it catch?
And then I wanna address a really big issue that many of you probably are already already considering is, what about the speed?
Sure, it's cool to verify all of our code is hanging together, but if it's really slow, then we don't want it.
So we're gonna come back and we're gonna do some timed type of, not quite profiling, because that somewhat affects the behavior, but some timing of some code that we'll write here.
So simple one first, and then one that addresses performance separately.
So let's call this runtime example and we'll say from bear type, import bear type.
One is the module, the other one is a decorator.
So let's just do the main thing here, we'll say def main.
And let's write a really, really simple function just so you can see what this is going to be like.
to put any type hints on the beginning part here.
So we'll say equals input, enter the first number, second number, and then let's just do some cool math here.
Let's go and say...
Now we got to write our little math on numbers function called this.
And this one we're gonna specify that it takes, it's specified takes an int, but remember our number tower, let's say it could take an int or a float.
So we're gonna do that.
And then what is the, this fancy formula?
Let's go X times X plus three Y.
That's good.
Now, in order to do runtime, oh, that's supposed to be a Y, in order to do runtime checking, all we have to do, go over here and say @BearType, like this.
Now, PyCharm is helping us out of here.
It's like, ""Ugh, input actually returns a string, not a number.
I mean, yes, you asked for a number, but that's not really a number.
But imagine, imagine we didn't know.
Let's suppose even we did this and we said, you know what, that is a number.
Good job, Michael.
And then we came over here and said, this is a region, hide this, -n region, like that.
Oh, even that has got a little mark on it.
Let's just say you can't see it, right?
Like it looks okay, right?
From this point onward, it looks okay.
And down here, this, sure, you say it takes a float.
Everything's good, so you should be able to do these mathematical operations on a float.
Yes, indeed.
But is it really gonna hold together?
Hint, no.
Let's run it and find out.
The first number is 42, and the second one is 100.
Look at this, whack.
We have the bear type, the bear has roared, given us a bare type call hint parameter violation, math on numbers, this dunder main, that just means it's really runtime_example, but it's just like when you run it, right?
That's all in this convention right there.
The parameter x equals quote 42 violates the thing that it should be a float because string 42 is not an instance of float.
Runtime, folks, runtime.
How cool is that?
So, you know, how do we fix this?
Well, we just go and do some sweet parsing up here.
Run it again.
Don't really need those anymore, but run it again.
Do what I tried before in 100.
The result is 2,064.0.
Excellent, right?
Excellent.
So here we have it.
Pretty awesome.
It really only works on fundamental types or it doesn't really work on super nested things like a list of set of float.
It doesn't do that verification.
Just looks more at the top level types.
Let's do one more thing real quick here.
We'll say math on points.
Not super interesting because it's a one-dimensional point, but we'll call this x.value just to see whether it'll check it.
Up here, I'll do one more.
So P1 equals point of X.
P2 equals.
And let's try it again.
We'll say 1 and 2.
Awesome.
That works.
Of course it runs.
But what if we try to put a Y here?
1, 2.
Excellent.
Excellent.
So you can see even for our own types here, it's saying, ""Look, the float value 2.0 is not an instance of the point class."" But what it doesn't do is it doesn't look within collections.
So like I said, I have a dictionary that has a list of things and you somehow express that in typing, it's not going to verify that.
But it does check, you know, top level types like this, and it's awesome.
|
|
show
|
7:01 |
We just saw how to use bear type and it's pretty cool that we just put this decorator in here and no matter how our code is used, it can't be used incorrectly.
However, how much of a performance hit is there?
Turns out it's pretty low.
The bear type page said very minimal.
Well, one person's minimal is another person's really important.
So let me add a little example here.
First thing I'm going to throw in is this thing I called timed and it's just a decorator that I wrote that when you throw a function on it, it runs the function and then prints out how long that took to in terms of milliseconds or the 10th of a millisecond.
So that's cool.
And then over here we have a runtime speed.
This one has no bear type on it at all, but you can see what it's doing is it's calling just doing a bunch of like useless busy work really.
What it does is it's given a list of counts, basically, and it says for each one of those, we're going to go through and for that many times, we're going to call these two functions.
This one's going to do some math-like things with numbers.
This one's going to do math-like things with strings, so build up huge, pretty huge actually, strings.
For however many times, it's going to come up with 10,000 strings each time it's growing.
is pretty computational.
The idea is like it simulates doing a web API or a database call or an external API or something that there's a little bit of work and it's not just like, hey, how fast is a for loop run in each language?
Well, that's not really what we care about that often.
Sometimes, but most of the time, no.
So let's just run this real quick here.
Remember, no bear type here.
we run it, it takes one second and 118 milliseconds.
Keep in mind, I'm doing a recording and things like that.
So it's not exactly just a system at rest, right?
I'll run it a few times.
Let's see what kind of numbers, but 1.1 seconds is looking, looks like pretty stable there, doesn't it?
That's cool.
Okay, so here's the question.
How much worse will it get if we run this with bear type?
Okay, so call it speed checked, like that.
And all we're gonna do, the order is important here.
We're first gonna have our regular function, then we're gonna have at bear type.
Did it import it correctly?
It did, okay.
So these are like onions, right?
First thing we're doing is we're calling this function, then that's decorator wraps collector top, returns another function which is wrapped by the timed function.
That's important because we change the order, we're not really timing the bear type aspect.
All right, let's go over here and we'll put bear type on this.
In fact, let's not even put it on this one just to be 100% sure.
This is this function that's called here.
This is this function that's called here.
Let's run it.
this checked one.
Run it again.
It took 1.13, so we had 1.135 instead of 3.3.
I mean, there's no way it's gonna make it faster.
It's doing more work.
So you can see it's not adding really much overhead at all.
Now, do I recommend every single function that you write gets this bare type on it?
No, probably not.
If you've got a really nice Python type, decorated annotated set of code that maybe is protected with my pie or your editor and you've run the total project checking once or twice and then you kind of keep it hanging together.
You don't need every function in there to be running that way.
Really mostly what you need with this looking inside a collections caveat, of course, mostly what you need is just the outside functions.
Like if you're creating a data access layer, well, maybe just the top level functions that people call like find user by ID, find user by email, right, you could say the ID must be an int.
And email must be a string, do that with runtime checking.
But from there on, you've got your Python type hints and your my pine everything telling you, if you get into this function correctly, from here on out, it's safe.
So I think a really good pattern with bear type is like protect the boundary, if the inside stuff is already validated with Python types.
But that said, that's kind of what we have here so far.
Let me make it a little more intense, right?
So let's go down here and say that this thing is going to also be a bear type protected function.
I'll call this extract the method, call this math stuff.
And this is going to be an int.
And then groups can be a list of float.
And we'll bear type that.
So in this case, we'll say, for every one of these plus equals one.
And then let's do down here the same thing.
didn't really matter, but we can put a list of stir on there like that.
And I guess total is just gonna be one, in this case, or zero, this one.
Here we go.
Let's run again, see where we are.
Now we're up to 3000 operations.
And Just see if we get any difference.
Zero difference, right?
No difference, no difference here.
That's for protecting the boundaries.
That is a lot.
And one more time, let's suppose somewhere in here, instead of passing an int, we're gonna pass a string of that down there.
Whack.
The parameter 20 in the string, as a string, violates the thing that it must be an int because you made it not an int.
Silly, don't do that.
Right, how awesome is this?
I'm actually really, really excited about bare type.
I think it's going to be something that I adopt in some of my code.
It looks solid.
So again, if this was a dictionary of lists of int, it's not going to check that.
So you might need to put bare type down a little bit further down.
Of course, if you're using things like pydantic, you know, it's not going to make it that far if it's incorrect anyway.
So you can mix and match these, but bare type on the boundary, it looks like a pretty solid idea to me.
|
|
|
16:59 |
|
show
|
1:06 |
Much of what you've already seen, you might have been a little bit familiar with.
Even if you didn't do a lot of work with Pydantic, you've probably heard of it.
Probably heard of FastAPI and you've probably heard of a lot of the things we talked about in the typing syntax section.
However, in this part, I feel like there's going to be many things in here that you have not seen.
This is a less commonly used aspect of Python typing.
But honestly, it's pretty awesome.
We're going to talk about something called structural typing, or I've labeled this chapter orthogonal typing in the sense that it's not in the same direct way that you would have typing in regular Python, aka nominal typing that is like with classes and inheritance.
And this thing is a one of those.
This is more about applying features or restrictions or structure to other parts of code, even if you don't change that part of code.
It's pretty neat.
I'm gonna have a good time talking about it.
|
|
show
|
6:10 |
Remember our friend the duck, duck typing?
When we started talking about Python and type hints and many of the things covered in this course, what we said was, ""Python used to have duck typing and still does, but it also has these additional ways to add typing by saying, 'It takes this type of class hierarchy or it takes these types of numbers,' where you specify exactly what it is and they have to match in a static language style.
The runtime is forgiving when they don't match, but from the type system, they're supposed to match exactly, right?
But maybe there were some benefits to the duck and, well, see you later, duck.
We're going to miss you.
You won't be missing him for long because duck is coming back.
But let's go through a little thought experience to see why we might want to bring the duck back.
So let's try a classical inheritance type of typing experiment.
And this is referred to as nominal typing.
So let's try a traditional classical inheritance way of thinking through types, aka nominal typing as opposed to structural and see how well that works out for us.
Now this is a bit of the follies of object oriented programming.
I actually like object oriented programming, but when you get too much of it, it's bad.
It's like saying I like salt, but you can't have salt for dinner.
That's bad.
So let's talk about vehicles.
Okay.
So we have a vehicle and we want to categorize other types of things that drive around or, you know, vehicle like, so, well, motorcycles, motorcycles are vehicles you can get on them.
They'll move you from place to place.
Sometimes they're fun.
Sometimes they're cold and wet, but you know, they're a vehicle.
Clearly, cars, cars are probably the very first thing you thought of when I said vehicle.
Let's think about some of the traits that these vehicles have.
Well, cars and motorcycles have transmissions.
Sure.
So our base class, our vehicle in its type definition by virtue of being a class, will have to have a transmission field or properties or something like that.
You'll have to have an engine, you know, brakes, throttle and keys.
That seems reasonable for what we're gonna model, right?
In the beginning, our motorcycle and car, they were awesome.
But this was such a good idea that we're gonna keep going.
We now have an electric motorcycle, maybe one of those cool zero motorcycles that you can get.
So sure, is it a motorcycle?
You bet it's a motorcycle.
It's an unusual one.
It's electric, it doesn't have gas, but other than that, I don't think a change in the engine makes a big difference.
So yeah, motorcycle.
What about an off-road motorcycle, like a motocross bike, an enduro bike, even an adventure bike, maybe?
Well, is that a motorcycle?
Sure.
Not all motorcycles have to ride on the road.
So yeah, this seems like it's working fine.
However, the farther we go down this chain, we start to run into things that don't really fit the constraints of the vehicle.
Is an off-road motorcycle a vehicle?
Sure.
Is an electric motorcycle a vehicle?
Sure.
But hey, the off-road bikes often don't have keys.
Like the motocross bikes and enduro bikes, the adventure ones probably do 'cause they go on the road and get parked in parking lots, but a motocross bike, no keys.
So what do we do about the key property inherent from the vehicle?
That's the problem.
Electric motorcycles probably don't have a transmission.
It's just connected straight up to the sprocket and it just goes.
Maybe something, some gears to change how it actually, like the rotational speed of the engine versus the sprocket, but it's not really a transmission in the traditional sense.
You don't shift it either automatically or manually, right?
There's no transmission, let's say for this one.
All right, well, we're starting to push our luck, but we'll figure it out, right?
What if we want a robot?
A robot that could move us around or something?
You know, one of these crazy things, these Boston mechanic type things.
Well, you can drive it around and maybe some of them could get onto them.
Is it a vehicle?
Well, nothing about the vehicle class that has to have a passenger.
Maybe it's a vehicle.
It's in like an autonomous robotic vehicle thing.
I don't know.
But sure enough, we decided it's a vehicle.
But it doesn't probably have a transmission.
If it has an engine, it probably has four of them as servos, one for each leg.
Probably more than that.
I don't know if there's a throttle, maybe, maybe in this little computer brain, but who knows, right?
You can just see this is really breaking down.
So two observations here.
One object oriented programming doesn't solve all problems.
Most importantly, though, the further you try to push object oriented programming, the deeper your inheritance hierarchies get, the less they match in the way they originally intended.
hear things like prefer composition over inheritance.
With Python and typing, we could have said, ""Hey, we have a function and it wants to do things and so all it has to do is take a vehicle."" But then in order to match that typing information, our robot, which probably could be used in a way that, you know, things that drive around are, our robot has to inherit it from the vehicle just to be using this type hierarchy so that it matches passes the constraint that the property or field or whatever being passed in is actually a vehicle class.
And that's a little bit crazy.
So remember our duck that we said goodbye to for a moment, our duck would have helped out of here a lot because whatever the hierarchy is, it doesn't matter from a typing perspective.
If you could say, turn it on and make it go, regardless of which thing it is on the screen here, then duck typing would be exactly what you want.
So that's a little bit sad, huh?
|
|
show
|
8:24 |
Well, well, well, our duck is sure looking pretty good right now, isn't it?
With the duck typing, maybe we wouldn't have had any of those problems.
Maybe that type hierarchy would have been useful, but we could have just said, ""It takes a thing that looks like it has an acceleration or something.
In our world, maybe we just wanted to start the vehicle, turn it in a direction, and accelerate or decelerate it.
If all of those things on that picture before did those things, it could be a walks like a duck, talks like a duck, it is a duck, you can just use it in this duck typing scenario.
But with Python types, we saw that not so much, right?
Well, this whole chapter shows you a how to have static typing and duck typing, and to get along really well.
It's a very cool thing we're going to talk about called protocols.
So let's put aside that crazy deep object hierarchy and build a better motorcycle.
So we're going to jump over into PyCharm and write some code in a way that looks like it supports duck typing or in a sense really does support duck typing, but statically.
Sounds paradoxical.
Follow me here.
It's going to be cool.
So here we are over in a new chapter six orthogonal or structural typing.
And we have a protocol app.
Now I've kind of laid out in comments that object hierarchy and the challenges you might run into with it.
And what we're going to do instead is we're going to use something called protocols in a way that is quite interesting.
Okay.
So I would like a type something I could say down here, for example, this do vehicle things, I want to be able to pass in an object called a vehicle.
And I want it to be able to turn on, turn towards a direction and accelerate.
Basically do those three things.
I want to be able to express that in typing, but in a duck type way.
So as long as those three operations with the right signature exist on the object class callable, whatever, it works.
If they don't exist, it's not a match.
And we're not going to do this with inheritance.
Not only.
Okay.
really cool typing construct called a protocol.
Now it looks like what you might know from other languages, like what would be called an interface.
In C# and Java, you have pure abstract interfaces, just the keyword interface, and that means it can't have an implementation.
It only conveys a structure sort of thing.
In C++, I don't know if they've added that recently.
It's been a while since I did C++, but pure abstract base classes, right?
There's no implementation, just structured, conveyed by that.
So you might at first think of protocols like that, but they're more about the duck typing side than they are about this interface side.
Let's see.
So we're gonna create a class, which will be called drivable, or whatever you want.
If you wanna go C#ie on it, you can put an interface, but again, that's not exactly the right metaphor.
I wanna say typing protocol.
And then down here, we're going to put the things we would like it to do, turn on, turn towards and so on.
So let me just write these out and zoom through them.
So first we say def turn on just like you would a regular class based function, you say the self and this one has no parameters, but it does return a bool.
But instead of an implementation, you just put triple dot, then def turn towards and so on.
And feel free to load this puppy up with the types that you want.
It's going to return a none, but the direction is going to be a string again, dot, dot, dot.
So here we have this description of the duck typing story that we wanted to tell.
Anything that goes in here must have a turn on or turns towards that takes us direction, which is a string in an accelerate, which takes a rate, positive, negative, whatever, but is a float here.
Now, here's where the interface sort of abstract base class thing changes.
So watch this.
Let's say this takes a drivable, which should be fine, and drivable.
And we have basically the same motorcycle program as from chapter two.
But down here, I did add a turn on, a turn towards an accelerate to the motorcycle class.
So it just comes up with some chance of it actually turning on.
and it says it's running or it's stalled, right?
Something like that.
You know, put your level of reliability in the divisor there.
It turns towards the direction and it's going at a certain rate faster.
Probably should check whether it's negative or positive and just say slower instead of a negative faster, but who didn't like a double negative in their acceleration?
All right, so the very important thing to notice is motorcycle does not derive from drivable.
I did not say this.
I wouldn't hurt it to say this, but check this out.
So if I go down here and I say, I would put, or we're doing our thing here, our motorcycle comes up and it says, do motorcycle or do vehicle things and let's just run it.
Hey, the Tenere is stalled.
Oh no, we're not doing that one.
It's running, hooray.
Now it turns north and now it's going 9.81 faster.
So probably units.
should be required there, someone with pint.
But this, okay, so what have we learned here?
Not too much, it just kind of seems duck typing.
But watch this.
Notice what I did is I removed the turn on capability.
So remember to be drivable, what do you need?
You got to turn on, turn towards and accelerate.
What's wrong with the motorcycle?
I didn't change its base class or anything like that.
But Python knows the type system knows this thing is not walks like a drivable talks like a drivable.
So it is drivable.
No, it doesn't because it doesn't turn on it doesn't match the duck typing story.
But here we have a concrete way through this drivable protocol here that says, these are the requirements.
I don't care what your base class is, I don't care what your hierarchy looks like or any of those things, I just need these pieces of functionality, this protocol, drivable, to be met in order to be used here.
So I put this back, there we go, works fine.
And again, you would know if we try to run it, it's going to crash and it says has no attribute turn on.
What does the error say up here?
type drivable got motorcycle instead because motorcycles are not drivable.
How awesome is this?
So I think this is an incredibly cool capability and here's your duck, but your duck typing, but in static typing form.
We define a thing that expresses what we expect from the thing being passed in, but this bit is not at all involved in the type definition of motorcycle.
It's not a base class.
We haven't registered them together.
None of that.
We just said this function takes a thing that has to have those walks like a duck, talks like a duck, quacks like a duck.
And so it is a duck.
It has to be a drivable type of thing in its behavior.
And if it is, it's a good fit.
As you saw, if I take out part of it or change it, I'm sure if I even just change the signature here, success, oh no, PyCharm is not checking it.
It's not going to love that.
It will not be successful, right?
But if these don't exist in the structure that the protocol suggested, not going to work.
I think this is just such a cool idea.
It lets you still have your duck typing.
It lets you still have a simplified object hierarchy if you have one at all.
And yet, you still get run even editor time and my PyTime level of compatibility checks.
Awesome, huh?
|
|
show
|
1:19 |
So we saw that we can dramatically reduce how much object hierarchy and object-oriented programming we have to inflict onto our code just to get checking that some capabilities exist to kind of force a static version of that duck typing.
Well with structural inheritance and protocols, we can dramatically simplify our object hierarchy.
Maybe keep it really shallow.
We can do a little bit more composition, a little less inheritance.
Those types of good coding practices.
And we just create a class, give it whatever name we want, say it derives from typing.protocol, lay out all the capabilities that it's going to need for us to use it in this way, and then the type system actually checks whether or not it's actually deriving from that thing.
It doesn't matter.
It doesn't matter.
It's going to just look at the capabilities or the attributes of a class or object being used and says, does it match what we need by the type?
Does it have a turn on, turn towards and accelerate?
Yes, good to go.
If it doesn't, not good to go.
Definitely look into seeing how this would fit into your programs because it's a really lightweight way, kind of bring back some of the original joy of Python, that duck typing style, but with a little more safety.
|
|
|
33:32 |
|
show
|
1:11 |
So you know all the syntax of Python typing, you know some of the motivation, the history, and you've even seen a bunch of cool frameworks on how to use it.
Now what?
Well, it's one thing to know a technique or some syntax exists, especially around typing, it's really important to know when to use it and when not to use it.
As we saw, unlike static languages where you always write your code the same, you have a choice in Python.
And sometimes that choice encourages you to write code with types, but other times, maybe you don't wanna write types.
We saw that the readability, if you know what the types are already, can actually be a little bit higher without all those symbols around, right?
So in this chapter, I'm gonna give you some guidance and show you some patterns on sort of when should you use typing, what are some of Michael's recommendations, my recommendations, and things like that.
So take these as my opinion, but I think it's certainly something worth considering as we talk about these typing patterns and type guidance throughout this chapter.
|
|
show
|
2:05 |
Let's say you've got a program and it does not yet have types or you're thinking about where you want to employ types in a somewhat complicated application.
We're talking multiple files, we're talking maybe you're thinking in architectural layers, right?
Here's some of the logic of the application and here's the data access layer or the part that calls the APIs and those kinds of things.
If you reach that scale, which I think is not very big at all, but you know, more than one file, I want to start thinking about the boundaries.
So one of the really important things that typing can provide is it can make sure that consumers of code provide the correct information, assuming that they're using the tools at all, right?
They have a proper editor of some sort, or maybe they're running my pie, or there's some kind of continuing integration.
But assuming that the consumers of some part of your application, be that a whole separate package or just an architectural section, like a data access layer, are being consumed correctly.
And one of the real values of these architectural layers is that you can just sort of think about, well, here's the functions I call, create new user, find this record, you know, find me this order by this user, or you don't have to think about how that happens, what are the connections, all the details or the types involved, you just think of these sort of high level of extraction of, of what this data, let's keep going with this data access layer is providing to you.
And because of that, if you just can make sure as you consume those high level components of your app, that all the type information is hanging together, well, it's a good chance that internally, the data access layer will be consistent from types, it may or may not have type information.
But one really big value with typing is to make sure at that boundary layer, like this is the API for this section is what you think about as the data access layer or something that that's really secured and reinforced by types.
|
|
show
|
1:30 |
If you think of my idea I just expressed about the boundary, well, what bigger and more concrete boundary could you have than public packages?
These are libraries that you build, that you give to other people, maybe you publish them on PyPI and you say, this is what I've given you, go and use it.
And here's how you call the functions to do so.
Well, of course, you want those packages to be very useful and helpful, consistent from types.
When you publish a package, you don't want it to look crappy and cheap and amateurish.
No, this is not something you would be proud of.
No, you want it to be beautiful and polished.
And when people get it, go, whoa, that is nice.
Look at this package, look how thoughtful it is, look how modern it is.
Look at how well it uses modern Python and the type system to really express how I'm supposed to use it.
And people with that type information, they might not even need to read your docs, not that you shouldn't write them, but if they don't even have to go look, your package will feel very natural and people will think this thing is built in a really nice way because I didn't even hardly have to think about how to use it, I just started playing with it and it was obvious.
Types can play a huge part in that.
So when you're creating public packages, I would certainly say at least on the boundary of that package, the public API for it, you definitely wanna use Python type-ins.
|
|
show
|
1:10 |
Another area, and I kind of hinted at this with the, you don't have to go to the docs, is using type hints and type information for autocomplete.
That's why you don't have to go to the docs 'cause you hit dot and then boom, here's a great long list of exactly what you need, what type of data is passed through and so on.
To me, this honestly is like one of the top reasons that I use Python types.
When I'm writing code, I wanna say variable dot or a class dot and just get a nice list of very clear, very accurate information of what is it that I have, what functionality exists on that thing.
Here we have a motorcycle we created and because the createAdventure method said it returns a motorcycle, when we say tenoray.
you can see, boom, here's a great long list of exactly the features of the motorcycle class.
So do not underestimate how valuable this is and if you're not using an editor that looks like this in some way, you should really, really think about PyCharm, VS Code or something similar because it's tremendously helpful.
|
|
show
|
3:10 |
More of a pattern than advice, something you really want to keep an eye out for because the Python type system doesn't enforce it very well, although we've certainly spoke about optional or maybe when things are not optional in the Python type system.
Here's a function, getUser, and we have an email that we pass in.
Presumably we're gonna go look in a database or some lookup given email, let's find the user by that email.
You can see it says it returns a user.
Great, that's really nice.
However, what if a user with this email doesn't exist?
Right, if this is arbitrary email input we could put in here, there's no guarantee that the user will exist here.
Right, maybe somebody says, I'm trying to log in, here's my email.
You're like, ""Well, you typed in an email that doesn't exist in our website, in our database, no user.
What do you do?
If you have it written like this, we return a user, a concrete user every single time.
Remember Python types say you cannot have none, right?
They're not optional or user pipe none or any of those things.
User means there is a concrete user that will always be returned.
Never ever will it not be a concrete user object.
So that implies a certain kind of use case here.
It requires that the author of this function, if they're gonna be true to the type system, will throw an exception if the user is not found.
There's no way to return from this function normally if there's no user, right?
You can't return none.
So that's a choice you can make.
And certainly people do write code like that, but that means anyone who uses your code has to use a try except sort of design pattern.
And so think about if that's what you want.
If that's not what you want, then you should say this returns an optional user, in which case it's allowed to return none if you pass an email for a user that doesn't exist.
Again, you can write this user pipe none or optional of user because the pipe thing means that here's just a combination of types.
As I've already said many times, I'm a huge fan of saying optional, like it's either a user or it's nothing.
And so I prefer this, but you take the pick, write it however, but that doesn't matter.
What you need to do is say it's optional or none or a user or none.
And you have to explicitly express that if it's not the first pattern, either it's a user or it's an exception, right?
So either of these in my world are fine.
I honestly kind of like the lower one a lot of times because you don't necessarily want it.
It's not a problem.
You want to just check, does the user exist?
If not, give me none and I'll do something, right?
So it's not an error that the user doesn't exist.
It's just, you know, the type system and the first one didn't express it.
So think carefully about optional or not optional and what that means for not just the types you put in your function, but actually the design patterns that you use and you force your users to use, your consumers of your functions.
Super interesting, I think.
|
|
show
|
4:18 |
One thing that can be a little frustrating with highly evolved and polished languages or libraries and so on is there's often multiple ways to do the same thing.
And nevermind the fact that the Zen of Python says there should be one and only way to do a thing and Python typing, not so much.
It came out and then we said, you know, it'd be a little bit better if we could do that.
And what about this?
It'd be nice if we could add that.
And so you'll see there are multiple ways in which you can write code based on what version of Python you wanna support.
In the older versions, we had to write from typing import list, import union, and even I still do because I like this better as I just said, but you could still, you would say import optional.
In modern Python, the latest versions, none of these are required to accomplish what you're about to see.
But what if you wanted to support something that's not 3.11, 3.12?
Well, if we wanted to express, we have a list of things and that those things are a bunch of strings.
We could write capital L list str.
Later on in Python, they said, that's really annoying to have to import a thing called the capital L list.
We don't have a thing called lowercase L list that is actually the type.
So list, the capital L version is like a weird stand in for the real one.
That's weird, right?
So, but the lowercase one at the beginning didn't support this indexing in there 'cause what does that even mean, right?
That was weird to it.
Later Python's type system was evolved to do this one where you don't require the list import.
Now, the first one with the capital L list will work in Python 3.5 or above, but the one with the lowercase L list will work with 3.9 and onward, right?
So you have to have a little bit newer version.
At the time of the recording, 3.8 is the minimum supported version.
But you know, people have older code.
I've had people reach out to me on YouTube videos or other things where I said, Michael, you've written your code wrong.
I've tried to run it, it just won't run.
And it was this lowercase L list of some item.
And they said, look, it just keeps giving me this error.
You just, you messed up and you need to fix it.
You're not really actually that good at Python.
Okay, so that's one.
One explanation for why this isn't running.
Another one could be you're running Python 3.7 and this code is written for 3.9.
So you'll run in, maybe not on YouTube, but you'll run into these issues running older code.
And if you don't understand the type system, I'd be like, why, why does this not work?
You forgot to make it capital L and import it or something.
But no, it's just about the versions.
Here's another one, union.
So we could say union of string int, or more recently we could say string or int, the vertical bar, that's 3.5.
And this one is pretty new in 3.10 to use the vertical bar.
We also could say a union of string or none.
That's one possibility.
Or we could say string pipe none.
Or you could go a little bit further back and choose my favorite way of saying this.
An optional string.
So the union was a way to express this in 3.5.
Also the optional was right there.
And the 3.10 version allowed us to say string pipe none.
And while I think union, like the middle one, union of string int, I think string pipe int is really great, but string pipe none, I don't know, it just doesn't communicate as clearly exactly what's happening.
Although a lot of people prefer it, and if you do, you know, totally fine.
All right, so here's the, just gives you a sense.
So you wanna think about what minimum Python version you're targeting as you write your code, because there's some really simple things like string pipe int, or most commonly probably collection, lowercase collection class bracket some type that you're gonna run into that you're gonna need a somewhat modern version of Python for.
|
|
show
|
4:11 |
Are you a minimalist or is your space kind of cluttered with a bunch of knickknacks and collectibles or other stuff just laying around?
Well, I personally, at least in theory, would love to be a minimalist, although I'm not really sure how practical that is, especially with the family and kids and pets and stuff everywhere.
But in Python typing, it is good to have this minimalist attitude towards code, regardless of what the world looks like outside.
Let's look at an example.
So here we have a function, so creative, called func, and it takes some things, and we've expressed the type of these things.
Awesome.
It's a list of integers.
So we're gonna do fun stuff with this list, like for T in things, we're gonna work with each T, and because we've expressed that the list contains integers, T will be an int in the type system, and further checking and inference and stuff will happen.
It's gonna be fantastic.
So how would we use this?
Let's explore some ways.
If we have some numbers, here we have the first five Fibonacci numbers as a list.
So the bracket means it's a proper list.
We call the function.
Perfect.
The type system says, ""You know what, Michael?
You're a great programmer.
Everything is just like it's supposed to be.
Okay.
Well, what other ways could we use this function that seem reasonable?
We just wanna collection and loop over them.
Well, what if our numbers were actually in a tuple?
Here we have the prime numbers, plus one, plus the number one, here that we might want to funk on, right?
We're gonna pass it over.
Well, if you just look at what's on the screen so far, will this work for T in things?
Sure.
I'm sure it'll work just fine.
However, the type system says, No, no, no, this is not gonna work.
You said we got a list.
You're giving us a tuple.
Those are not the same, not even close.
Sure, there are collections that are flat, collections of single dimensional things.
And sure, you have numbers in your tuple, but for example, you can't sort a tuple, you can't put new items or take items out, they're immutable, et cetera.
So they're not the same.
And the type system is right to say, You expected a list, and you gave us something not a list.
But looking above, would that work for T in this tuple?
Absolutely, we'll just go, here's one, here's two, here's three, here's five, here's seven, fine.
Similarly, what if we get more creative and have a generator expression, where the generator on demand as you process over them will generate the square of whatever's in numbers.
So one, one, four, nine, so on.
Now, also a generator is even more different than a tuple from a list, right?
These are really, really different things.
You can't even index like bracket three, can't even do that to the generator.
So sure, the type system is right, but is that really what we need for our func?
No, what if we were just said, All I need to be able to do is to use this in a for in loop.
After that, I don't care what's in there.
It could be a set of integers, it could be a list of integers, it could be a tuple of integers, right?
So we wanna make sure that when we're putting types in our code, we're not just using, Oh, well, what's top of the mind?
Oh, I'm gonna use a list here, so list.
Fine, if it needs to be a list, but if it doesn't, if it's more general, then think about our friend, the duck, think about duck typing, and if it can behave in this way, it is one.
So one way to express that without say protocols, which is totally different, is to just use a more generic, a more general type of thing.
So we can say from typing import iterable, and then we have an iterable of int, and that just means, well, if it's anything I can put into a for in loop, that's all I need, right?
In this case, all three examples would be fine, the list, the tuple, and the generator.
|
|
show
|
1:45 |
So here we're in a new chapter, so chapter seven, type guidance and patterns.
I put a couple of starter pieces of code here for us to look at, and again, I marked directory as a sources root so that it can find each other.
This one doesn't need it, but the next ones do.
All right, so here's our example.
You can see we've got our function, we have numbers, we have numbers as tuples, we have numbers as a generator expression, and it just says, look, we're gonna loop over those and we're gonna print them out and we're gonna do math like square them.
So even though there's an error here, it says, whoa, whoa, whoa, we got a tuple of, instead of a list of int, or we got a generator instead of list of int.
Let's run this and see what happens.
At the top here, we print out the type.
So list tuple generator, sure enough, but look, it works just fine.
Not the same output 'cause we're not giving it the same numbers, but absolutely it works just fine.
So we can come up, let's say, I'll call this func cluttered.
I'll make a copy real quick.
It's this one.
It's not minimalist, right?
So over here, we can just say, we're gonna say iterable, and we've gotta import that from typing of int.
And notice that error goes away, that error goes away, because guess what?
Just like the list, we can use those.
There you go.
So runs the same, of course, but more importantly, the errors go away because we were more minimalist.
We just said, look, I don't really care what it is.
If I can forward it and it contains integers, we're good to go.
And that's true for all of them.
|
|
show
|
1:30 |
The next advantage for types I want to talk about has to do with refactoring.
Now, for some people, refactoring means I'm changing my code.
It was like this, now it's like that.
No, refactoring has a more constrained meaning.
And so I want to be real careful, just in case you've kind of been using it loosely.
It means changing the structure of the code, changing the code so that it behaves in exactly the same way as it did before, but maybe it's cleaned up in some way, maybe it's more readable, maybe it's split into smaller functions, maybe it's split across modules, whatever, right?
But it's just, I'm changing the code in a way that doesn't change its behavior from the outside.
Okay, so that's something you want to do all the time.
One of the problems people have writing code is they overthink, they dramatically overthink, how should it be designed?
How should we use this?
We don't want to get it wrong, because if we get it wrong, it's going to be real bad.
No, it might be a little bit bad, but it won't be real bad, because we have awesome tools.
Like in PyCharm, you right-click, rename this function, move that into a class, move that to another module, combine these, split those, all that stuff.
Code is plastic when you're working with the right tools.
It's malleable, it can be formed and reformed.
And one of the things you don't want to do is you want to run into trouble with all of your type systems.
So refactoring tools love types.
Let's see that.
|
|
show
|
2:37 |
Here's a simplified simplified version and also slightly partitioned into multiple pieces of our motorcycle app that we started this course with.
So here you can say create some bikes and it's gonna go create some adventure bikes of different types, Himalayan, Tenere, KTM, et cetera.
And it's awesome because it expresses the type information in 3.9 or above style with a lowercase L.
So list of bike, yeah, that's super cool.
And it creates a list of them and sure enough returns them.
And if we run it, just let's see what it does.
See some bikes and there they go, right?
That should be familiar from before, right?
All we, right, I hear all we care about is that it's iterable, but in this case it's kind of the reverse.
So it's fine that it's overly specified in a sense.
So what if I wanna make some changes?
Imagine I'm not even in here now, I'm over in this section and I've got a way more complicated app than two files with simple things in both of them.
But maybe we're over here looking like, you know what?
We called it bike, but there's another part of our app that works with bicycles or mountain bikes or something like that.
We wanna be really, really clear that this is let's say a motorbike or motorcycle.
I'll call it a motorbike.
And by charm, we can right click, refactor and there's all these awesome things we can do, but let's just keep it simple and say rename.
I'll say this is a motorbike.
And notice down here as I type, it's like this whole file is live updating itself, which is awesome.
So this, we're gonna make some changes here.
Do we wanna do this?
Yes, yes we do.
And because we're using typing.self, right?
That's one of those weird edge cases on how Python defines classes, but it also, because it's our motorbike where even the return type has changed.
But look over here, this is term blue, which means it's changed.
And it's changed in what way?
Well, motorbike.
And I'll rename the file too.
Let's guess that's what it was probably asking me.
Motorbike, that's obvious, right?
This part of code, but even the type information here, right?
So if we wrote this in like, say some kind of docstring style type information or whatever, or in a strings, we might not get that right.
And here we do.
So the complete type system sticks together under this awesome refactoring, which is pretty sweet.
|
|
show
|
7:39 |
Remember at the beginning of this chapter, when I talked about optional or non-optional, I said it returns a user and the only way to get out without returning users to throw an exception.
What if you never return from a function normally at all?
What do you put there?
Do you put none?
I don't know, you might think, Michael, that sure is weird.
Well, what if you have like some kind of interactive user interface and it's while true, ask a bunch of questions and then maybe you break out and throw a no more input or type of exception, something like that, that'd be possible.
If you use system.exit, that's like this, system.exit will signal the exit code for your program, but it also raises an exception and immediately ends it.
What if all the paths in a function use system.exit?
There's no return value, not even none in fact.
So how do you express that?
Well, surprisingly I think, it's surprising to me, it actually exists explicitly in the Python type system.
So here's a really, really simplistic assembly of some sort of web application.
Now, one thing that you can find often in a web app is you'll find that there might be different status codes you want to encounter for certain things, right?
So if I'm just gonna render some HTML, maybe that's a 200 response code, right?
That's the standard successful, everything worked.
But there are other times to say, you know, this page was found and it's fine, but you're not logged in yet.
So I need you to go log in over there and come back.
So you might throw some kind of authorization needed status code, or it might be a redirect, like, yes, you came here, but actually there's a different URL where that lives, and that's either permanent or temporary, right?
So there might be an HTTP found, like a 301 or 302 type of thing.
So we're gonna simulate that real simple story here.
So here's our fake web framework, and it says, do you want to redirect or see the page?
So it says redirect version one, because we're gonna run version two in just a second.
It says we are redirecting, notice this, we are redirecting the user, dot, dot, dot.
You will see the page content and you are amazed, okay.
Down here it says framework redirects exception or some kind of bad request.
So if you pass in some kind of bad information, right?
Like if this is an API, you posted missing data, the thing to say would be 400 bad requests.
All right, now let's look down here.
We can redirect to the URL and it can come in and have a URL and say it's permanent.
We do a permanently moved.
What is that a 301 I think?
And then a 302 is it's just over here, but not necessarily forever.
Just this one time, go look there.
Like you've logged in, now go over to your homepage.
All right, so if it's permanent, we do one type of framework exception.
If it's not permanent, we do another, or if it's bad data, we do yet a third.
But notice there's no normal return path here.
All right, well, let's just run it and see what happens.
So do you wanna see the redirect page?
Yes, I wanna see it.
Framework redirect exception over to here.
Awesome, let's run it again.
No, I don't wanna see it.
You see the page content and are amazed.
So anytime that we call redirect to, whether it's permanent or temporary, if I put in here true, you get a framework redirect exception, okay?
And if I put in none, you get the bad request, right?
But you never ever will see we are redirecting the user, right?
Because anytime we call this function, no matter what we pass, there's gonna be an exception.
But look, PyCharm says on line eight, everything's fine.
The very next thing to happen, dear programmer, when you write this code is first it's gonna run this, and then we're gonna go here.
But do you?
No, you never ever do.
And while it's technically true that nothing is returned from here, more accurately is there will never be a case where you go through this function and there's ever a return value whatsoever, right?
At all.
Exception, exception, exception.
So there's a V1, that means there should be a V2.
And let's go up here and use V2.
Nothing's changed yet besides the name.
What can we put here?
We could put no return.
And we can import that from typing.
Typing.noreturn.
Now, you might think this is like a void function.
It just means there's no known value, right?
So if I had def bunch print i, right?
You might say, oh, this has no return value.
That is not what this means, right?
If the way to express that would be to say it has none, right, we talked about this previously, right?
So it's not this, it's definitely not that.
What this no return means is there's no value at all, not even none.
Another way to think about it would be always an exception.
Maybe that doesn't mean a bad thing, but always an exception.
If we come up here, for some reason it took a second, I don't know why, but notice there's now some kind of warning on line 11.
What's going on here?
We call this function, it says, you will never reach this code.
Why?
Because no return, maybe like I said, a better way to think of it would be always an exception.
There's always an exception, and this is not in an except block or a finally or anything.
So the very next thing that happens is an exception will be thrown and you're into that block.
So by virtue of going to redirect version two, you're always here.
And as we saw, this never actually did run, right?
But yeah, I'll put it like this.
We wanna see that warning, right?
We wanted to say, hey, you realize this is not a thing.
I told you about the system.exit, right?
So we could import sys.exit72.
Notice, same deal.
This code is unreachable.
So same idea, we don't have to exit 72 there.
We want it just to run, but this, you know, maybe, I think this is really valuable, right?
So it's not a common thing to do, to have a function like this, but it's not never either.
And it's good to know about.
Just, I covered this for two reasons.
One, I think it's interesting to say you can express that there will always be an exception from this function in the type system.
Pretty interesting.
Two, maybe more importantly, is that if you run across no return, it's not to say that this situation, where you just don't have a return statement, that is returning none, that's how Python works.
No return should be renamed to always an exception or something like that.
All right, well, that's no return.
And it's pretty interesting.
It's not common in a lot of different static languages, I think, so quite interesting to find it here in Python.
|
|
show
|
2:26 |
Final thing I wanna talk about are collections and their contents.
This is a beautiful closet setup, isn't it?
I mean, okay, maybe it's from a store, but look how nice it looks.
It's got all these different colors and all the variety.
And as a person, I look at this and go, oh, that's just great.
Look how creative, how interesting.
But if you were a collection in the type system, not so much.
You probably would feel better if it looked like this.
All the same, it's all just these like no-armed gray sweater things because it's a collection of no-armed gray sweaters.
You wanna think about that in your type system for collections as well.
So something like this, these things, we have a list of, and then what goes in that bracket?
If that's always the same thing, you've got tons of information to work with.
It's very well constrained.
But Python types, they'll take anything.
Like I could say, you know what?
Things actually is a one numerically, then it's a string, then it's written out word two, then it's a list of ones, and then it's a user with ID five and so on.
It's great that the Python has this flexibility.
However, what do you do as you go through these different things?
How do you process it?
Like how do you treat the first and the third thing the same?
Their strings are without the text and then the ones a number and then it just gets worse from there.
So if you wanna express this, you can, you would just have to say list of any, but you should try to avoid this.
There are certain times of like hierarchical data that is just so hierarchical and diverse that you just have to embrace the dynamic nature of it.
Think JSON, complex JSON responses from APIs.
But in general, in general, try to make your collections all the same thing.
Make them those boring gray armless sweaters and don't make them have a lot of variety because if they're all the same thing, then all of a sudden it becomes very easy to reason about its contents.
Like when you loop over it, what is the thing in there?
Well, it's a number, great, or you index it out or whatever.
Certainly there's times when it doesn't make sense, but aim to have collections be homogeneous, homogeneous, not heterogeneous, containing the same type throughout.
|
|
|
5:05 |
|
show
|
5:05 |
You made it to the end.
That's it.
I hope you really enjoyed this course.
I know there's a ton of cool stuff that I'm sure you've learned because I learned a lot of it as well while creating this course on Python types.
Let's just do a quick look back, a quick review at what we've discussed throughout this course and kind of keep it fresh in your mind one more time.
We started out by talking about the whole spectrum of type systems and type safe or unsafe languages.
On one hand, we had the really type strict ones like C# and Swift.
And the other we had JavaScript and Python.
We've seen the initiatives.
This course is conversation around Python type hints and TypeScript to push that stuff, Python and JavaScript way more to the right.
So we saw you can dial this in the Python world.
You cannot in TypeScript so much.
But in Python, you can choose how much typing you want and how much you want to just kind of freeform it in a dynamic style.
Python types came from PEP 484 originally introduced way back in when in September 2014.
And they appeared in Python 3.5, which is actually, it was an amazing version.
It's not even supported anymore.
That's where async and await came from.
That's where Python types came from.
That's one of the most important releases of Python, in my opinion.
If you want to have types that are not part of a package or not in the code, we saw that typeshed has this support for many external packages that are out there that maybe can't be changed to have types for some reason.
You could also do this at your company, this idea of these type definition files.
We also talked about awesome frameworks built on types.
In fact, we started that conversation off by talking about awesome Python typing, which is a collection of awesome Python types, subs, plugins, and tools to work with Python types.
You can check that out and there's many more places to find that.
But we covered Pydantic, which is like kind of data classes plus data validation, data conversion, really, really popular and important.
FastAPI built on Pydantic.
Django Ninja built on Pydantic.
Genie from MongoDB built on Pydantic.
We also spent some time talking about editors explicitly, even though you've seen one example of it throughout this whole course.
VS Code and PyCharm, super, super valuable to have them understanding the type information and surfacing that to us and providing warnings where we get it wrong.
We can also run mypy, which is great for continuous integration or for Git pre-commit hooks, that type of stuff where you're not necessarily even looking at the code, but it'll run against a code base and give you basically the same errors.
And if you want true runtime checking, think about bare type, you can actually put that on maybe the boundary of your code and say, we're not just going to suggest that the type line up, we're going to make sure it absolutely does with runtime type checking.
We closed out our syntax conversation about like, how do you express stuff in Python and types by talking about something pretty unique, protocol.
So we said, look how messed up this object hierarchy becomes when we're using straight inheritance.
But if we use this drivable protocol, all of a sudden we can have little tiny object inheritance sections if we want, but long as they all conform to this protocol, then we can use them all the same and still express this drivable type.
That's really unique with protocol, unlike say, interfaces and other languages, motorcycle, automobile, car and robot, none of them have to derive from drivable, just long as their structure matches, Python will figure that out through the protocol.
Really love this.
Finally, we talked about patterns and all the different ways you want to think about it.
We talked about minimalism and say, things like iterable instead of list.
We talked about using optional of thing versus say, thing, pipe, none, the multiple paths you might choose based on what version of Python you're targeting, no return, a lot of fun stuff over there.
So stay in touch.
Thank you for taking the class.
You can find my essays and links to basically every social media and stuff that I have over at mkennedy.codes.
Come to the Talk Python to Me podcast where I interview many of the creators of many of the things we've talked about, some of the peps, some of the frameworks, all that over on Talk Python to Me, the Python Bytes podcast where we talk about awesome libraries and tools we find every week.
And find me over on Mastodon where I'm@mkennedy@fosdodon.org.
And if you X, you can just find me@mkennedy over there.
Thanks so much for taking this course.
Awesome.
You made it to the end.
See you around.
|