|
|
17:20 |
|
show
|
2:33 |
Hello, and welcome to Python for the .NET Developer.
I'm so glad you're here in this course and that you're excited about learning Python well, as a .NET developer.
In this set of videos, we're going to talk about what you're going to learn, what prerequisites you need and why Python is such an amazing language.
Let's begin with the methodology of this course.
Sure, you could just go take some random Python course.
Heck, we have many random Python courses some of them are getting started courses.
Knowing that you're a .NET developer we want to make that jump from C# to Python much cleaner, much more reliable and more comfortable.
So what we're going to do is we're going to take all the stuff that you love about C#, .NET and its ecosystem, ASP.NET, Entity Framework, Linq Lambda Expressions, classes, generators, whatever it is.
The stuff that you love in C# we're going to look at it first in C# in working examples, and then for that example we're going to rebuild it from scratch in Python.
Are you a fan of ASP.NET?
Cool, over in Python we have something called Flask and it works a lot like that.
So we're going to build a cool web app in ASP.NET and then we're going to rebuild that app in Python.
Do you think Lambda Expressions are all the rage and make your C# code so much better to read and write?
Well, in Python we have something similar and we're going to create some little examples in C# with Lambda Expressions and then rewrite those in Python.
Working with databases just is not going to be the same once you get started with an ORM.
Entity Framework is the ORM for C# and .NET.
Over in Python we have amazing ORMs, as well and they work very similarly to Entity Framework.
So we're going to take a data driven app in C# and we're going to, you guessed it, rebuild that app in Python.
So the idea, the methodology of this course is we're going to take everything you love about C# and .NET and we're going to show you the Python equivalent talk about how we put it together how we decided which aspect of Python to use or which library to use to implement that.
Instead of going, Well, I'm going to jump into Python and I'm going to be a total newbie I'm not going to know how to do anything you're going to jump into Python, have all of your experience in C#, I know how to do data bases, I know how to do web apps, I know how to do X, Y and Z," well, guess what?
We're going to show you how to do all that stuff in a super smooth way over here in Python so you can jump in and be productive immediately.
|
|
show
|
3:04 |
As a .NET developer, you could go learn many other languages.
C# requires a lot of skill and it's similar to many other languages as well.
JavaScript might be a good choice.
C++, maybe that's a good choice or even gasp, Java.
You could go learn that as well.
So why should you learn Python?
Well, let's start with a story originating from a place that I'm sure you're familiar with Stack Overflow.
Stack Overflow was created by Jeff Atwood and Joel Spolsky who had a lot to do with the Microsoft space.
So kind of the home of .NET Q&A in the early days, right?
Well, they wrote this cool blog post, The Incredible Growth of Python.
So they went through and they analyzed all the questions on Stack Overflow and they said, Which ones are tagged with Python relative to other languages?
Let's look at that over time.
Let's look at how that's growing.
Let's do some data science and predictive analysis and see what we can find out about the Python space relative to other things.
Here's the first graph that they presented.
They said, We're going to focus on high-income countries, and they plotted out the interest in questions for any given language over time.
Do any of these stand out to you?
Do any look particularly amazing versus, you know ones that are downward-trending like PHP?
PHP is not a good thing to learn right now because it's just fading.
Java's pretty flat.
C# went down from its original high but then it flattened out.
But certainly the last two years it's either a flat or slightly downward, which is not amazing.
On the other hand, Python is unlike all of these languages.
It's growing so incredibly quickly.
I don't know about you but if I was going to position my career to glide along one of these curves would you want a downward curve or would you want something that is rocketing up?
Well, maybe Python was popular.
Maybe this is true of this graph but say that little peak at the top?
Maybe that's like a super-turn and like we're all going back down and it was just a blip on the radar.
Well, these folks did some analysis to figure out where they think it's going to be in a couple years.
Look at this.
It was impressive before.
It's ridiculous now.
So you can see this is the future traffic predicted with the STL model along with a 80% prediction interval.
Python is really good to learn right now.
And on one hand, it doesn't matter how popular a language is if you're just trying to build an app.
On the other hand, it really actually honestly does.
It means there are many more libraries available.
Those libraries get more attention.
It means there's more tutorials.
And it also correlates with career and job growth.
So Python is an amazing language to learn right now.
As a C# developer, once you get over the initial shock of Oh my gosh, here's the language that's different I think you'll find it is a super, super-language.
It really pairs up well with the stuff that you already know and love and that's what this course is all about, making those connections for you.
|
|
show
|
0:36 |
I'm sure you're wondering what experience do you need in order to take this class?
What knowledge do we assume that you already have?
Well, not too much.
We think that you know C# and that you're familiar with .NET the runtime, and the base class libraries.
That's about it.
As long as you know the basics of the C# language and you're roughly familiar with some of the things in .NET like Entity Framework and ASP.NET this course will be custom-tailored for you because, like I said in the beginning we're going to take those concepts see some application using them and then rebuild Python's version of that.
|
|
show
|
3:37 |
This course is going to be full of hands on examples using awesome Python technologies libraries, language features and so on.
So let's quickly talk about what you'll learn.
First thing that we're going to do we're going to spend a fair amount of time on is just the Python language itself.
But, like C#, the Python language is just the tip of the iceberg.
There's an entire standard library what Python calls their Base Class Library from .NET as well as many, many, many external libraries that we can bring in and use to build amazing applications.
But first, we're going to focus on a solid foundation with the Python language and we're going to be using an editor called PyCharm.
This is by the same company that creates things like Resharper that plugs into Visual Studio and makes it awesome.
Use a whole IDE specifically built to make writing Python code amazing.
After that, we're going to focus on object oriented programming.
Python has amazing object oriented programming features and we're going to do a whole chapter on that.
As I hinted with my many, many, many packages yes, there are many packages in the package management framework or location that puts these all together and lets us install packages that extend our Python application, called the Python Package Index often referred to as PyPI for short.
We're going to see how we can use PyPI to manage packages in our application and how those packages make our application do amazing things in just a couple lines of code.
Having a good understanding of how your application runs requires a good understanding of how your code in general is run on that platform.
Then, we're going to spend a whole chapter thinking about Python's memory management and how it manages memory for you and what that means for runtime consideration.
So, we're going to dig into Python's memory management.
After that, it's time to start building rich applications.
So, we're going to build an awesome web application with Flask, this is like ASP.NET NVC but for the Python world.
And, of course, web apps require database access.
In .NET we have Entity Framework in Python we have SQLAlchemy along with a handful of other amazing ORMS.
So, we're going to use SQLAlchemy to build a database backend for our web app.
Of course we want our code tested we want it to be reliable, don't we?
So, we're going to use the application library called pytest that lets us write unitests in a really clean and factored way.
If you want to get a lot done, sometimes that requires parallelism, so we're going to explore the async and await keyword.
No, not in C#, maybe a little bit but we're going to explore in Python because Python also has the async and await keywords and Asyncronous methods that are going to be super familiar to you as a .NET developer.
What might not be so familiar what might be new, and is really amazing is something called Jupyter.
Jupyter Notebooks and Jupyter Lab.
These are computational notebooks that are very different from traditional applications you would write.
They execute piece by piece with lots of visualization and exploration, so we're going to go explore two different types of data using Jupyter Jupyter Notebooks, and Jupyter Lab.
Then, we're going to round out the course by taking our data driven web app that we built in Flask and deploying it on Linux, in the cloud.
So, we're going to work with Ubuntu with uWSGI and Nginx to properly deploy our app.
So, it performs amazingly on a super cheap virtual machine in the cloud.
That's it.
Of course, we're covering more there's many little things that we're touching on but here's the big summary of the topics we're going to cover.
Think of all the things that you could do with all this technology after you've taken this course.
You're going to be on fire.
|
|
show
|
4:08 |
Being new to the Python space, you might wonder what can you build with Python?
What apps are out there that you know of that are actually built with Python?
Maybe you don't know they're built with Python but yes, in fact, they are.
So in this short section I just want to give you a taste of what is out there and what is built with Python so you can think of it and reference what you want to build and things that you're trying to do.
When I think of companies that are impressive to me these days well Space X is absolutely among them.
What Elon Musk, and his team over there are doing to jump ahead of all these governments and just create true innovation in space.
It's blowing my mind, it's like we live in the future.
And guess what?
If you go over there, there are 92 different job positions probably more jobs than that open for Python developers at Space X.
They don't talk about exactly how they're using it but I know that they're doing some amazing stuff there.
Spotify.
Spotify makes heavy use of Python for their web apps and APIs.
NASA.
NASA uses Python for a lot of things.
They did this montage of how we're going to reach the moon this new project that they're on.
And they had all these different people and engineers and they had one section where they were focusing on a awesome code that they were writing.
Yeah, that was Python code.
Bitly, the URL shortening service.
Bit Bucket.
JP Morgan Chase.
Some of these, Spotify, Bit Bucket they got those like little start-ups and that there kind of thing.
But enterprise stuff, what about that?
Well, JP Morgan Chase, they're core trading engine the central part of their bank is built in Python.
In fact, 35 million lines of Python code that JP Morgan Chase has doing super central stuff over there.
Quora, Q&A site I love how rich and detailed the answers are over at Quora.
That site, you guessed it, Python.
Disqus the way you can plug in the comments at the bottom of your blog or other webpages.
They talk a lot about how thy use Python and how they scale it.
Really amazing.
Instagram.
Instagram does all sorts of stuff with their APIs and their web apps in Python.
They gave an amazing keynote at PyCon a couple years ago about what they're doing with Django and it's super impressive.
Read It.
Front page of the internet, as they say sometimes.
Read It is not only built in Python but there's an archival version of it where you can get the source code for the Read It site and yeah they use tons of the technology that we're covering in this course.
YouTube.
YouTube is built in Python and they get millions of requests per second.
Per second.
And it's built in Python, doing amazing stuff.
There's a mix to technologies over there but Python's central over at YouTube.
PyPI, the Python packaging index is implemented in.
No surprise really there, Python.
Pintrest.
Pintrest is also Python and Django.
PayPal.
PayPal has some central APIs that do their pricing their real-time pricing stuff and these APIs get called billions of times a day.
They need super, super low latency response times You know like, five milliseconds, that's a lot can we get that down to three type of response time.
Python.
Dropbox is almost entirely Python.
Both the stuff behind the scenes, as well as the client the little box icon that appears in your task bar or your menu bar, all that is Python.
They have millions of lines of Python making Dropbox possible.
And finally, last but not least yeah, Talk Python.
The training site, our APIs, so much of what we do of course is built in Python and it's been serving us incredibly well and it's going to continue I'm sure.
Here are just some of the things that are built with or using Python deeply.
These are amazing, right?
If it worked for them chances are Python's going to work awesome for you too.
There's a quick little link here at the bottom.
This links over to the description of many of these different companies how they're using Python, and so on.
Not all of them, like Space X and JP Morgan they're not covered there.
Well, neither is Talk Python I guess.
But a lot of the other stuff covered here they actually talked specifically about how they're using Python if you want to dig deeper and see what's going on.
|
|
show
|
0:28 |
I want to tell you, don't worry.
More than half of this course way more than half of this entire course, 10 and 1/2 hours is building hands-on code with an editor either Visual Studio or PyCharm.
So we're going to be building tons of code in here and this is just a call to say we're going to set the stage do just a little bit of high-level overview before we dig into writing the code.
Hang tight, let us get everything lined up just right for you and we're going to start writing that Python code.
|
|
show
|
2:54 |
You might be wondering Who is that disembodied voice telling you all about Python?
Well, hey, it's me, Michael Kennedy, nice to meet you.
I'm so glad you're taking my course.
Follow me over on twitter @mkennedy.
Now, what have I been doing the Python space that makes it worth listening to me?
I happen to run the most popular Python podcast out there called Talk Python To Me.
I've been running this for about four years and many of the technologies we're covering in this course I've sat down and had deep conversations with the founders and the people maintaining these projects about where they came from why they built it the way they did, and where they're going.
So I've spent a lot of time talking to the folks who build Python for us.
I also run the Python Bytes Podcast along with my co-host Brian Okken.
This is like a news show for the Python space that we do an episode every week.
You can bet I'm on top of all the Python trends.
And finally, I'm the founder and principal author at Talk Python Training, where you're taking this course.
You can probably tell, I'm pretty excited about Python and I am very excited to tell you about it.
But there's one more question you might want answered for this particular course.
Why should I be qualified to tell you about C#?
Yeah, I'm some Python guy, right?
Well, yes, but I was also head of the curriculum and an instructor and author at DevelopMentor one of the main training places where people learned .NET in the early days.
I worked with some of the thought leaders in the .NET space when .NET was originally released.
Also spoken at international conferences NDC Oslo, Dev Week in London, a bunch of other conferences about .NET to .NET developers.
I've written some articles that were published in MSDN Magazine.
Can you see the by in this one?
ASP.NET long-running workflows by Michael Kennedy.
In fact, I still get invites to go to the Microsoft conferences from Microsoft.
In just a few weeks, I'm going to Ignite to be part of their special podcasting event.
Last couple of builds I've been there on their invitation.
I'm very connected to the .NET and Microsoft space.
Absolutely my history and, even still, in some ways.
Why am I telling you this?
Not because I want to make myself sound important.
Actually, I don't really like talking about it like this.
But, I want to point out that I have been one of you.
I have been in the C# space, I wrote professional C# for over ten years.
I think C# is a great language and I really enjoy working in the .NET Framework.
Being a Python developer but also someone with a rich .NET history, I think I'm uniquely positioned to line up the dots for you, to say Here's what you do in C#, here's how you do it in Python.
Here's what you do in C#, here are the three ways to do it in Python and here's why you pick those and so on.
We're going to have a ton of fun digging into C# and seeing how to replicate all of those ideas over in the Python world.
I'm going to make a prediction.
By the end of this course, you're going to love working with Python.
|
|
|
8:23 |
|
show
|
2:45 |
In this short chapter we're just going to talk about the setup and tools that you need in order to follow along and work on the code from this course.
Would it surprise you to hear that you need Python?
Yeah, that's right you're going to have to have Python installed for our Python course.
But, specifically, you're going to have to have at least Python 3.6 or higher.
Look, if 3.9 is out, go get that.
At the time of this recording they just released 3.8 so that's totally good.
In particular we're going to need to use the async and await features that were introduced in Python 3.5.
Throughout the course we use what are called f-strings in Python.
These are formatted strings.
Think string interpolation the dollar sign string in C#.
That requires 3.6 or above.
So, basically, to run the sample code exactly as it is you're going to need 3.6 or above but like I said, get the latest.
You may be wondering, do I already have Python?
What version is it?
Do I need to update it?
Some operating systems come with it.
Others don't.
So if you're on macOS or Linux, you can type Python3 -V and it'll print out the version.
Like I said, 3.8 just came out but it hasn't gotten a chance to propagate over to my MacBook yet.
3.7.4 is what we got installed right now.
And this is plenty good.
3.7 is great.
This is how you check on Mac and Linux.
Most of you, I would suspect, are on Windows.
And on Windows, if you type Python -V, capital V again you'll see what version of Python you have there.
But there's a caveat.
Python in Windows is a little bit tricky.
Until very recently they didn't have this Python3 command that lets you -V, a command, distinguish between Python 3 and Python 2 or whatever the default version is.
Depending on how you have Python installed on Windows this might report the latest version of the most up-to-date version of Python or it might just report the version that is the latest or the most recent in the path okay, that occurs earliest in your path on Windows.
So you can type where Python.
It'll give you a long list of all of the options that you could possibly run and then you might see oh, here's a newer version.
Let me make sure that that's listed in the path first.
Both platforms we're going to need 3.6 or higher.
Here's how you check.
One final note on Windows.
If this comes up with something weird or doesn't give you an output like if it doesn't say Python something below just type Python alone and that will probably on the latest version of Windows 10 open up the Windows store and suggest that you go get and install Python 3 from the Windows store.
So there's this command on Windows that is Python even if you don't have Python installed.
It's just a shim to launch the store app version of Python and get that set up for you.
Okay?
|
|
show
|
0:37 |
Now, if you do need to install Python if you don't have it yet, you can just go to Python.org and download it, and install it, that's probably fine.
But there's actually a ton of different ways to get Python on your system, and they have trade-offs and they have different update paths and all of that so it might be worth dropping over on the Real Python article, Installing Python.
realPython.com is a great place that has all sorts of tutorials and other information about Python.
I'm sure as you're going through this journey you'll be there a lot, but they have this cool article about installing Python on the different operating systems so if you need to install Python, have a look here.
|
|
show
|
2:09 |
We're going to need a proper editor to work on our code.
We're going to write a bunch of cool apps we're going to write some database apps going to write some data science apps web apps, all kinds of things.
A really super simple, simplistic editor would suffice but it is really not what we want.
So there's a couple of choices we could choose but I'm going to be using PyCharm for this course.
I do believe it is the best way to write Python code and I think as a .NET developer you're going to really, really appreciate it.
For the .NET side of the world Microsoft side of the world I do want to throw out that the second best editor in my opinion that is right behind PyCharm is VS Code with the Python plugin.
So if you want to use that that's totally fine.
But we're going to be using PyCharm.
There's a Community edition and then there's a paid edition.
The Pro edition, the paid edition is what we're going to need to do some of the data science and the web work, the database work.
But other than that you could use the Community edition of PyCharm for everything else.
I'm tempted to go into all the details about why this is so awesome.
Do want to say that we will go through lots of the features and you'll see this thing in action over and over and over.
We're going to write a ton of code throughout this course so you're going to get to learn how to use it really well.
But let me just leave you with this thought.
Many of you are fans of ReSharper the plugin for Visual Studio.
You know, especially in the early days it did amazing stuff compared to what Visual Studio itself did.
Visual Studio has adopted a lot of those features but still ReSharper is awesome.
You maybe had the thought like Well, Visual Studio with ReSharper is awesome but what if the people that made ReSharper just made the whole thing they made the entire IDE and it came with ReSharper?
Well that's basically PyCharm.
Right, this is completely made by JetBrains it's the same people that make ReSharper.
And it's just dedicated to working on Python code.
And by the way JetBrains did eventually come out with their own IDE for C# and .NET as well it's called Rider.
We're not really going to work with or talk about Rider.
Just interesting bit of history there.
But PyCharm is really a super great IDE for working with Python code as well as HTML, JavaScript database stuff, and so on.
And that's what we're going to be using for this course.
|
|
show
|
0:30 |
Like I said before we're going to write a ton of code in this course.
In fact, we have a whole bunch of applications that are C# applications, and the flow is going to be let's look at the C# version of this app and then go build it in Python.
Now looks at this next C# app and then go build it in Python.
So of course you're going to want to have the C# code to look at, and you're going to have the Python code that we create to explore and run and modify.
So I want you to pause this video and go over to the URL at the bottom https://github.com/talkPython/Python-for-dotnet-developers-course and star and fork this repo so you can work with it and make it yours throughout this course.
|
|
show
|
0:42 |
Now, you're going to learn a bunch of cool little building blocks in Python throughout this course.
How do I make a loop, how do I make a class how do I create a web app, how do I, you know render a template of eight dynamic HTML and so on, and so on.
And these are super powerful to build up your toolbox but it's more fun to actually build a thing and I think you're going to learn a lot more.
So, when you're taking this course I encourage you to do a couple of things.
Make sure you've cloned the git repository, of course and then look through the C# version maybe even before we start that chapter.
Play around with the code, it's usually one app except for in the first chapter, the language one where there's a bunch of super small little features we're exploring to explore the C# code maybe even before you start that chapter or right at the beginning as before we even dive into the first demo.
We'll do that in the videos as well but I think it'll help you to get a little bit more accurate sense of what that C# code is about and then try to recreate it in Python.
The goal of the course is to learn Python so don't start that first.
Watch the chapter, and then once you're done with that chapter go and just create a new Python project take the C# code, and try to recreate it.
Of course, if you get stuck, don't let that be a big deal.
Just go back over to the real repository of the code you saw me write, and go oh yeah, that's how we do a for in loop, got it.
And then go back and keep working, or whatever, okay?
So I think if you want to get the most out of this course you should try to follow along, and try to build stuff and I think the way to do it here was to look at the C# code and try to build the equivalent in Python.
That's what we do in the videos and I think it'll be a fun way to reinforce what we learned.
We all love building things, right?
|
|
|
1:40 |
|
|
2:05:42 |
|
show
|
1:56 |
It's time to start to write some code and digging into the Python language.
In this chapter, we're going to focus on comparing language features to language features.
The Python language features to C# language features.
And as you know in the C# side of things which is also true in Python knowing the language is not much.
It's only the tip of the iceberg in terms of knowing how to use C# and Python is just like that.
The language is great you do need to know it but there are so many libraries.
There's the standard library which is like .NET's base class library.
And then of course there's all the other libraries out there hundreds of thousands of them that we could use and that you need to know.
This is just one part that we're starting to dig into here and we're going to focus on language features in this chapter.
When you think of the Python language and the C# language depending on your background you may think that they're quite different, right?
You might think well Python is kind of like this scripty language.
It's a little bit like Bash, but a little bit more.
And then C# is what you build real applications with with compilers, and runtimes, and all that.
These two languages and ecosystems are way more similar than you think.
Yes, Python has been used in a scripting context but it is used to build some of the most important applications out there and some really major ones.
For example, maybe you've heard of YouTube or Instagram or things like this, right?
It's used for really large and important projects.
The language structure that you have in C# and Python is actually quite similar.
Many of the things you care a lot about in C# and the language you'll find very comfortable and sometimes once you get used to it even better in Python.
So we're going to talk about those now but I just wanted you to keep in mind even though they may look different when you first see these new language features or these language features in a new language they're actually quite similar to what you already know.
|
|
show
|
3:20 |
Before we get into the details let's just briefly look at the various language features of Python that we're going to cover, and how those map back to C#.
We're going to start by talking about the language structure itself what holds the language together and defines its elements.
And in C# we have things like curly braces, and semicolons.
Python has neither of those.
So, how do you define, like, an if statement or a function or a class, stuff like that?
So this is what we're going to start focusing on.
Then I want to cover loops.
C# and Python have a lot of similarities but there's also a few unique and powerful features to loops in Python that we want to look at, because they're not obvious until you've had some time to work with the language and they're really powerful.
Functions, functions are first class objects in Python similar to C#, but C#, all functions have to be contained within a class.
Python, not so much, so they're a little more flexible in that regard.
Generators, lazy functions, things that use the yield return keyword in C#, or maybe the LINQ extension methods like Where and Select and so on.
You'll see that Python has something like this as well these great functions that are super easy to turn into iterators or generators so we're going to definitely talk about that.
Ternary conditional expressions, delegates some of the terminology I'm using here is C# terminology but that's not what they're called in Python.
But since you are C# developers I'm going to speak your language as much as possible when we get started here.
How do we define a function that can be passed as an argument or accepted as an argument?
Some of the best delegates are lambda expressions these short little functions that you can pass along without going to all the trouble to create separate functions.
See, Python has great support for those as well.
Closures, when a function captures data and holds onto it for the lifetime of that function which is pretty interesting.
Type systems and runtime types think of Python as a dynamic language but actually all the runtime elements do have types and there's actually some static typing that we can use in the language.
Error handling and exceptions, a lot of similarities here.
Using blocks, the idea of for this little block of code, even within a function just a smaller block to find it using context where code is going to run and then outside of that something important's going to happen.
A file's going to be closed, transaction will be rolled back something like that.
We'll see how we work with these types of constructs IDisposable, and so on, in Python.
Finally, the switch statement.
Python itself doesn't have a switch statement but because it's a very flexible language there's some really cool things we can make, like almost like language switch statements.
So, you'll see you can do some really interesting things there.
Now if you look at this list, these are a lot of the things that you probably think, oh, these are really important to me in C# and every one of them has a great implementation in Python.
See something missing there?
What about classes?
Do we have classes in here?
No.
Don't worry.
Python, of course, has classes in object oriented programming, and all that kind of stuff.
Because it is important, we're going to treat that as its own separate chapter.
So we're going to over the idea of classes here but that's not because they don't exist that's because we're actually treating them with a little extra love and care later.
|
|
show
|
3:53 |
Well enough with that PowerPoint let's put that away and get our hands dirty and really start focusing on the code, and the editors and creating some little applications.
Little in the beginning, slightly bigger at the end.
Before we get into the Python code, we're actually going to talk about some C# code, because almost all the things, not everything, but most of what I'm going to show you in Python we start out by looking at it in C# and then creating it in Python.
So for each thing that we do in the course you're going to have a nice analogy, here's what it was in C# here it is in Python, I can run and play with them both.
If we're over in the GitHub repository notice there's a net, .NET but doesn't work super well in all the systems it would hide that directory for example.
So just net is what I'm calling it right here and we have a Python section which is empty.
I'm going to mostly stick to working on macOS not because Python doesn't work over on Windows but because it's a little bit easier for me and I'm going to give you a cleaner, better presentation of Python on macOS than I will on Windows just because that's where I work and all the hotkeys are broken for me on Windows.
I worked on Windows for many, many years but recently the hotkeys there have been erased so, I'm going to just stay on macOS for the most part.
We are going to do a little work with Windows 10 and APS.NET, at least in the beginning we're going to be here on my Mac.
Now, doesn't mean we can't do .NET, but we don't have Visual Studio for Mac which is pretty cool they have this these days.
We're going to work with the various programs.
So this is the code that is already existing it's in the GitHub repository to start from and we're going to look at it.
We're going to look at this chapter three language here.
We don't have code for chapters 1 and 2 because those were just intro things but I decided to match it up with the videos.
Over here we've got this shape of code so here's a simple, simple program that we're going to run and basically we've got a bunch of different things like here's for typing here's for turning our expressions and so on and they each have a run method which I'm calling from program.
Yes, I could change the run configuration, the project about which one runs, but I find just always running this file and just un-commenting and commenting out different parts of a program is what we're going to do.
So we're going to run this shape of code this should look familiar to you this should be very simple code.
If you're a C# developer there's nothing fancy going on here.
So we have a run function, it prints out on a single line and asks you a question at the end of that line what is your name?
And then it just passes that off to some method, OK?
So in this, the method here takes a string, a name and it checks if the name is, if you need to lower it trim it and all that, you know, ignoring whitespace and capitilization.
If it's Michael it says Hello, old friend otherwise it says Nice to meet you and it's using this $ for string interpalation to print that out, instead of format, which is pretty cool says My name is C#!
Let's run it.
So here it asks what is your name?
My name is Michael.
Hello, old friend.
We can run it again.
My name is Bill.
Nice to meet you, Bill, my name is C#!.
Very exciting, not super surprising to you, right?
So, this is standard C# code we have a name space, we have a class and then we have static or instance methods here's our static run, and then, obviously our other static method.
Our if statements have parenthesis around them and there's curly braces creating them.
Obviously for if statements and one liners that's not required, but in general it is and then all of our statements are terminated with semicolons, you should know this, right?
But this is very different than Python.
Now we have type names here, right there's a type definition about the argument as a string and it's return value is void, and so on.
|
|
show
|
3:01 |
All right, time for some Python.
You've seen the C# code over here and we haven't looked at most of it but you can imagine the web part the website that we're going to build in Python the testing parts, the testing we're going to build.
We're not starting from any code at all when it comes to Python.
We're going to write every single bit of it during this course, which I think really helps you internalize it and see how it's put together in the whole flow.
So what we're going to do is we're going to create a new project, we already started with our C# code with our solution and our projects and our project structure and all that over here so we're going to do that right now for Python.
With Python, I could just create a file here and start working on a directory and then put a file in it potentially.
There's not really a project structure I have to create.
But I want to use something called PyCharm.
PyCharm, which I already introduced earlier is a really excellent editor.
If you don't want to use PyCharm then also VS Code with a Python plugin is super good similar setup here anyway.
For Python, what we're going to do is you want to create something called a virtual environment.
When you install Python, it gets installed into your system and it has some packages and libraries set up and some configuration.
But if you want true isolation so this project has its own set of files and is completely isolated from all the other things that might have happened on your operating system what you do is you create this thing called a virtual environment.
So we're going to create a virtual environment and then load this into PyCharm.
So let's just jump into the terminal over here on it's a little extension I have called GoToShell and you see, there's nothing here yet.
What we're going to do is we're going to create what's called this virtual environment and the way we're going to do that is we're going to type Python3 here.
On Windows, you can, depending on how you install it you may or may not be able to type Python3 so just do Python but here I'm going to do Python3 -m to run a module.
The module is called venv for virtual environment and the folder we're going to create is also venv.
Now we have this folder here and this is basically a copy of Python and the entire Python run time.
Sort of symlinks back, but basically it's a copy of Python think of it that way, at least conceptually.
Now in order to use it, we have to activate it.
So we can say source venv/bin/activate.
And notice our prompt changes.
On Windows, don't have this course source concept you say venv\scripts\activate.bat I don't know why those have to be different, but they are.
Okay so now we have this as our active Python here.
We can load up this project into PyCharm and when we install stuff like libraries we want to use think NuGet packages and other stuff like that what we're going to configure is this little local version this local version of Python, so whatever we do it's absolutely exactly as we want it and it's not affected by the other stuff.
Not technically necessary but a very good practice so we're doing it right at the start of this project.
|
|
show
|
11:57 |
Our Python project is ready to create in PyCharm.
We've created our virtual environment here and on macOS, you can load files and directories by just dragging and dropping them on here.
On Windows, you would just say File, Open Directory but we get this little shortcut here.
Now, we don't have any source files at all.
You do want to check really quick in the terminal that your venv got activated.
Turns out, in this case, annoyingly, it did not so we can go really quickly to the settings to the project, Project Interpreter and sometimes PyCharm detects it, sometimes it doesn't.
I don't know why.
You see it's detected it here and suggesting it but these are our projects we got in there or our libraries that just manage installs.
This is kind of like NuGet for you all.
Okay, so now, check one more time, notice this is here.
We can ask which Python.
On Windows, that's where Python, but it's the right one.
So, what we can do is we can come over here and create a project just like we had in C#.
We called it ch03_lang I'm going to make it lowercase.
It's more of a convention in Python.
There's more lowercasing of things so chapter, let's go with ch03_lang 'cause we're going to have more than 10.
And in here, we can add a new Python file and let's call it program.
So, let's remind ourselves it's been a whole couple minutes about what we're going to try to put here.
This is what we want to simulate or to recreate, rather, in Python.
We want to have a method that runs and is going to ask, what is your name?
Get that, pass it to another method check that for lowercase and trim it and I see one of these two things based on the outcome there.
Alright, so how do we do that over here?
Well, it turns out to be pretty easy.
We're going to come and we're going to define a function let's call it run at first.
Let's call it main; that's a little more common in Python.
Like so.
Now, the way we define a function is we use the keyword def.
Either it's a member function of a class or it's just a standalone function we define it with the keyword def.
We do not specify a return value.
More on that later.
This one doesn't take any arguments and instead of having curly braces this is where it gets a little funky instead of having curly braces like this you can see PyCharm does not like the curly braces.
It's like, no, no, no, something is really wrong here.
We do use them but for other things in the language.
It's as you expected, a colon.
So, what we do is we'd write code and we define blocks.
What would be the curly braces in C# in Python we'd say colon.
Then what we do is a little bit funky the very first time you see it but it turns out to be really nice over time and actually, I love it a lot now these days is we indent four spaces.
So, see when I hit Enter, PyCharm knows I indent four spaces and automatically did that for me like those are four spaces right there.
Now, you might think, oh, my gosh spaces, not tabs, that's crazy.
Well, it doesn't really matter so much because you'll see that editors make this more or less, transparent.
If I hit Back, notice those four spaces got deleted.
If I hit Tab, it goes forward four spaces like so.
So, basically, and also, when I hit colon Enter it added those four spaces.
All the editors, VS Code, PyCharm they know all about this structure and they're very good at helping you write it correctly or lint it back into the right shape if it's not there.
So, what did we do?
In C#, we said, next thing was get this what is your name bit here, we're going to ask that and then we want to get the name as a string.
So, we're going to define a variable called name.
We don't say string name, we just say name leveraging the dynamic types of Python and then we want to print on a message and get that value back from the user.
That's super simple, we just say input and you put the string.
That's it.
So, we can go and run this now and just for starters, see what we're getting already.
I encourage you to run, make little changes run, make little changes, and so on.
So, what we can do, this is not really an important warning what we can do is we can just right-click over here and say run.
It's not going to give you the outcome that you're hoping for but let's just run it.
So, it ran it with Python from the virtual environment and you can see the argument way at the end is that file and nothing happened.
That's weird.
Well, it turns out that Python doesn't have this concept of a static main void that is the one and only static main void.
You have to call the function at the very end of the file like it is at startup of the application.
So, if we run that, does what is your name?
Cool, I'll say Michael.
It's cool with that, and we could print it out and see what we got there, but notice it did that little bit we were hoping for.
However, because sometimes we want to run this only if we're targeting the application as the program but we might want to use this as a library and pull in other functions, there's this convention which is, albeit weird, but you get used to it is we put a little if down here and there's these implicit values that are defined for all the Python files, like __name__.
We ask if that is __main__.
Alright, so this is weird, but you get used to it and if that's the case, then we'd run main.
And it's just not liking the spacing.
Let's run it again.
Operate the same.
There we go.
This is just the convention.
This is the static main void of Python and because this is so common, I've created within PyCharm, a little thing called if main.
If I hit Tab.
Oh, I didn't create over here, let's go create together.
It was in my other profile.
Let's go over to Live Templates and let's just go to Python and let's add one.
So, it's very common to have a main and we have this test down here.
If...
Alright, it says we also have to define a context.
This is valid within Python.
Alright, so now we can just say if main like this.
So, we're going to use that throughout the rest of class which is why I took the time to show you that because we're going to write 20 or so programs I don't want to type that in, I just want to say we're just going to do this main trick.
So, remember, what we had was name equals input what is your name?
Like that.
Okay, so that was step one.
Now we want to define a second function up here and it was called some_method, so we say def some_method.
In C#, we have camel case, like so, it would be like this.
In Python, we have, well, snake case, which looks like this.
Okay.
This is the convention in the language so that's how we're going to do it.
And then it takes an argument name but we don't say string name, just like we don't up here.
We just say it takes a name.
Excellent.
Now, what do we have to do after this?
We had an if statement, so we're seeing a little bit about if.
We'll come here and say if, and then a boolean condition and we don't have the parentheses, we omit them.
I'll say some more about that in a second but let's do this test here.
We have name dot.
Now, it doesn't give us a lot of help here.
We can trick it, and I'll show you how to do this more in a second, but if we go down here and type this, notice we actually get autocomplete for all the string information.
We can actually specify some type information here and PyCharm would totally pick it up.
For some reason, it's not.
Oh, hold on, I think if we were to call this it might actually automatically do it.
Will it?
There you go.
So, you can see, PyCharm's actually understanding the types the real runtime types, that are being passed around as we're using them here.
So, now, it's like, oh, you're calling this function with a string, that must have string operations on it.
But there's a way to actually specify the types if you want as we'll get to.
So, what have we did?
We did a trim, which, in Python, is a strip and we did a ToLower, which, in Python, is just a lower.
We said if that's equal to Michael, like that define a code block here, like so.
I'm going to say print, that's like Console.WriteLine hello, old friend.
We're going to do it just the same.
Colon and print.
Nice to meet you, and we had the name here like this, right?
Well, it turns out, the string formatting in Python and C# is almost identical.
We could do a dot format and pass a name value over just like you can in C#, but we can also remember, in C# we had a dollar, in Python we have an f for formatted strings, and now notice name is lit up like a variable.
And I'll also have the other one print my name is C#.
Exclamation, you're not Python.
Here we go.
Alright, well, there's a little warning that we need two lines by convention between those so we can tell it to autoformat.
See down here, you can see the code the key that I'm running, the hot key there.
Okay, I think we're ready to run it.
Have we created our C# equivalent in Python?
I think we have, what is your name?
Michael.
Hello, old friend.
Let's try again.
Sarah.
Nice to meet you Sarah, my name is Python.
Super cool.
This probably looks a little bit weird to you if you've never seen Python and you're like what is this space stuff?
This space is crazy.
But like I said, it actually, you don't really deal with spaces even though they're technically present in the language.
These colon blocks are interesting.
The if statements here, notice, I could actually do this if I was missing my parentheses I could put them in there and it would still run still runs okay, but the warning is these parentheses are not needed.
You can get rid of them.
Actually, a lot of things like that are optional or just missing in Python, and it's really, really nice that you don't have to put them.
First it feels weird, later it's weird to require them because if they are not really required why do I have to type them all the time?
Okay, so this is that program.
Let's go and look at it again in C#.
I'm going to try to put these things side by side for you.
So, let's see if I can side-by-side this here side-by-side that.
Alright.
Well, you got to forget this part for a second 'cause there's a separate file in the C# version that represents that.
This is the program.cs that has a static void main.
Look at this.
The Python one probably felts a little weird to you if you haven't seen it before and if you have, maybe it's fine, maybe it's no big deal but if you haven't and you're like wait a minute, there's no parentheses here and this colon thing is kind of funky and so on but there is something nice and beautiful about that code on the left compared to how many symbols there are on the right.
There's so much syntactical stuff in C# and whatnot that I feel like it almost makes it hard to read because it's just covered in a lot of curly braces and brackets and static void this, static void that and so on.
But let's just look at them side by side.
There's a lot more stuff on the right-hand side that just doesn't need to be there.
These are the ways that we define code structure in C# curly braces and semicolons and in Python, colon and whitespace.
If I come over here and I unindent, print other this is not in the main method.
This is a separate line of code that is kind of after main is defined but then just executed when you import the file.
If I run this, you'll see, it first prints out other.
Oh, I took away that thing at the bottom, didn't I?
Because I wanted to compare them.
But now if I run it, you'll see other and then what is your name, right?
These are not in each other.
It's the indentation that defines the structure.
It takes some getting used to, trust me but once you get used to it, it's actually delightful.
|
|
show
|
2:34 |
Now lets quickly review the shape of a Python program.
Or the shape of code structure within the Python language.
I want to make a quick comment though about these little review segments these metacomment if you will.
Some folks have said, hey we've just talked about this why are you showing me the same thing?
I'm going to show you the structure and so on and highlight a few important things.
One, well, I think review is important and we talked about it for twelve minutes let's summarize it in thirty seconds.
I think that's helpful for remembering.
The other thing, though, this is meant to be reference material for you.
These little tiny things are super easy they're going to be separate videos you can just go back and jump into and look at.
Oh, how do I do iteration with something like IEnumerable collection?
I'm going to jump into that concept review really quick and just catch that in two minutes, right.
When you're coming back a couple months later most video courses have a really bad or they're just absent of any reference material so these are your little reference points you can come back really quick.
Stepping out of that metacomment there let's talk about the shape of a Python program.
Python defines code blocks, technically called code suites but I like to call them code blocks so I'm just going to keep doing that and it uses colons to initiate them and then white space to define what is in it.
So, here's a single source file that I've put a bunch of colored boxes around to highlight each code block that is happening, code suite if you will.
So, everything here, these are all code suites.
The blue boxes, they're defined by the def some_method and then a colon or def main colon and then anything indented in four spaces or more is defined to be within that code block.
And then, we have an if statement in there and a colon, and we indented four more.
That's a print hello old friend.
Then else, that's a not indented, different code block colon, indent, that's a bunch more.
We have two print statements in there, and so on.
So, this is Python's alternative view of the world on structuring code.
Instead of using curly braces and semi-colons use colons and white space.
A couple things to note: Obviously no semi-colons, code blocks start with a colon but white space really, really matters.
There are no braces, there's no parentheses tab as an item inside the file is not your friend.
These have to be spaces, but all the important editors know that, and when you press tab, it puts four spaces when you hit delete, it removes four spaces things like that.
There's actually an exception like, hey I found a tab in your code exception type in Python.
Alright, so this is how Python defines the structure of code.
It might seem unusual at first but trust me, you'll get used to it and it actually works out really nicely.
|
|
show
|
2:04 |
Next thing in C# I want to look at and then create in Python is loops, different kinds of loops and iterations and things like that.
So here we have a simple C# program and it's going to start having a loop here.
It's going to come through and say while true, it's going to ask the user, what is your name and it's going to greet them as long as they enter a name.
If they don't, it's going to break out.
I guess we could really shorten this if we wanted as Visual Studio was suggesting, there we go.
I just want to read it in, check it, and break out.
We're also then going to take an integer array here.
We have a pre-defined nums, it contains a bunch of integers.
1, 5, 8, 10 and so on.
And we're going to use one of the nicest features of C# in terms of iteration stuff, I believe is the foreach loop, right?
So much better than the for loop, which is down here.
So in the foreach loop, we're just going to round.
The next number is this.
But it has some shortcomings like maybe I want to say the first number is this the second number is that, the third number is that.
We're going to fall back to our for loop and get that number, but also have the index say the first, second, and so on.
Let's just run this program real quick.
I changed program over here CS in this project to run that one.
We run it.
It says, what is your name?
Remember, it's going to ask me my name and keep greeting me as long as I say a name so I'll say my name is Michael.
My name is Zoe.
And if I'm done, I hit Enter.
It's going to go to the next section and just work with the foreach loops.
So here it loops through those numbers.
The next number is 1, 5, 8, 10, 7, 2, great.
The next, next, next, how great is that?
We said no, no, no.
We're going to use a for loop and we're going to have the first number as 1 the second number is 5 now obviously it should be first, not firth or however you pronounce that right there.
But we're not going to sweat the details.
We could obviously add that adjustment for the suffix of these letters but first is 1, second is 5, third, 8, and so on.
This project in C# works with most of the structures of looping that we have in the language.
|
|
show
|
7:30 |
It's time to write the Python loop code and explore the different types of loops and ways we can work with iterable objects in Python.
Now, quick note, I renamed this from program to L01 For Language Part 1 Structure I'm going to create and partition this a little bit.
So, I'm going to have L02_iteration that's going to be the way we want it to run.
Remember we always start by having the main method and then that if thing that I created with if __main__, we going to write it like that really quickly.
And in our main method, we're going to do some exploration.
You could actually just put the code right here you don't need a method but I don't know, that's just seems wrong to me so I'm not going to do it.
So, let's do what we had before, we had a while loop and in Python, we have while loop as well.
But we have a for true written like that we don't have that in Python.
We have a capital T, True.
So True and False, the Booleans are capitalized.
So, that's fine.
We wanted to find the block of code here and what we did is we had name variable and we ask the question What is your name?
So, What is your name?
So, we also at the very beginning, we had a Python iteration demo.
Something like that, right?
Then down here, we had an if statement, we said if we had string.IsNullOrWhiteSpace We can do something like that, we can just say if not name like this, you'd have more sort of truthiness in objects in Python than you do in C#, everything has to been exactly a Boolean.
We can just say, is this string False, right is it empty or something like that?
We can break after there.
Otherwise, we going to do print with a formatted string Nice to meet you + name something like that.
All right well, that's pretty straight forward isn't it?
Lets run it.
That looks really similar, exchanging the code structure a bit as we had before.
So, down here we can say What is your name My name is Michael, Nice to meet you What is your name, My name is Zoe.
If I enter, it breaks out, is done.
So, that's this part right here.
I'm going to comment that out for a second so we just focus on the next bit.
We had some numbers over at the C# version and I'm just going to grab amount, so we have the same thing, so just going to copy those over.
Now, we are going to define some numbers in Python an integer array so we had nums, we don't say the type, it's dynamic at least for now unless you want to add the types, like I said we'll talk about that later.
And we put brackets, paste.
So, this is not actually an array, in C# we had a static array, here we have a list.
So, this is like list of int or more like list of object equivalent.
In the C# equivalent would be the list of object here, all right, pre-allocated.
Now, what we want to do is we going to go and have a foreach loop type of thing, and I told you, it was one of my favorite thing about the C# language coming to it from C++ is like Whoa!
This is so much nicer.
Turns out Python has exactly the same construct, the rules about what go into the loop are the same and has to be iterable kind of like it has to be IEnumerable in .NET But it's not called foreach, it's called for.
So, the way it works is that you say for like this.
So, this is like foreach in C#.
And then, what do we have, we just print it out the next number is n.
Well, that's pretty straight forward right?
Really nice that there is something like if foreach loop, words are not exactly the same but what's really interesting is that there is no for that does not exist.
There is none of this.
Alright, so I'll put this down here.
It turns out that we can simulate this kind of loop in Python but there's not an actual integer for loop.
This is the only kind of for loop in Python as is for.
Alright, well, let's just run it and see if it works.
Boom!
The next number is 5, 8, 10 just like we had in C#.
What we did though in C# is that...
Well, we wanted to say the first number is this the second number is that and so on.
So, we said alright, fine.
We'll break down using the for loop.
We're going to do something even better in Python.
Check this out!
So, Python has this concept of tuples this group, two or more things that is kind of like a list, you can iterate it and stuff but what's really interesting about it is I can have like two variables to find like this.
Let's say x, y = 1, 2.
This would create a tuple and assign 1 to x and y would get 2.
We can use that in this little loop here I could say I want index and the number and I can not iterate over the collection but I can iterate over the enumeration of the collection.
Enumerate nums and I can even say start = 1 and then we can come to here the idx how we had it, numbers this.
We don't even have to plus one because it automatically goes 1, 2, 3 not zero 1, 2.
Alright, let's run this.
Let's put it also some space between them.
How cool is that!
That is a super slick way to loop over those and it's much better than what you have to do if you resort to this.
So, you can see this for in loops are really really flexible in Python.
Final thing is sometimes you want to just do a thing 10 times right?
And this for actually it's really useful for I want to do limit times.
We also have that in Python and it leverages this idea and natural support for ranges.
So, I could just say for n in range of one to 11, it's a...
So we go one up to 10 inclusive, not the last one and I can say print again or this time or I don't know, something like that.
Let's put a little separator here and let's actually make that just run five times.
So we run this again.
Then, 1, 2, 3, 4, 5 times we said this time.
All right, so that's really cool.
This is like your for right?
But we're not using this n here, there's a convention in Python to say there has to be a variable define here, you can't just put nothing here, that's going to be an error.
There's a variable that has to go here but I specifically am stating I don't want to use it, I don't care what it is and that's underscore.
So, all the Linters and code-checkers and stuff when they see that underscore, they will not warn you that underscore is not use but they would warn you that n wasn't use in that case, potentially if it weren't use up here, right?
So, let's run it one more time.
Same thing.
Alright, so this is loops in Python and super good news, your for-each loop, you still got it and it's actually I think a little bit better than .NET.
|
|
show
|
1:31 |
C# has the foreach loop.
Python has the for in loop.
So here's a list of numbers that we were working with we want to just loop through them or any collection, anything that is iterable in Python's terms.
We just say for n in nums: and then we work with n.
We just loop over a collection like objects.
These can be lists or arrays.
These can be strings these can actually be classes that just implement the right interfaces and we can treat them like this.
If you want the idx that goes along with an item in your for in loop we just use enumerate.
So, enumerate(nums, start=1) instead of just for n in nums.
Then we have this tuple projection we get a tuple back when we assign it to two variables its elements at two variables the idx and the n and here, we can say, we can work with the idx n in each time through the loop.
We also saw that Python does not have a numerical for loop but we can get back to something really, really close to that, super easy.
We can just say for i in range and we give it a range, zero to 100.
And that'll go from 0 to 99 stepping by one, right?
And then, we just work with i and change things like the step and so on so there's a lot of flexibility there.
But there's no traditional for i = value i < such and such that you get from C#, C++, Java and so on.
Alright, that's it.
Looping in Python is a joy.
|
|
show
|
2:22 |
The next area that I want to focus on for you is functions.
So, we've already really sort of been using functions in Python.
You saw we've had our main method and our some_method and took an argument, but I want to focus specifically on what are the parameters, what can we do in terms of flexibility, return values and all sorts of stuff like that.
Method overloading, and so on.
So we're going to write, at the first start exploring this we're going to write a little game, it'll be a lot of fun.
And then we're going to come back and focus on specifically on, method overloading type of operations.
So here we have a program in C#.
And let's just run it, I think you'll enjoy.
It's a high low game, so it says I'm thinking of a number between one and 100, how many steps can you guess it in, what number am I thinking of?
This is where you apply your binary search algorithm 50 too low, 75 too low 90 too high, 85 too high 80, 76, 77, 78, 79 it must be.
There it is, of course, I was thinking of 79.
Well, my little binary search didn't work that well and I kind of broke down there when I got to the 70s but nonetheless, we're going to write this program in Python it does things like go and get the guess from the person and so on.
So, it should be a lot of fun let's just really quickly look at the code here before we go talk about it in Python.
So here's our main method.
We've run it and it says ShowHeader that's the big little printed bit at the top there that says, Welcome to the High Low Game.
We create a random number generator and we get a random integer between one and a hundred.
And then we also keep track of how many times this happened.
So when I go through, want to get a guess if they don't give us a guess it's a knowable editor, if they don't give us a guess then we could just tell them, hey, no, you've got to give us a number, ask 'em again, something like that.
Count how many times they give us real guesses and evaluate the guess, right here we're passing the value of that knowable type in the number and then if they get it right they break and we say, good job, you did this many times, all right.
So pretty standard C# down here, easy six lines for what it's worth.
We'll want to come back and look at this in Python as well.
All right, so this is the game that we're going to recreate over in Python to explore working with the functions.
|
|
show
|
10:09 |
Here we are on the Python side over in PyCharm.
And we're going to create that high-low game again.
We're going to start by defining our main method and the __main__ convention.
So that's my little live template we created.
And the first thing that we did over there is we said I'm going to show the header.
That's the big welcome to the game.
So we'll say show_header, like that.
And this one calling a function that has no parameters and we don't really care about the return value.
And PyCharm you see will actually create this for us.
I really want to do it just once but I don't really like how it does this.
So notice it went and created the function just fine.
If you want it to be empty, you can use this word pass and that's also fine.
But what I don't like is that it put it above main.
It put it above main because that's the safest option.
And it's not convinced that we're doing this in the right time and place.
But I like the main to be the top and then all the stuff after it so I can see the overall view of what's happening in this program and then the details.
No, I'm going to put this down here.
Alright, that's why I don't use that little auto-generate bit thing so much.
And here we just have a little print statement.
I'm just going to paste it 'cause there's no real value to watching me type this.
So it just changed the word C# to Python here.
And that's what we got.
So we'd run this one.
Notice it's got a little header and that's it so far.
Okay, so what's next?
We can put that away.
The next thing we did is we actually generated the guess, the random number.
Okay, so what we did in .NET is we said Random rand = new Random() like this.
Alright, that's what we had.
And we had to have at the top using System for that to work, right?
Python has something very similar.
If we want to use the random library from the standard library that comes with Python, we have to add an equivalent statement like this to say we'd like to reference or refer to this part of the system.
So the way we do that in Python is we say import.
And there's all sorts of stuff we can put up here.
One of them is the random library.
So we say import random.
And down here we would say the guess.
Alright, this is sort of two steps here.
We also had the guess is rand.Next or something like this.
I forgot the function.
Alright, so we had that.
So then here we're going to say random.randint.
Now, this takes two values.
It's not super-helpful about what it is.
But it's two numbers, I believe inclusive.
We can always say view quick documentation.
Yeah, so inclusive A to B.
So we want to go one to 100, like this.
So that's the guess.
And just for a moment, let me just print the guess.
Now, obviously you don't show it or it's a pretty boring game.
But just so you can see that we're making progress here.
Alright, so we guessed 83.
Good, we're on a good path.
I'm not going to do that again.
Then what we did is we went around and around asking What do you want to do?
So we had a while loop and we said while true.
We're going to say the guess is equal to 1 and get the guess from the user.
Alright, so we have a function that's going to return a value back here.
We also had the count.
That equals 0.
And each time through, we would increment this counter, right?
So we'll say, we wanted to say if they didn't give us a guess, just ask them for a guess again.
So if not guess.
Before we checked whether it was null and things like that.
We can just say use the truthiness of this here.
It'll return none or an integer.
And we'll say continue.
Exactly like C#.
Now, we want to make sure, okay, we're recording this check here so we're going to say count.
In C#, we had this.
Python, for some reason, doesn't have a ++.
I really kind of wish it did.
So we do +=1.
Basically the same thing but, you know, ++.
I'm kind of a fan.
And then we need to evaluate this guess.
So we'll say if evaluate_guess.
So when I give the guess that the person gave and we'll also have the, call it the guess.
Let's rename this.
Use a little refactor rename.
Say we're to current rename all the, I'll put this to the number.
There we go.
That's better, right?
We're going to evaluate this with a function that doesn't exist yet.
And if it's true then we're going to break out.
At the very end, we're going to do a print statement that says something like this.
You got the number in some number of attempts.
Thanks for playing.
Alright, so the last thing to do is write our get the guess function here.
So let's go and say def get_guess.
And remember what we do in get_guess.
Well, a couple of things here.
Sure, it's easy to just ask for the number from the user and then just convert it to an integer.
But we want to do some validation here, right?
So we'll text.
We'll put text as input.
What number am I thinking of?
And then we'll put, let's put the value is going to be convert this to an integer from text, right?
This is like int.TryParse.
First thing we want to do is make sure they're guessing between one and 100 so we'll say if val is less than one or in C# and a lot of the C languages you say || for or, right?
In Python you just say or and and, right?
And would be the other one there.
And it would say that or 100 is less than val.
Alright, say nope, this number's not in the right place.
It won't return Python's equivalent of null.
We don't have null here.
We have none but the meaning is the same thing.
And if that works, we were able to parse it into an integer and it was like this so we could return the vowel.
But just like .NETs and .Parse, this throws an exception if you put in something like that, right?
That's going to be exception.
So we got to do one more layer here.
We're going to talk more about error handling in detail in a minute.
But let's just deal with this like so.
So if you try to do this and it fails instead of catch you have except I'm going to return None as well.
So those are the two cases.
Maybe we'll put a little note in here as well like print.
Whatever that was, it's not an integer, okay?
And just so it doesn't freak out, right.
It's always defined somehow.
Okay, so this should get our guess for us here.
And the last thing to do is just to evaluate it.
Let's copy this over so I can not have to type too much.
And I'll just put it right here.
Again we say def.
Now we take the two parameters, input here.
And just for the sake of time, let me just drop in some code real quick here.
And I'll just make that a number.
So we say if the guess is equal to the number, that's it.
I was thinking of the number.
If it's too low, too low, too high, and so on.
And then we're going to return True or False.
Is this equal to the number?
And by the way I have this type of font installed on my system that will convert things like double equals to have, basically look like one long equals.
And if you say not, like if I say not equals and I put that together, it slashes through it or less than, oops, take that away, less than or equal to and so on.
So you might see some funky characters.
Anyway, it's just that font there.
So it just means two equals, right?
Nothing funny about that.
Okay, so this is our evaluate guess.
I think our program should run.
We've imported our random library.
We've called a function on it, got the number doing our guesses.
Yeah, let's try it.
What number am I thinking of?
I'm thinking of 50.
That's too high.
Oh, 25, 15.
Jeez it's low.
10, 5, yes.
It was 5.
You got 5 in 5 attempts.
Thanks for playing, bye.
That's a pretty good little program, huh?
Not too bad.
And let me take, let's just account for the fact there are two comment lines there and I think that that's it.
Right, so what do we have in C# for this?
We had 86 lines.
Kind of the empty line.
In Python, 63.
That's 23 fewer lines.
Format this?
No, everything's formatted correctly with the right spacing line separation, all that.
Alright, so here is that game.
Actually, remember, take this two out as well.
Super, super-simple, really nice.
The way we do arguments is we just pass them in like it's almost exactly like C#.
Same here, you just don't define the types.
There's actually a lot of flexibility when we get to that in the next section the very next section.
But if you don't care about specifying the types or default values or stuff like that, you just say the parameters here.
The other thing is you never specify the return type, right?
In C# you always have like string or void or list of task of I don't know, int, right?
Something like that.
We don't put any of that.
And the reason is one, we don't say the typing necessarily.
It's sort of optional these days in Python.
But the other is every function has a return type.
What does this return?
Well, this returns a boolean because that's what we're returning.
But what's less obvious, this one also returns something.
This is, there's an implicit return None.
If you don't specify a return type for a function its return type or the, actually the return value, is None.
Every function has a return type.
There is no concept of a void function.
It just happens to be they return None or null when they don't specify anything.
All right, so these are the basics of passing arguments into, calling, and getting the return values of functions.
It's actually super, super-similar to C# with the exception of this implicit return type.
|
|
show
|
0:56 |
Creating Python functions always start with the key word def.
We're defining a function.
We give it a name.
This one we're calling evaluate_guess following the Python naming conventions of snake case not camel case.
Then we can optionally in this case, we want to take some arguments here So we're defining some parameters, guess and number.
They're positional, but you can also use them keyword style.
And then these functions have the implementation and they always have a return value.
So here we're explicitly returning the single value that we can, which is True or False.
Is the guess equal to the number or is it not?
But if we don't specify a return value the value will be None.
So if you think of functions as always like returning nullable of something something to that effect even the ones that you would conceptualize as void functions, those return None so every function always returns something just sometimes None.
|
|
show
|
2:46 |
In this next section, let's look at functions and how we can have them behave differently depending on the arguments we pass in.
So for example, here's one called SayHello and we can just call this with no arguments here's one where we can call SayHello("Zoe").
I'm going to say SayHello("Zoe", 5), and then "Zoe", 5 and then some arbitrary number of things, doesn't matter how many there are.
I guess I'll put four there.
How do we do that?
And here we can also have it behave differently on the type of argument we're passing in integer versus a double.
So this is part of C#'s static typing it's pretty easy for us to do this and it's flexibility in defining functions.
So let's look at it first in C# and then we'll go recreate this as much as we can in Python.
So let's just run it real quick and we can see what the output is.
So when we call it void, it just says hello there friends, and it had some default values and it has extra values it could pass over.
These are the params, arguments that we'll see.
If we call it with name, the name's value is Zoe so instead of saying friend, it says Zoe and then we called it with names and 5 times so it says hello there Zoe, and this value is 5 and so on.
And then we had, I didn't update this 1, 2, 3, 4, so the extra values that got passed here were 1, 2, 3, and 4.
Then when we call it integer, we SayHello(5) it actually does that five times.
And if we call it with a double it repeats the number of times but it says hello there double times.
This is a totally different function if we pass a double than an integer which is also different than any of these.
But these actually turn out to be the same one with default values and params.
Alright, so let's look really quickly at that.
So down here for the one that gets used most of the time, we have the name which is required, and we have a times which has a default value of 1 so it's optional, and then anything else that gets passed gets captured in this param object array called extras.
It'd be nice to just print out what that is but it just says it's a list or an array of objects it doesn't tell you what the values are so I had to write this little function in C# to print that out.
We want to call it void, here's the void overload that just says hello friend.
And you see it delegates back to this one by passing friend.
This one, if you pass in times, it says hello there some number of times, and if you pass in a double same basic thing, but it says hello there double time.
So we're able to select between this function this function, this, and this, based on the arguments that we give it.
And we got those different behaviors that you just saw there.
Like that.
That works pretty well, and it's pretty common in C#.
You want to see how to do it in Python?
That's next.
|
|
show
|
6:08 |
Let's see function overloading in Python.
Want to start without with our standard structure here like this.
And in here, remember, what we had in C# was calling it with different parameters.
I'm just going to paste a bunch of coding here so we have something really, really similar, okay?
So we're going to call it, say_hello, say_hello("Zoe") say_hello("Zoe", 5), I think I updated this to have four, so let's do that, and so on.
Now, we're going to comment that one out for a sec let's define this function that we're working with here.
def say_hello.
And then what do we want to do?
Well, let's put a little print statement here that just prints out the values.
First it'll say, Hello there.
So turns out that's going to work great for this one but PyCharm is kind of warning us maybe something's not super about this.
Right, unexpected argument, unexpected argument, and so on.
Let's run it, we'll get through a little bit.
That said, say 'Hello, hi there, hello name' and then it crashes because we got to the Hello name in that one, right there, that's not working.
So how do we add this?
In C#, what we do is we had an empty one, like this and we said, want to call all the say hello one with friend and we had a name over here, Hello, name, like that.
It turns out Python does not have this concept of two functions with the same name but different parameters and see here, there's this problem and it ends up it's going to say basically only one of these is going to survive.
And what actually happens is there's not even an error this is really super frustrating about Python this definition of that say hello simply replaces that one because it's later in the file.
Think this is a shortcoming in the language but anyway, what you're going to find out is it's as if the code was only that.
Well then obviously this line doesn't make any sense.
So this process of how we did it in C# doesn't even make any sense, it's going to go the wrong way.
So what we can do here instead is we can say This is friend.
Okay?
So we'll have our default value but when it's like that, it'll be friend and this one hello, so that's kind of what we did in C# but we did two functions, now we're just doing one with a default argument.
Could've done it that way in C# but I wanted to show you more of what's going on.
say_hello, it says Hello, friend say_hello("Zoe"), it says Hi, Zoey the next one crashes right there.
So this next one is, this is how many times we want it to say hello or something like that so let's...
in here we have another parameter and we have times=1 and that solves that problem.
That's pretty cool.
So with times equals times...
And that'll get us farther, as we'll see.
So it gets us to this, but again when we call up this one, it died.
And we just click here to go right to that line.
How do we deal with this variable number of arguments?
Well, in Python, we-- C#, let's step back we had params, object array, args.
Python has the exact same concept but they don't like all those words so they just put *.
And we'll say args equals args, like this.
Python just prints arrays I don't have to write print array here, that's pretty sweet so if I run this again, I can see the args are like this.
Let's do one more thing here let's suppose in this one we wanted actually do one more line, like this.
Let's suppose we wanted to say val=7 mode=prod, something like that.
If we want to write code like that, here yeah, I think I'll be able to make it work.
Line that order, come down here like this.
We have to come in and say this takes also arbitrary keyword argument so here's our arbitrary positional arguments.
So the way you say that in Python is you say, **kwargs, like this.
Now that's happy up there and we can print kwargs={kwargs}.
This becomes a dictionary where the keys val, value 7, keys mode, value prod.
Run it one more time.
Beautiful, look at that, it's working like a champ.
So here's that additional bit.
So this is an additional level of flexibility of passing arbitrary arguments I don't think C# has I don't remember it having, maybe it does but they definitely have this, but this is also super nice to be able to take additional keyword arguments on top of just the positional ones.
So we could actually pretty much implement this one as well and it's, I'm not going to it says basically the same thing, so we're going to drop it.
Here we go, this lets us have arbitrary positional arguments arbitrary keyboard arguments default values for these, and these are positional ones.
That looks pretty good to me.
You may remember, there were two other ones in C# that was like this, where you said say 'hello integer' and it did something different and it said say 'hello decimal' and it did something different.
Technically speaking, we could do something stupid like this we could say, if is instant, name, and what've we got here an int, print, all right, could do print int version da da da da da, else.
Technically possible, I think it's a bad idea.
I would not, in fact I'm going to delete this.
Just not a good idea, I wouldn't do it.
Basically this type of overloading by type under the one argument, and it's the type that determines which function gets called it's not something that works in Python.
But all the other types, the default values the void ones, the additional parameters the named ones, all that kind of stuff works really similarly to the way you'd expect it does in C#, but this type, this last two here this type of overloading just doesn't exist 'cause you don't base which function you call on types right, it's just not how it works.
|
|
show
|
3:20 |
Let's quickly review functions in Python.
Here, we have a function called named arguments and it has one required positional parameter called name, ironically.
And then, it takes arbitrary positional arguments *args, remember that's like params, object array but we just say *.
And then, we can pass arbitrary keyword arguments to it and that appears in the dictionary, kwargs.
I don't really love that style because it doesn't tell me what arbitrary keywords I could pass.
A way to accomplish almost exactly the same thing like 90% of the time, if you just have default values like this times here.
If you could've said times=1 but you don't have to, well, you could also just say times=1 as a default argument and it turns out the editors give you way more help because if ask what is the signature of named args in the top one, it tells me I can pass anything.
I don't know what type those are.
I don't know the values of them, the names of the parameters are, the list of options, nothing.
And this is prevalent in Python and I kind of hate it because usually what you can do is just name out the optional named arguments as default values and folks can decide to pass them by name or not, right?
So really, really nice to do this second one.
I feel it's better than the top one.
There's a few use cases where you really don't know what could go in there 'cause your passing it along but most of the time, you actually know.
It's just you don't want to say.
So anyway, I kind of like the bottom say hello version over the named args.
One thing you do want to be careful about though with these default values here, these can be dangerous.
So see this say no, as in no, no, no, don't do this?
This one actually is a bit of an anti-pattern and let me tell you why.
If you pass a mutable type or if you set a mutable type for the default value, this could go really, really wrong and let me tell you how.
You might think when you call this function the default value is created and set each time the function is called but that's not actually where the default value is set.
The default value is set when that module, that file is loaded, once, ever, so there is a singleton list that is the default value for names.
That singleton is, well, it's a singleton and that means it's shared by everyone who calls that function every time.
So imagine something happens inside that function where names is altered, right?
An item is added or removed to names if it's empty.
Well, guess what.
Now the default value has that change reflected in it or worse, if this function returns names makes some changes, adds more names and returns that list, then it's open to the rest of the program to possibly add or remove items from that default value so never, never put a mutable value here.
The way you would accomplish this is you would say names=None and then, in that function you say if names is None, names=[], like this.
And that way, you get a new copy every time you call it not the one that got set when you imported the module.
That's super not obvious, so I wanted to call it out and make it really clear.
This seems okay but it turns out to actually be an anti-pattern.
|
|
show
|
3:46 |
This next section, I want to cover something that I think is probably underused in C# and in Python and it's such a cool idea, generators.
This is the yield return keyword and things like that in C#.
So let's look at a real simple example here Fibonacci numbers.
So we have two versions.
We have the naive version of the Fibonacci sequence that you might write and if you don't remember it's 1 and 1 and then you add the two numbers together and you get 2 and you add those two to get 2 and 5 and then 8 and then 13 and then 21 and so on.
Right, so that's the sequence that we're working with here.
Here's a simple standard function.
Return something IEnumerable of integers and you say I would like, I don't know five Fibonacci numbers or six or 1000 or 100,000 and what it does is it creates a list it does the algorithm, puts the items in the list as it computes them and when it's done it returns them, great right?
This is a standard function it returns a basically a list that we can iterate.
However, this is an approximation of the Fibonacci numbers in fact, right?
The Fibonacci sequence is infinite.
How do you know how many to ask for?
Things like that, it can be tricky.
So here's a better one that has no limit.
See while true and it returns IEnumerable and at each step it uses the yield return keyword to return a single integer.
So this is how yield return works in C#.
We yield return an item of a collection.
We say we have a collection of integers and here is one and here is one and here's one and it will just keep generating these until we stop asking for them.
The trick to make this work is we're going to to through them in this foreach loop and get an item out and then if the item is big enough then we're going to stop asking.
So let's go and run this.
Look at it go, runs and runs and runs hits an item, hits an item.
If we put a breakpoint here this is when it gets pretty interesting.
So here we are in our foreach statement and if we step in it's going to do what you expect.
It goes into the function and great so we're stepping along here.
I'll go into while true loop it's kind of crazy that that returns out of there but okay that's how these work.
So it's going to generate the first item which is 1 and it's going to return it and go back to the loop, okay?
There's one and is equal to 1.
We're writing it out and if we keep stepping we step back into our Fibonacci sequence but we jump to this line after yield return.
Notice even though current started out at 0 it is now 1.
And this is not because I ran to this place in the function, no.
It actually resumes.
These are like restartable functions, these generators.
If we step through here, we're going to do again.
Get this, now we're back here and now n is probably 1 again.
It's going to go 1, 1 and then 2 and so on.
So it just keeps pulling them one at a time as we iterate over them.
And the result that we get is it generates all those until we stop asking for it where we said if it's over a 1000 stop.
All right, so this is generators and the yield return keyword in C#.
Super powerful, it's the foundation of things like the lazy evaluation of things like LINQ for objects and so on.
I don't think that many people use it but it's really powerful when you want to start work with a collection or generate a collection or something like but you're not sure how much you want to take.
You want to just pull them one at a time.
Really, really nice.
Yield return keyword, C#, love it.
|
|
show
|
4:09 |
You saw generators and the yield return keyword in C#, was super cool.
Let's see what Python has to offer.
We're going to see, actually, some of the implementation here might be even better than C#.
So, just to jump-start things a little bit I've added the standard counting finite version.
Let's print out 10 of these Fibonacci numbers here.
We can do a cool trick, here we say the end is actually the inline, instead of backslash N backslash maybe backslash R backslash N, or just back backslash N is actually comma space.
So we'd run this, and you run the correct one what do you get?
Get a cool thing like that.
Okay that's neat and those look like the Fibonacci numbers to me.
And of course there's the implementation.
But this is the kind where you ask for a certain amount and we saw that we can use generators in C# to make a really cool version of this right so let's see what we could do there.
Go like that, and we had while true and there was some stuff in here we had this but we didn't have the list we just used this yield return keyword to say here's an item.
Maybe we could shorten this, to like this if you wanted.
A little more condensed say here's our starting state 0 and 1.
I didn't use the word next, because next is a function that's built into Python and it would conflict with it so just this of course that's capital.
So check this out, here's the implementation in Python you can see it below as well.
So you say, use your tuple, packing and unpacking so current and nxt is equal to nxt that's the value of current.
And then nxt = nxt + current.
Super cool.
Now how do we return this and generate this iterator type of thing, that we saw in C#?
In C# we said yield return item, here we just say yield item, that's it.
That is the whole implementation of the infinite Fibonacci sequence.
And by the way, integers in Python are effectively big ints they can be billions or huge, huge, huge numbers that don't even have names without overflowing unlike say 64-bit integers or longs in C#.
Now this, is obviously not going to work we passed the value here so we need to do something like this, if in is greater than 1,000 break.
I think to be exactly precise, we had that before.
There we go, let's run it again.
Oh, just like that, it's exactly the same, except for let's do a little side-by-side here.
See if we can fit it on the screen.
Not really but we can at least get the for loop on there.
Get it up so we got the for loop.
Check this out folks.
So here's the C# version, we're doing all of this and then here's the generator and the implementation in the Fibonacci, look over here, here's the top function going through them, if it's the same break out.
Do the print with a comma on the end.
Here's Fibonacci, there's the Fibonacci implementation versus this in C#.
I mean it's glorious you guys.
It definitely is a little bit different structure and it takes a little bit of getting used to but there's a lot of examples like this where you're like actually that is really simple and clear and this is kind of symbol soup, there's symbols everywhere.
There's like this generic type up here, there's the static and the public, and the curly braces, and the semicolons and the integers and whoo yes they have purpose in there they're not lacking value but they definitely sometimes obscure the essence of what you're doing and I feel a lot of times having many years of experience in C# and in Python really the Python stuff is often quite clear.
We are missing the type information like do you really know those are integers?
Hang tight, we're getting there.
We're going to cover these foundations first.
Here is generators both in C# and in Python really only major difference is you don't say the return type of function so you don't need IEnumerable as that cause you can't even really say it yet.
And instead of yield return, you just say yield.
Super similar right?
|
|
show
|
1:01 |
The essence of a generator both in C# and in Python is to loop or go over a sequence and then somehow indicate here's one, here's one, here's one and the functions can basically run until you say here's an item, it returns it back and then when whoever's consuming the collection asks for the next one, it restarts and runs to that point.
Here's the Fibonacci implementation for the infinite series in Python, and the way you do it is you just use the yield keyword.
I go through and set the initial state we've a while true and that one line is the implementation of the sequence to generate the elements and we just, after each one, we say here's one.
That's it.
We can consume it just in any for in loop or by passing it to a list.
It's not a good idea to pass an infinite sequence to a list, but if it were finite if there's some reason that would stop, you know it wasn't a while true or there's a break or something we could actually do it that way as well.
Basically generating something that is iterable Python's equivalent of IEnumerable.
|
|
show
|
1:36 |
Ternary expressions are like short one line if statements.
So here we have a program that goes around and asks the user to enter a number between 1 and 1000.
And then it parses it, and then here is a little if statement that's all on one line.
A ternary expression.
First has the test, num < 100 if that's true, it's going to print out the first value or execute this command here, small.
If for some reason it's bigger or equal to 100 it's going to say huge.
And then we just use that to print out The number is small or the number is huge things like that.
It's kind of a silly example, but you can definitely see what's happening here, right the idea is just use this one-liner probably it's better in some kind of if statement but you know maybe not in this case.
Let's run it real quick just to see what's happening.
We can go over here and say 10.
The number's small.
99, 98, small.
100 and I'd don't know, 101.
It's huge.
Okay, if we hit enter, just leave.
All right looks like our program is working as expected.
So here's the ternary expression, C#.
Test?
true : false How do you know that it's true and then false and not false then true?
You memorize it.
That's how it works.
There's not a whole lot of obvious reason it's not like if you didn't know about this you would know what this meant, you just have to study it and learn it.
Not too bad, it's really good if you like little lambda expressions and stuff that you want to have, just a little test and then some kind of conditional and as an expression and the value comes back, pretty nice for that.
|
|
show
|
2:13 |
Python also has a ternary expression and we're going to look at it now.
So, let's start with our little ternary app here we're going to use my if main live template and it had a while true section in here.
So in this it had text like so and it said and then you'd have to test it if it was empty so we can do that if not text print, like this later, and a break.
Alright, that's cool.
And then the next thing if it was some kind of content, we assume that we can parse it.
We didn't have really error handling here so it's fine.
We're going to parse it over I'll say the number class is either small or huge.
In C# is was test?
true expression : false expression.
Python takes a little bit more to get used to there's some advantages and disadvantages but it tries to be more English-like.
It starts out with the true case.
So we'd say: small, then if, then test so n < 100, else false case, huge.
Like this.
And I guess that would be num.
So this right here is the ternary expression in Python.
True thing, if test else that.
So it tries to be more English-like.
Small if the num < 100 else huge.
We're just going to print out the number is num_class like so.
Alright, let's just run this and see what we get.
Alright, inner number 4, small, 100, it's huge!
Boundary test 99, small, something huge like that.
Perfect.
Empty, it exits.
Pretty cool, huh?
So that's writing this test here in Python.
Let's do a quick side-by-side.
Like this.
Like that, so let's go up to the main there you go.
And remember you got to ignore that 'cause that's just in another file in the C# one.
So here's the comparison of these two.
While true, go through, get the stuff now mile it, parse it here's the test, the part we really care about right here versus that part right there.
Pretty cool.
|
|
show
|
0:39 |
Quick review of Python's ternary conditional expressions.
So here we have a number.
We're trying to test if it's small or if it's huge.
There is no middle ground in this world.
And it's defined to be small if it's less than 100.
So it's meant to sound like English.
Small, if number's less than 100 else it's huge.
Pretty straightforward.
The one thing I don't like about it is often I like to see the test and then the outcome.
I would kind of sometimes prefer C# for that.
For other times I find this really readable.
Anyway, this is what we have in Python.
It's the equivalent of the C-style one that you're probably familiar with from C#.
|
|
show
|
2:35 |
Lambda expressions were introduced with LINQ back in C# 3.0, in my opinion, they are one of the most important additions to the language along with LINQ itself.
That was groundbreaking work they did from C# 2.0 to C# 3.0, so, let's look at that really quick here and see if Python has some kind of equivalent.
We're going to take a random list of numbers here list of integers.
And I would like to sort them, normally you could just do real simple stuff like data.sort, you'd be done.
However, I don't want to sort them smallest to largest I want to sort them some other way or maybe I have a list of customers I want to sort them by their total value descending right so I need some function to pass to sort to say, well, don't just sort by comparing the items directly but apply this change or this algorithm to each comparison so what we can do is we pass a lambda expression here.
We say there are parameters n and m.
They go to the expression the return value.
Math.Abs(n) - Math.Abs(m) so what we're trying to do is sort by the size of these elements, forgetting, whether they're negative or positive, if you could just take away all the negative values and just do regular sort and then put them back and that's what we're trying to do.
So we're going to print it out here.
That's a little function print collection again.
And I'd also like to, you know, maybe do some other stuff like given those elements, I would like to convert that to an collection that is double that double each element in the list, after it's been sorted.
We can use little LINQ to objects here, and pass a lambda expression to LINQ and say to the Select function and say, given any element in the list I want you to say the next element is two times that.
So let's just run this real quick to see what we get.
The first one is the perfect sorting 11 23 that all seems normal till you say 21, -34 55.
Notice it's sorting by amplitude or magnitude of those numbers.
And then the second one that I pass off to the print using that select statement actually created a new collection that is double the first collection.
Interestingly, just like the stuff we talked about with generators and Fibonacci sequence, this is also one of those lazy collections which is additionally, awesome.
These are lambda expressions, they're really great instead of writing a whole different function of processes I can literally write this incredibly small bit of code here and just pass it to the select statement and modify the behavior select or sort or things like that really a nice feature of the language.
|
|
show
|
4:59 |
Let's see this idea of lambda expressions and these little computational, tiny methods that we can pass around look like in Python.
So we'll start again with our main structure.
And I'm just going to paste in some numbers because these are the same numbers we were working with in C#.
And we don't have to write the print collection we can just print these directly and Python will print them.
So let's just see that everything's hanging together.
Beautiful.
Unsorted.
Unsorted, but there they are.
So, what if we want to sort these?
How would we do this in Python?
Well, we saw how to do it in C#.
We said numbers, nums.Sort Oh that's right the difference is the capital S versus the lowercase S.
Instead of passing just an argument we have to do a keyword argument here.
So, the key the element that we're actually sorting on for any element in the list it's a little bit different.
You don't compare two, you just give one back.
So here is where I would have written the lambda expression.
In C# I always say, n goes to, What was it?
I would say something like Math.Abs(n) Now, Python, we don't have this syntax.
We have a better one, not better.
A different one.
We see the lambda n Well, it sort of triggers here's a function and then from there to here actually it goes there.
Sorry.
What goes before the colon is the argument.
So you could have n, n but we don't need it in this case.
We don't have a goes to, we just say this.
So here's how we achieve exactly the same thing as we had in C# but in Python.
Not bad, right?
Let's see that that's true.
Look at that.
Sorting ascending.
There's our -34 right where we would expect it.
Perfect.
So, this cool.
I really like this.
This is exactly the same idea as we have for lambda expressions in C#.
Big difference though: in C# you have two types of lambdas.
You have single line ones like the ones we've been using in the example I just showed you and you have multi line ones where you can use curly braces and actually do work and then return a value like more explicit lambdas.
Python only has the one line.
So, in comes an argument.
On one line you have to generate the return value.
That's it.
It's a little bit simpler but that's the big important kind of lambda anyway.
The other thing was we did the Select statement with LINQ objects using select on an i numeral.
That is not something that you can do in Python.
We don't have LINQ.
That's actually unfortunate.
I really love LINQ.
I wish we had LINQ objects.
But we don't.
Nonetheless, we do have some interesting similar types of expressions.
So here I could come over here and say doubled.
So we're going to create a new collection.
This doubled is going to be...
remember we had nums.Select(n => 2 * n) Let's go over here and I'll leave that for a sec.
What we do is a little bit different here.
We say we're going to generate a list so we say [] and we first say the element that we're going to select.
So we would say 2 * n, right?
That's this part.
for n in nums.
And if you wanted to test you could say if n % 2 == 0.
Like you could do only for evens or something like that.
I'll put it like this for you.
There we go.
In case you want to see what that looks like.
And then let's print out doubled.
There you go.
That's the Python version that we actually previously had done with LINQ objects.
This one actually generates a list like if we ask, if we say print type of doubled you'll see it's a list.
If you want a generator a lazily evaluated one like you had with this one from LINQ objects you make the slightest, tiniest change you convert this from what's called a list comprehension to a generator expression.
Remember the yield from keyword?
Well, yield in Python, yield from in C# well, if it's square brackets it's a list.
But if you just change this to parentheses then you print type of doubled Now we have a generator and printing it out like that doesn't tell you anything.
So you got to loop over it.
A quick way to just make this so it prints is to throw it back into some list or something like that.
We could loop over it and print it explicitly but you can see the same numbers do come out.
You even have this lazy evaluation version like that.
If you want to put this in here you got to change that.
I'll go ahead and change that to parenthesis to get the same effect, okay.
All right, so these are lambda expressions in Python.
They always go like this.
Lambda arguments if they are None you just go like this.
Here, like that and then it goes to a single line expression that is the return value.
You're going to find that you can use these a lot in Python.
|
|
show
|
0:52 |
Functions in Python are first-class objects.
You can pass them around, and one way you can define them are through these lambda expressions.
So here we're sorting this in Nums, a list of numbers and we want to specify how we're comparing each individual element.
So we just create this lambda expression given an input, or argument n, what do you want to do?
Here, we're actually going to sort by the negative amplitude if it's an even number and the positive amplitude if it's an odd number.
It sorts exactly as you would expect: the first four, five numbers are even so they're from largest to smallest and then it switches to odd so those are all sorted to the end and then from there it goes up, up, up, up, up even though there are some negative ones up near the top their amplitude is, of course like, amplitude 233 is bigger than 89.
How cool is that?
|
|
show
|
3:21 |
I want to talk about another function feature of C# that's pretty interesting.
I suspect it's not used that often.
It's a little more common in the Javascript space but it's definitely a feature of C#.
It's really interesting.
It's called closures.
So the idea of a closure is I've got some function going to pass some data to it.
And then that functions going to create another function here.
And that other function is going to capture or get the closure of these variables that were ambient to it.
Kind of like globals but only within this function which was only defined temporarily.
Then when you execute that function later we're going to return it.
When we execute it later its actually going to remember these.
It's just like we went and created a counter little method does counting.
We created a counter object a Counter class and gave it two member variables.
Where does it start?
Actually 3.
What is its start value and its counter ID.
And then each time you call execute or something on it it can work with those values.
Same thing here except for not creating a class.
It's just a function.
So notice this delegate that we're creating has it's void, it has no arguments.
And yet it's working with start which is defined right here.
So we're working with counter idea which is defined there.
And its working with starterVal which comes in here like this.
So we're going to call this CreateCounter.
It's going to describe what it's doing creating counter with this.
Create a function that does not execute.
We're just handing it back.
But it's now captured that state that is erased when the function returns.
So it hangs on to it really funky.
So we're going to call CreateCounter with 7 and then an id of 1 and CreateCounter with -100 and an id of 2.
We call counter1() it goes and increments 7 to 8.
And we call it again down here it goes 8 to 9, 9 to 10.
But this one called with different values actually has different like state captured in it.
It's different closures.
So it'll be like -100 to -99 and so on.
So let's just run this and see what we get.
Check this out this is crazy.
I called a function pass it some arguments and then I called it again and again each time being void and yet it remembers that it's id was 1 it's start value was 7 and it's current value is 10.
First 8, 9, and then 10 it holds on to this.
It's a really interesting idea of how to pass additional data.
It might seem crazy like why would I ever use this.
Well, imagine you have a lambda expression and your trying to do a sort and you need to use data.
Its ambient to the current scope but there's no way within that lambda function to pass it over like your passing it through the list and the list only passes what it passes.
So you can use closure or this capture stuff to actually get additional information or values into these lambda expressions.
Alright, really really cool stuff.
This is how you do it in C#.
All you do is you have a function a delegate in this case could be a lambda expression as well.
And all you have to do is just use the values here with you know that come from the outside use them inside and now their captured and held onto forever.
And they can even you saw they can be changed right?
Like the start keeps getting changed and changed and changed to remembered between calls up here.
So that's closure in C#
|
|
show
|
3:24 |
We saw this idea of closures in C# was really interesting and powerful.
Does Python have something like it?
Let's find out.
Over here, let's create one of these functions that creates this counter, this closure.
So, we'll say def create_counter and it takes a start_val and a counter_id.
So what did we do?
Well, we had a print statement, and then what did we do?
We defined a delegate type, didn't emphasize that we had to create a delegate type and then we defined a delegate instance.
Python you have this idea of these delegates as well but it's much, much simpler.
You just define a function with exactly the same syntax but indented so it's defined here.
We'll create a counter, which is a void function say has a start, equals a start_val it's going to start from that.
Maybe, it's going to increment.
We called it start before but it's kind of badly named so we said every time you call it we're going to increment this by one, and then we're going to print out some kind of formatted string.
Like this, we're going to say the current counter_id is this and we started at this and now our current value is whatever we've incremented it to.
Notice though, there's something a little bit funny going on here.
We have these values that seem to be captured just fine but this increment, there's something off with it it says we don't know what that is.
And in order to tell this function it's allowed to capture local variables or even just to try to print it out you have to use a new keyword, a nonlocal inc and that says I'd like you to reach up to this intermediate scope, it's not global it's not defined here, and work with it.
Sometimes you have to use nonlocal, sometimes you don't it's not entirely obvious when that is true but down here we can return count, counter not like this, that would call it, just the name.
So we're turning this function that was defined right here and it's actually going to do closure on these values just like before.
So let's go over here and say counter1 = create_counter, start value and we have 7 and the id was 1.
So let's just call counter a few times like that we run this, and we run the right thing.
Look at that.
Counter with id 111 it's start value is 7 now it's 8, now it's 9, now it's 10.
Pretty cool, right?
So exactly like C#, except for we didn't have to jump through hoops to specify the return type here of whatever this delegate type that we're going to define we just said no, here's a function really more generally, there's something.
Here's a thing that can be called 'cause classes and other objects can implement something along those lines as well and then we just passed it back.
We also just had another one that was at -100 and this was id 2, that we said counter2, counter2 and then we did that three times.
Run it one more time, okay.
Counter id 1, 1 and 1 still goes from 7 to 8, seven to 9, 7 to 10.
And number 2 is going from -100 to -99 to -98, to -97, exactly like the C# closure that we had before.
Cool, right?
So, same thing if you have lambda expressions or those little types of things you can use closure to capture those values into interesting things.
But just be careful because these values are remembered they don't reset like normal functions.
|
|
show
|
0:56 |
Python functions capture data if they're in this intermediate state using closure.
So, here we have a function that is creating a function.
So, create counters, creating counter the function and counter can capture and remember the variables that were ambient to it while it was created in this intermediate state.
So, it can work with start_val.
It can work with increment or inc one as we called it.
And as I make changes to those they actually remember over time.
Counter is a local function that remembers both inc but also the start_val actually just changes increment though which is more impressive.
Start_val and inc are captured.
But notice that only inc needs nonlocal scope right, the parameters you don't have to do that for but if you have like an intermediate to variable that's defined in the third line here you have to say nonlocal to make that visible to this inner function.
|
|
show
|
2:06 |
This is a little section that you are going to be very excited about as a C# developer.
It's probably been a little funky for you to deal with Python in its dynamic language types and sometimes you're just like, oh, what is this?
What operations can I do on this element or why can't I say this is an integer or something like that.
We're going to look at the C# type system super quick because I'm sure you know it really well and then we're going to do something similar in Python, surprisingly.
So we've got a class, it is a Wizard, like from a game or something and I know normally you would put this in its own wizard.cs file but with all this stuff going on we're like running low on grouping options so I'm putting it in this typing file here, okay?
It has a string, which is a name it has a name which is a string, rather it's a level, which is an integer and it has this factory method called Train.
You can pass in a base level and out pops a wizard that is trained up to that level.
Not a lot going on there, but whatever.
And then here's the main method.
We're going to create a wizard, we're going to call him Gandalf and we're going to level him up a little bit and we're going to say the level of the wizard is whatever their level happens to be, okay?
Run this.
Awesome, the level of the wizard is 8, and it started out as 7 and obviously it compiles and runs.
So this is just a real simple case of working with types, right?
We have a wizard class, we say dot, we get Train.
Obviously if we can explicitly state this is a wizard and everything works, but if we tried to say it's an int obviously not so much, right?
Not so happy.
If we try to change this, obviously you can't just leave it void or whatever, right?
We have to say either var, where it implicitly adapts the type that is returned here explicitly a wizard, or we have to say it's a wizard like so, right?
And once we do that we have all the type options, name and level and things like that that are a part of this class.
That's C# typing, and let's just run it one more time to make sure I didn't break it, 'cause it compiled while I was fiddling with it.
Great, Gandalf is level 8 in C#.
|
|
show
|
7:37 |
If you've been longing for types, somewhere, somehow in this code, to show up and say, you know this next it's actually an integer or the return value of this thing is a list of numbers or something like that.
Well, since Python 3.4, we've had the ability to specify types, much like Typescript for Python.
Typescript lets you optionally add types it's a little more constrictive than what you'll see in Python but you can either run plain Python or you can have Python with types these days in Python 3.
So we're going to explore that, and this Python with types, in a limited degree, is actually the kind that I like best.
You'll see that some parts of your code don't need any typing and some parts it actually benefits them a lot and lights up the editors like Pycharm and VS Code and so on and it's totally worth it.
Now I know we haven't yet gotten to classes and we're going to focus more on that in a little bit but I'm going to use a class here so we can have a type to find here, okay?
So, if a little bit of the details of the class are vague, don't worry about it remember next chapter we're diving into classes deep.
So what we're going to do here, is we're going to define a class which is a wizard.
Let's remember what the C# one had.
We had a class which is a Wizard and they had a string name and an integer level.
So, for Python classes we go to the constructor to define the fields not necessarily at the top level.
Can do it at the top level but we're going to talk about that later.
So we have not a constructor method or a wizard or whatever we just have a what's called a __init__ double underscore init and down here we don't have a this we have a self, so we say self.name = let's put a W for the wizard and self.level is going to be equal to 0.
And then what do we have over in the C# world?
We had a static wizard method so let's go over here and define a train method.
And it's going to return a wizard and this is going be a staticmethod.
Right.
That's what it was before.
In C# it was static goes there, in Python it was here.
We'll talk again about that in the classes and what we're going to do is we're going to create a Wizard.
This is like new, but you don't say the new keyword.
Then we say let's set the base level here which is an integer as we'll see.
Notice there's a level, right, got dot level so it's understood this is this type but it doesn't really have the typing yet, it's just the editor's being really smart.
And, yeah, let's just say, we're just going to leave the name alone, some wizard, I don't know.
We could pass this in but the one we did in C# didn't have it so let's just leave it like that.
Alright, so here is the definition of a class that is almost like the one we have in C# it has a name and a level, it has a train method and it returns a wizard.
Did I actually return it?
No.
There.
Now we have like what we had in C# and we don't want this self method for a primer for static methods.
Okay super, so this is looking pretty good and then we had this main business down here.
And what we said is that Gandolf is a Wizard.train set the base level of 7, over here it's going to be 100 and then we print it out that something like the wizard is some level, I'm going to say gandolf.level, like so.
PyCharm thinks this is misspelled of course it's not, right?
Alright, so let's run this and see what we get.
Does it behave?
Excellent.
Oh, it didn't level it up.
Right, that's the other thing we did.
We said gandolf.level += 1.
Now it's 101.
Perfect.
That's what we expect.
And you might be wondering, okay, well I guess there's a class but where's the types, Michael?
They're not here.
Yet.
This is the untyped version.
But Python, like I said since version 3.4, we are on 3.7 you can see down here on the right, since 3.4 you can define types.
Actually it's a rich typing system that describes.
So I can come over here and say, in C# I would say something like string name or in Python's types I'd say str name.
That's not how it works here.
It's more Typescript like.
You say the variable and then the type, so name colon str and name colon int.
So now when I go over here and I say dot level dot, notice it's offering auto complete for integers.
How cool is that?
Okay.
So it knows that this is an integer and down here I can go to this method and say this method takes an integer and then it has a return value which is this arrow here like that and we're going to return a Wizard is what you want to say.
There's a small challenge about the way that the parsing order happens in Python so when you're talking within a class that it returns that class, you have to put this in quotes.
But notice it has it like colored to say no, this is a special thing, not just a string.
Okay?
So now if I go over here and I could work with this.
We could say this is a wizard and then gandolf dot name obviously it knows that's a string but if I said this is something else I'm defining it to be an integer it gives you a big warning and says no, no, no you can't assign a wizard to an integer and down here it says the integer doesn't have a level.
What, are you crazy?
Right, so you can either omit this and it probably can figure out that that's a wizard you can see it's doing this here.
But if you want to be super explicit or some reason it doesn't pick it up, you can say this is a wizard.
Double e here right or for some reason this method didn't say what it is but you know what it is go like that.
So check this out.
We have Python with types.
Try it again.
It works fine.
One thing to notice though, this is like an editor helper type thing.
If this is an integer in C# this would crash.
Here it just keeps running, right?
You get this warning in the editor but you don't get runtime validation.
Rarely, not never, but rarely does the typing information actually get taken into account.
For runtime it's for continuous integration tools and linters and definitely for editors like Pycharm and VS Code.
So that's a real big difference.
But you can have types.
Now, one more thing we want to do here.
Why would I give this name as always some other?
That's weird.
I could just set it to be none and then people could specify what the level is, right?
Now notice there's an error.
In C#, strings are reference types.
In Python, everything is a reference type.
Everything.
Even numbers are reference types.
In C# world they should be able to be set to null they're None in Python, right?
The type system is more specific and it has special handling for things that can both have a value and be none.
You have to explicitly say this is a nullable string even though it's a reference type.
So notice here, does expect the string got None.
So what we have to put an optional string.
What is optional?
This is defined in a library called typing.
The top, run typing, import optional.
Now, our none error went away.
Right?
We could do the same thing if we didn't want to start with a zero value, we could have an optional int set to None.
Let's run it one more time.
The wizard gandolf is at 101 with all sorts of type validation in Python.
I think that's pretty cool.
|
|
show
|
1:19 |
Using type annotations or type hints that kind of go by these two names in Python is super, super useful.
The bigger your application gets the more help you want your editor and other tooling to say, you know what?
That is a Wizard or that is a string or that's an optional string and you're not checking it for None and things like this.
As of Python 3.4 we can have types in our Python code.
So here, for example we have wizard class and its constructor.
It defines two fields, two public fields which are an optional string for the name and an optional integer for the level.
And they're both set to None.
Using the Optional[str] Optional[int] defines those types.
Then our train method takes an integer base level and returns a Wizard.
Because Python parses and defines these types all at the same time it's not until the end of this whole code block that wizard is defined as a thing.
So, we have to put wizard in quotes only within this class.
The rest of the application we just say wizard no quotes treat it like a regular type.
There's this little edge case here.
The newer versions of Python are working around this have better syntax.
But, you know, it's not a big deal and I want to make sure this is like broadly applicable today and not just in the future.
So, "Wizard" at the end here.
|
|
show
|
2:31 |
Let's look at a program that's not very reliable so it's going to need some error handling.
Here we have our run method for the Errors class and what it's going to do is it's going to generate a bunch of numbers 1 to 20 and it's going to add, I don't know some Fibonacci numbers or something like that on the end.
And then it's going to try for each one of those numbers to call SketchyMethod.
The name might give you a hint of what's happening here.
So it's going to set the color to gray so you can see what's going on really well and then it's going to set it to yellow and if there's an error based on the error it's going to say cyan and then some kind of message.
So here's error handling in C# we'll call a function and a try block.
And then we can distinguish between the different types of errors by having different catch blocks.
So there's a SocketException.
We have catch(SocketException).
There's a ValidationException like some value is not valid we'll get it down here.
If it's some unknown Exception we can just catch it in general and to find a variable which will not appear and we're going to print out oh, there's some kind of error we're going to get the type say it's this type of error and here's the message We were not really planning on this but something bad happened Pretty cool, let's run it and see how that works There you go, you can see we didn't have a lot of faith in it so we run it, and it says calling it with 1, hey that worked, 2 that worked we call it a 6, for some reason, we got an overflow or underflow arithmetic exception, right?
This was one that got caught down here we didn't expect it, so we just printed out the type in a message Down here, we've got a network error that was a socket exception that was thrown We said there's something wrong with your connection It worked for a while, network, and then arithmetic and then lets see In this one, we pass it 0 and it said no, no, no, I can't work with 0 that's not something I can compute with Okay, so here's error handling in C# couple things to note, try, do stuff, catch But the catch has to be most specific down to general Because the way it works is the error's thrown and C# says, does it derive from SocketException?
If yes, we go here.
Otherwise, does it derive from ValidationException.
Otherwise, does it derive from Exception Finally, yes everything derives from Exception that can be thrown, so down here we are handling this So, you've got to make sure that you have it more specific to more general the way it goes down here, all right This is our super reliable way of working with this SketchyMethod
|
|
show
|
6:08 |
Let's see the error handling here in Python.
First of all, we're generating the same set of values.
We're getting a list out of this first 1 to 20 numbers and then we're adding on these additional values here like so.
So really nice and we're just doing one line here instead of 4 or 5.
We're going to loop over those values.
I'm going to call this sketchy_method.
In the moment, we're not doing any error handling whatsoever so you can imagine this might not go well.
Also notice up at the top we have something called colorama and this allows us to set the foreground color and so on for our output just like we were console foreground and whatnot.
So in order to install this we have to install this separately.
There's a couple of things we can do.
We come over here and have a new text file which is a convention in Python.
We'll talk more about it later but just so we have this listed somewhere.
So we have something called requirements.txt and the tooling knows about it.
So if I go over here and just I put colorama notice immediately, PyCharm is like whoa, whoa, whoa.
Colorama is not satisfied.
We need to install this.
Where is it going to install?
It's going to install it into our whatever virtual environment is active there.
So it's going to install to this one.
So we could do that manually or we could let PyCharm do it.
So I'll go ahead and just let PyCharm do it.
Boom, it's installed.
It's still got an underline but that's just 'cause it's misspelled.
I'm going to say no, it's not misspelled.
We come back here, now this works, okay?
So now we can do, like, Fore.yellow, blue, whatever.
All right, so let's run this program here and see what we get.
Well, it worked for a little while 2, 3, 4, 5 and then it looks like the error happened earlier but it actually happened at the very end.
It's just the priority of the system error output stream is higher than the priority of just the regular output.
So it comes out of order, right if we don't flush things and so on but ignore that, right?
It ran for a while until it got to 6 but it didn't actually work.
It crashed and we got an ArithmeticError.
Okay, so let's start adding in the type of error handling we had before.
Remember with C# we had try and catch this, catch that.
So Python has almost the same syntax, try do something.
You don't say catch.
You say except, like this.
You can just have it blank.
That probably isn't what you want.
But we could print out oops, like this.
And let's make it some kind of color.
Foreground.red, light red, let's say.
Now if you run it you can see oops, oops, periodically.
It's crashing.
But we can do better.
Just like in C# we were able to capture these different types of errors we can say except error type.
Instead of catch error type, except error type.
Now what we can do is we can put out some message about the type of error that we got.
And we could do something like our value here and maybe let's make it really obvious that this is an error, like that.
Okay, try again.
Cannot compute with 18 because it was an ArithmeticError and we got another one which is a ValueError.
So we're going to get some of these random types and we've done the studying of this sketchy_method and we've realized that there's a couple of types we can catch types of errors we can deal with.
There's a BrokenPipeError and this'll be check your network.
Check your wifi, okay?
We could get an ArithmeticError and then there may be other ones that we're not aware of.
So down here we can just say except Exception and we can print out, let me just copy this.
Print out something like this.
So when we didn't know what we were doing before like when we didn't know what the error type was we said error and we have the type name and then the actual problem.
So in order to do that we actually need to capture some sort of variables.
So you don't say exception ex.
You would say as ex, right?
This is how it works.
And what is this unhappy about?
It just says too broad.
It wants us to catch more specific stuff.
Alright, so now we come over and we could print the same type of thing.
So we'd say give me the type of ex.
And under name, like this that's the type name there and then we could have some kind of thing like what is the error.
Just the string that's this is the way you do to string on an object in Python.
And let's space it out a little and run it now.
Nice, look at that.
So we got exactly the same error.
We got a cannot compute a 6 and a network error cannot compute with 12.
Oh, let's see down here what we have.
Oh, this is one that was unknown.
It's a ValueError.
None apparently is not valid.
So when we didn't specifically handle this as an exception case we were able to catch it and do something kind of meaningful with it, I guess.
And then the rest of it, that worked.
Pretty cool, right?
This is actually super, super similar to C#.
Let's go and do this side by side again here.
Oh, that whole bit of screen there is the C# one.
From here to there, that's the Python one.
We can see it's the same basic structure for each, or for in and then try do the block.
Try do the block.
And then either except exception type or catch exception type and then you just deal with it.
If you want a variable, the you define the variable either like this here or as ex over here.
Beautiful.
This should be really, really familiar to you there if you've worked with error handling in C# it should be super comfortable.
And there's also a finally in both.
Obviously there's a finally in C# that you probably know.
There's a try finally so we could add a final block here.
Finally, you know, one colon, print.
Finally, whew.
So you can see there's a bunch of finallys coming out now.
Alright.
I'm going to comment that out 'cause it kind of messes it up but I'll leave it in there as a comment.
So try except finally versus try catch finally.
|
|
show
|
0:43 |
Error handling in Python is quite similar to C#.
We have the exception throwing type of behavior when we say try, do the thing instead of catch we say except and we don't have parentheses but, pretty much other than that it's more or less the same.
The one thing you need to keep in mind also applying Python is this goes from most specific to most general.
It goes through and says Does the error derive from the thing you see in the except clause?
Like, the exception I got, does it derive from BrokenPipeError?
If it does, run in that bit.
If it's an ArithmeticError something deriving from that go in there, and so on.
So, just like C#, has to be most specific to most general or it's going to get caught too early.
|
|
show
|
0:54 |
One of the beautiful things about the C# language is the using statement.
Here we're creating a text file a stream writer, that we're going to write to this JSON.
So we're creating a dictionary and we're going to store the dictionary by serializing it into that file, and it just says, hey, we created this file.
While we're in this block, this file is open and ready for writing.
Here, it is closed, and flushed, and done even if there was an exception, it's all cleaned up.
Beautiful, right?
Let's just run this real quick.
Alright, it says it created a file let's go have a look, see what we got.
That would be in bin/debug on that core and down here you can see here's our file.json with Michael and the language is C#.
Pretty cool, right?
And of course that was created safely using the using block.
|
|
show
|
2:55 |
Would it surprise you to know that Python has its own using block?
They don't use the keyword using, they use the keyword with but it's very, very similar.
In fact, even the things that can go into the using block have to implement a certain method or interface type of thing much like you have to implement IDisposable in C#.
So, let's get started with another program here.
This one we're going to use the json library.
In the C# version we use in json.net Python has a nice built-in json library so we'll just import json and we don't need to add it to a requirements file or anything like that.
And over here we can create a dictionary we'll call it data.
And the way you create dictionaries in Python is just use curly braces.
This is one of the use cases for it.
And we'll have a name name is Michael, the language is Python.
Okay, super, I want to save this using the json library save the data into a file using the json library, super easy.
So we're going to create Python's equivalent of using block, we use the with keyword.
And then we're going to create the thing new up something, but for files you just call an open method, and you pass the file name.
So it's going to be file.json, I think is what we called it.
We want to write to that file and we want the encoding to be UTF-8, that's usually a good choice.
This creates the object and then we're going to define a variable to work with it so I'll say fout, for file output stream.
This is like a using statement with thing as variable.
And then I can go to json and I can say dump to a file, it's kind of annoying.
The terminology, I'd like save or write or something but this is the way it goes, you can pass the object and the file pointer.
So the object is data, the file pointer is fout.
We're done, that's it.
I'll say print.
Save to local file, file.json.
Ready to see if this works in Python?
You can bet it will.
It ran, it saved to a local file.
I noticed over here we now have this file.
Beautiful, right, how cool is that?
And we could even make it prettier, we could go over here and say, indent equals true.
Now if we go look at it's slightly more formatted and so on, but that's not really the point.
The point is we have this same idea as the using statement in IDisposable in C#.
This is technically called a context manager.
If you implement the right interface as we'll talk about later when we get to classes then you can use that item here like this.
You can also do it without defining a variable.
It could be that if we didn't have to actually refer to the variable, this'll be fine as well.
But because we refer to it, it was like that.
The warning here is just that it's quote misspelled.
We'll fix that problem.
Awesome, looks like our file got created here and everything's golden.
|
|
show
|
1:24 |
Python also has a using statement.
It's called with and the syntax is super super similar.
with.
Create the item as the item name or variable.
Just use with.
It's really really nice.
It's like Try, and then finally enclose or dispose but it's even better than C#'s.
Why is it better?
Well with C#'s, using statements using a thing and then that thing in the end even if there's an exception it gets disposed called on it, right?
IDisposable, that sort of thing.
With Python's version when you implement it behind the scenes like you do not see it happen here but if you created a class that was able to be used in this context the finally closed part calls the close the right time even if there's an error but it also passed to that class that's being used in the context manager whether or not it ended in success.
So when it closes it or it calls the finally disposed type of thing it passes, here's the error, here's the exception or it passes None, null for that value and you know whether or not when you're disposing it whether it's success or false.
Imagine IDisposable took a nullable exception type that was part of that when that when that function when disposed got called.
That's what Python is like and it's really nice.
Super cool, you can implement a ton of fun stuff.
This and plug it into these with statements.
|
|
show
|
0:56 |
When you have a conditional situation with lots of different options lots of different possibilities you don't necessarily want an if else if, else if, else if else if all over the place.
Switch is probably what you want.
Now you can't put everything under switch but a lot of times if it's a direct comparison in quality it can go in here and that's pretty sweet.
So what we're going to do is we're going to have this program that loops around.
It asks you to enter a number between 1 and 4, verifies you entered it and then it's going to switch and then based on that it's going to do a Console.WriteLine and break okay.
Let's just look at that real quick.
Super simple switch statement.
I could put 1 it says 1 is fun.
I could put 4.
4 more, 3.
3 and free.
If I could put 72 it says no 72 I don't know what's up with that.
That is of course the default case.
Say what number?
I don't know what to do with that.
Finally, if you hit it, enter it goes away.
So here's how we use switch in C#.
|
|
show
|
6:11 |
Now let's see switch in Python.
This should be pretty short because you know what Python doesn't have a switch statement.
Literally the language does not have a switch statement.
Like, it does not have a numerical for loop like for i = 0, i < limit.
There is no switch statement.
There's only if, else if, else if, else if.
So why do we even have this part of this chapter?
Because Python is super flexible and we can build our own.
In fact, what you just saw this using statement this with context manager is all you need to actually build your own switch statement.
And let's look over here.
I did.
I created this thing called Python switch over on my Github.
It's public.
You can play with it.
Do what you want.
It actually shows you how to do that context manager stuff I talked about.
So let's create a new program here.
What we had before was a while true we've got some text equals input enter a number...
Like this.
And it said if not text print 'later', break.
Like that.
And then we said num was equal to the integer parse of the text.
I think that's what we did in C#.
And then we wanted to write switch.
Well you can see this is an error.
There is no switch statement.
Check this out.
So we can start using this library this module over here by saying, from switchlang, we need to change this to have that be a source's root.
So here we say switchlang import switch.
This is the switch statement that I made for the world and down here, the way we're going to do it is instead of saying switch value, case, case, case, case, case we're going to use a context manager to say with switch(num) as s: Looks maybe a little bit weird but I think let's roll with it for a minute.
I think you'll like it.
So we can go, s.case instead of saying case: or the value:.
We can just say the 'key'.
The key is going to be like 1 so we'll link this really closely.
Case 1 is going to be case(1) and then you put a function colon here.
Like a lambda.
This one's going to do nothing take no values.
And it's going to print out exactly the same value we had in C#.
One is fun.
Okay, that's cool.
Let's do Case 2.
Let's put it and see what it said right here.
So 2 times 2 equals 4 which is fun.
That was that one.
This one said 3 and free or they've got a period is 4.
Four more and of course the case was 4.
Now by the way, if I leave it like this and I run it right now I put 7 It doesn't matter what I put.
It says duplicate case.
It checks that you just like the compiler would check that you can't have Case 3, Case 3 this thing checks, like that.
Then what did we have before?
We had a default.
What happens when there's a default?
There's a lambda, and the lambda prints.
Say what?
And the value over here was num.
This, by the way, is using closure to capture this value.
It seemed not useful, and heck here we are in this trivial little case using it already.
Let's run it and see what we get.
Number 1, one is fun.
2 and 2 is 4.
3 and free, four is more.
If I put 72 or 74, say what?
74.
If I put enter, later.
Now that was pretty incredibly easy to write.
But this switch statement is actually more powerful than that.
It accepts ranges and other stuff like that.
So there's another thing in here called a closed range like that.
And I could do something like, here.
I could say I would like to have a case that has a closed range from 10 to 20.
This matches all 10, 11, 12, 13, 14 and so on and other types of comparisons could be put here and instead of having this necessarily do an action it could actually return a value as part of this switch as well.
So we could have it return the function return value.
We'll just have whatever the number is, squared.
That's going to be the return value.
Of course, this could be more complicated based on inputs and all that.
And at the end, outside the context manager we could print done and got.
Let's see.
It might not always do it but sometimes you might get a result.
Let's run it one more time.
One, got none, that's fine.
Two or three, got that.
But if I put 12, I should get 144.
Done, right?
Also we get 11, it hits that case.
If I get 15, it hits that case.
If I get 21, it's out of that case, right?
Don't know what to do with that.
Isn't that cool?
Here's a really, really clean way to do actually more than C#'s switch statements can do.
Okay, so pretty sweet.
Actually maybe they support ranges now as well but there's a bunch of interesting stuff that we can add to this switch statement.
'Cause it's not part of the language.
We control it.
Alright, side by side, let's see what we've got.
Does that even fit on the screen?
Not really, sort of.
But in our version over here of course it fits on the screen.
Look at that.
Look, it's a little bit weird but if you look at this switch statement it doesn't have the cases and stuff or the return value it's not bad, huh?
Remember, Python doesn't even have a switch statement.
But I added this to the language because hey, I thought it needed it.
I use this for lots of my programs for super gnarly code and it really is valuable, right?
It catches errors because it makes sure you can't have two branches that might do the same thing which would have been okay in say, an if statement or something like that.
So really, really nice.
I love the way this works.
I make use of it when it makes sense for like a switch statement in the language.
But we just had our own definition we created here and used a context manager to make it work in Python.
|
|
|
47:34 |
|
show
|
3:06 |
Let's talk about object oriented Python writing classes, inheritance, creating objects modeling things like, well, you are probably pretty used to from C#.
It turns out that Python has excellent class inheritance object oriented support.
So, let's talk about some of the features that Python has.
Everything is an object.
Now, you can sort of say this also for .NET or for C#, everything is an object there as well.
Everything derives or can derive from System.Object That's almost true for C#, because you have value types and reference types in order to treat the value types as an object, you have to box them and unbox them and there's some complications in there.
In Python, truly everything is a reference type.
Even the numbers are reference types.
And all of those reference types derive from the object class.
We have instance methods and we have static, also something called class methods.
These kind of behave similarly.
just like we have in C#, you can create an instance of an object and it has its behaviors or the type has, like, static behaviors, got that, properties.
Remember the days when you used to write get value and set value because you needed validation or these values were computed, or something like that.
And C# added properties, which is great.
Python also has really good support for properties.
If you want to hide data, that is, like, private data within your class, Python doesn't have the keywords around public, private, internal, protected, those kinds of things.
But, there are several levels of mechanisms in the language to have private data within your classes.
We also have inheritance.
In fact, Python has multiple inheritance which is usually, actually doesn't even appear.
Sometimes it shows up, sometimes it gets used, but it's actually quite rare that multiple inheritance aspect of Python's OOP shows up.
But there's a rich inheritance structure, like you have in C#.
We can overload operators and we can overload methods much like you can change what equals means, or what hash means, or double equals or divide in C#.
We can do the same thing with our Python classes.
We can implement special interfaces.
Either this can be deriving from a class and doing something like that.
Or, there's a whole host of these special methods that are like IDisposable.
Remember we talked about with and the compared to the using block.
There's a set of functions you implement, you effectively implement the usage within that with block.
It's not technically an interface, while it's in quotes here, but the outcome is the same.
You also have abstract methods and abstract classes.
If you want to create a base class, you can't create an instance of, but you can use it as a base class totally supported in Python.
So, you can see, there's a lot here.
This is not just some little bolt-on thing or it's not that it doesn't exist.
OOP in Python is proper object oriented programming.
We're going to go build some really cool classes and model a particular environment and put them into action.
|
|
show
|
3:46 |
Before we get to the Python version of our object oriented programming let's look at the equivalent over in C#.
What we're going to do is we're going to create a hierarchy of cars.
Regular cars, sports cars, electric cars.
So here we have the car class and this is meant to be only a base class.
So it's abstract.
And it has some auto properties here: model name, engine type, cylinder, and base price.
Those are all passed in to the constructor here like that.
It also has cool little property.
It'll tell you if it's electric, true or false.
And that is to just obviously check whether the engine type is electric.
There might be better ways with enumerations and other stuff to do that but we're just keeping it simply here.
Just use strings on this one.
Then we're going to ask the car to drive.
And the default car will just print out the minivan goes vroom or something like that.
And then all these, I put the base class here or the class, the type name that's this implementation is coming from.
You can put dispatch together.
We'll do it in Python.
You can compare.
They're similar but maybe not exactly the same.
That's just drive and it's virtual so it can be overridden.
Right?
For example, the sports car over here might want to change what drive means.
Then, there's also the ability to refuel.
And because we have both electric and gas cars the base class, it just has I have no idea.
I got to throw up my hands on refuel.
It's so different what that means.
The class that derives from me is going to have to figure that one out.
Right?
So when we derive from this for example, the sports car it must implement refuel if we want the sports car class to be able to be created, instantiated.
Let's just look at one of these or we can look at the electric car real quick.
This one simplifies the constructor.
Takes the model name and the base price but it doesn't pass the number of cylinders or whether its engine is electric.
It uses the base a delegate to the base constructor for that.
Okay?
So it just passes through the model name and the base price, but the other two these are set by virtual the electric car.
It overrides refueling.
With the electric car, like I said it says the type name at the beginning just for this example, so the whatever is charging up and then we're driving, it overrides this as well so we can create an electric car and says the whatever it is zooms silently along.
We also have a parking lot over here.
And we can create a parking lot using a factory a static factory method.
Give it a number of spots, like I want Three levels and five spots, so we're going to create 15 parking spots on each level.
Return those.
I have some iterator stuff going on here.
That's pretty cool.
We can get back how many spots are taken.
And we can park the car into one of these spots.
So we can loop over our spots and then figure out, no one's parked there and we're going to park our car in that spot.
Simple data structure, just dictionaries and so on but that's what we're going to do in this particular thing here.
And last but not least, we can run it.
I'm going to create some cars.
We're going to loop around and ask them to drive and fuel and then we're going to park them.
So I'll just run that real quick.
Boom, hello C# cars.
The sports car, the Corvette, tears along the highway.
And then it only wants the best gas.
And then we create another one.
A Windstar minivan, it goes vroom.
Cause this one does not override the base class.
It's a basic car.
But it must implement fuel, so it will take any old fuel.
Electric car, zooms along silently, is charging up.
Volt zooms along silently, is charging up.
And then here's a bunch of free spots.
We park a few, and we have The Corvette and the Windstar and the Tesla and the Volt they're all parked in these very spaces.
All right, this is our world in C#.
We're not going to create it, it's already created for you.
Our goal is going to be to explore these ideas and create something similar to, or as similar as we can in Python.
|
|
show
|
6:16 |
We saw the car class in C#.
Let's go create the same thing in Python.
Over here, I'm going to have a program just going to start with that.
And then in the C# world, we had a directory called models.
And over here, we had a class called cars, car.
I think that's what we called it.
We're going to run this so we can get that set up up here and it's going to work with whatever we define in this file here.
We can break the classes up into one class per file.
We can have it all crammed into a single .py file if you really wanted to.
That's sometimes done.
I'm not a big fan of it but there's nothing in the language that says one class per file or anything like that, right?
It's very free form.
Remember in C# we had a class and it was a car like this.
So in Python, we write literally exactly the same thing.
The naming convention is also similar capital C, camel casing.
We had electricCar earlier, like this.
So also similar convention, when you derive from something you would have the base class like that as opposed to in C# it's like this, okay?
But pretty similar and if you don't put that you derive from object use still derive from object, also like C#.
This is how we define that there is a class and instead of these curly braces, as you would expect we use colons.
Here's where you're going to see some differences in how you define types.
In C# if you have a field or a property it always gets defined up here.
Like you would have an int cylinders something like that, right?
And then down here, you'd have your constructor.
In Python, it's a little bit different.
You can define the fields or properties up here but it's typical if you want just instance fields and you don't want them in the static aspect it's a little complicated.
You define them actually in the constructor.
So we're going to define a constructor and just like methods or functions outside of classes methods inside classes you start with def and then the built-in stuff always starts with double underscore.
So we have double underscore init or add if you want it to act like a list or asynchronous iteration if you want to work with async and await and do iteration in there.
So the one we want the constructor is __init__ for initializer.
And what did we have before?
We had something like model name, engine type cylinders, and base price.
So that's what we're going to have and now what we need to do is we need to create a local field.
These are just this method.
What we need to do is create a field in this class.
So we don't have this, we have self in Python and it's explicitly passed, right?
C# implicitly has these arguments pass for this.
Python is very explicit.
We're going to go over here and say I'm having a model name and that's going to be equal to model name.
Obviously it could just be model.
Those don't have to match, but there they go.
We're going to go ahead and make them match 'cause it seems to make the most sense.
Now, PyCharm is nice.
It'll say, hey, you're not using this.
Do you want to add a field to this class?
Yes, I do.
And it'll give a chance to rename it.
We'll do that for the others.
We also saw Python supports types.
So we could come over here and say this is a string.
This is also a string.
This is an integer and this is a float, for example.
And probably even more valuable is to do that here on these this is like defining the types for the field.
So now later if we say self dot, you know, baseprice dot notice all the operations are coming from float 'cause hey, we know it's a float.
So here we've defined a class.
It's called car.
And we can create it with these things.
Let's go over to our program really quick.
We'll just say cars = create_cars().
We'll write a little function down here that's going to do that for us.
And it can return a list and we can come over here and say I want to create a car.
Now in C# you would say new car, like this, right?
In Python, the new keyword is omitted.
We just say car and call it.
Now we have to import it up there.
And I noticed first of all it's saying chapter four models car car.
I want to create the stuff that's contained inside this chapter as if it was just its own program so we don't have to say the full name.
I can go over here and say mark directory as sources root and then it says okay.
That's like the top level.
We're not going to talk about it.
We just have models car.
PyCharm will write up the top for us from models.car import Car.
And that let's us write some things here but notice it says you are omitting some stuff.
So we can ask, what is the model name?
Let's go over here and say Corvette.
What is the, what's next?
Engine type is gas.
Number of cylinders?
Let's say it has 8.
And its base price is $50,000.
Now, we could write it like this but Python lets you put a little comma digit grouping thing in here and that's really nice.
And let's put a few more.
Alright now, we're getting our cars here.
Let's just loop over to them real quick.
Just print out car.
If we just print it right away it probably won't give you the outcome that you're hoping for.
Oh look, there's a car object at this address.
Let's print out something like the model name and car.price, base price, or something like that.
So there we go.
We have Corvette 50,000, Winstar 20,000, and so on.
All right, so that let us define a basic class in Python.
We just defined class like this create a constructor either pass in values or just compute them from somewhere else and we create fields by saying self.whatever in the initializer.
Notice back over here, when I say car dot it knows it has exactly those four fields car, car, car, car, right?
It has those four fields because that is the convention in Python over here to create a private instance field over here exactly like that.
So the tooling obviously knows how to surface that back to us.
|
|
show
|
2:23 |
Well, our car is interesting.
It has these properties, right.
We saw that it has base price models cylinder and engine type.
But you can't really do anything with it.
We can construct one through its initializer but classes typically are valuable because they bundle data and behavior together.
Right now we just have data.
So let's go and add a method here.
So again, we're going to make the method part of the class by just indenting it within the class.
And then we're going to say def and we have two methods we want to define we want to define drive, and we're going to define refuel.
Now watch what happens when I close the parenthesis here or open it to start defining the parameters.
As I open, notice it, PyCharm, automatically types self.
Because this is an instance method it can only be called if it has this explicit self parameter.
That takes a little bit getting used to but it's not hard, it's just different, okay.
Then down here we're going to have a little print statement.
We're going to say the car, say this is from the car the whatever, self.model_name goes vroom and let's tell PyCharm that is not misspelled.
We're going to have another one refuel.
Same thing we have our self here and we have a print statement.
Actually on this one, let's just pass for now.
Remember this one is going to be the abstract one that we don't know how to deal with.
Say it's blank, this is like the equivalent of just empty so I use the word pass.
I know something has to go here for this syntax to work but I don't care what it is, ignore it.
Go over here and instead of doing this let's say car.drive and notice it has this function here.
And car.refuel, okay.
Run it again.
Yay, the car, the Corvette goes vroom the Windstar goes vroom, the Tesla goes vroom the Volt goes vroom.
Not super accurate, or really interesting but it does work, right.
Our cars are driving and they're refueling and let's put a little new line between them.
Cool, so we've got these methods that we've created over here and, right, we saw that they're super simple.
just define a regular, like a function has the self as the first parameter.
If you don't put this there, if you put a, b, c notice it's yellow and the others are not and this is usually the first parameter is self.
You might be forgetting that you've you might want to put something there.
So the tooling does help but you got to remember to do it.
|
|
show
|
2:01 |
This refuel method, remember our assumption was the base car because we have electrics, gas, maybe diesel cars we have no idea how to fuel this car.
It's up to each individual car to figure out how it's fueled.
Well, we're going to have some classes that derive from this, as you already saw.
But what we want to do is we want to make sure that you cannot explicitly create this, but you have to create a fully formed specialization of car.
In C# we saw that we could write something like this, like abstract car, right.
We don't have this concept of public so maybe we could write this, mm-mm, it's not looking like it, is it?
So Python has a perfectly fine way to deal with this.
It's just not through keywords, it's through inheritance.
So what we're going to do is we're going to use a module called ABC, so I'm going to say import abc for Abstract Base Classes.
And here we're going to say abc.ABC.
Kind of wish the naming was a little more explicit there but abstract base class.
So this is cool.
Down here we're going to say this refuel method is an @abc.abstractmethod.
This syntax here is new, I don't believe we've talked about it before, it should remind you a little bit of attributes.
It's called a decorator and it serves a lot of the same roles as an attribute does.
It distinguishes the thing that has got the decorator on it to make it behave differently or to indicate that it has different behavior but it's actually implemented super different than typical attributes in C# are implemented as.
Those are like compile-type things.
This is actually something that runs an extra function that modifies how these behave.
We just put this on here and if we try to run this again it says, No, no, no, you cannot instantiate an abstract car method, a class with the abstract method refuel.
Alright, so that's done what we've achieved.
We can't create a car, now we have to create specializations that implement refuel.
|
|
show
|
4:31 |
So let's define some other types.
We saw that we can no longer use car directly 'cause it's an abstract idea.
It's the base class we're going to use for our program but we want to have specializations that really implement the details.
So let's create a new file here called basic_car something like that.
Bring another class called BasicCar like so.
Now it needs to derive from Car so we want to write just Car here and PyCharm says, well looks like you don't have a definition of Car on one of these two lines, so I have no idea what this is.
But we can import it up at the top.
We say from, models.car import Car.
And then down here, we can put nothing for just a second and PyCharm says we need to implement some of these abstract methods like refuel car.
Cool.
So it's just going to go here and write this function which right now is empty.
Not super interesting is it?
But we can print out something like this.
Put the type name here so it's really clear where that detail's coming from.
The basic cars take any old gas.
Right, that's good.
Now here, let's see, this is going to be a BasicCar.
And again, to use that, up at the top we don't have the right import statement.
PyCharm will add it.
Or you can write it yourself as we go up a little bit there you go.
That let's us do this part here and if we only do that one car, it works.
Look, the car part deals with the drive and the Windstar goes vroom.
The basic car deals with refueling and it says it will take any old gas.
Super.
So that gives us our basic car.
Let's do a couple real quick here.
We're going to define another class, sports car derives from car, just like before we add the import statement above.
Now it's going to pay some print statements.
So we have the sports car does the such and such tears along the highway, and it only wants the best gas.
That's true.
Let's also add an electric car.
Once again, import the base class implement the two functions, in this case.
This, what can I say, the electric car zooms along silently as it has to change the behavior and the refuel one, we have to implement and it's charging up.
One thing to notice about the IDE here check this out on the left.
Click on this, it takes us back to where it's being implemented, where it comes from.
So drive, we see, did it say?
Overrides this method in car.
Same here.
But if you have a rich hierarchy like Car, BasicCar ElectricCar, ElectricSportsCar, whatever right and you're overriding at different levels it will tell you, then also when we're here have the reverse.
You can see who is implementing this can see BasicCar, SportsCar, and ElectricCar all implement refuel and you can jump back and forth between them.
That's pretty slick.
Right, let's change the thing for creating here.
This will be a SportsCar and import that.
Let's make that SportsCar there like so.
This will be an ElectricCar.
You can just type E-C and do the CamelCase matching here and get the ElectricCar to come up right away.
And up at the top, now we have the 3 that we're actually working with.
And now I just remove that in that car there but let's change this real quick and say that this is going to return a list of car.
And that comes from typing like so.
So this, when we come over here we're going to say cars[0].
and we're certain to get the right things.
So we're still using our base type here thing, this is the commonality you can assume across all the collection that comes back but of course, their behaviors are different when we ask them to drive and refuel.
Whew, let's try it one more time and see how it's going.
Cool, so now the sports car, the Corvette tears along the highway, and it only wants the best gas.
The Windstar didn't override how it drives.
It just goes vroom.
But it had to say how it was fueled and it takes any old gas.
Electric car zooms silently along and it's charging up, the Tesla and the Volt.
Pretty cool, huh?
This is really nice, clean, full-featured OOP and we've got more to come.
This is just the start of it.
But it's a pretty cool way to get started.
|
|
show
|
2:22 |
In our C# version, we saw that for the electric cars we didn't have to indicate that they were electric nor did we have to say the number of cylinders which is always going to be zero.
We did that by changing the constructor.
If we go over to this we can say we want to define a special constructor.
And it was going to take let me just rob a little bit from here model name and that is going to be similar.
Like so.
And we can just do a quick little pass for a second because I want you to see some of the issues or the help that the editor is going to give us.
So in C# what we did was we said base.
We put stuff here and then we had curly braces like that.
Well the colons already used so we're not going to do that.
And what we do down here is we can actually say super and get to the base class or super class which is car.
We can call it constructor explicitly.
That's how you do it.
And then what goes in here?
Well the model name goes in there.
And then the engine type is electric.
The number of cylinders is zero.
And the base price is like that.
So here's how we accomplish the same thing in Python.
Now if you try to run it where we create the electric cars that part's going to get unhappy.
As you can see I thought we had 3 arguments but 5 were given.
By the way, notice 3 and 5 not 4 and 2 the way you probably think about it.
But let's jump over here where we're using it.
It's part of that self thing.
So it says we got some two extra things here.
And the two extra things are the electric and the number of cylinders.
Both drop that and that.
Run it again.
Works like a charm.
Right so go back here.
The way we delegate to the base class or super class constructor, initializer in Python is we call super and then the initializer explicitly.
This should always be first.
Then other stuff, right.
You want it to do all the set up and then make additional changes after that I would suspect most of the time.
So I guess I'll leave those comments in here for you.
This is how you do it.
This is how you call the base constructor and you can have specializations.
Notice the other ones just don't even define initializer and they just fall through to the car's definition of initialize.
|
|
show
|
2:23 |
Properties are a wonderful addition to classes.
Older versions of object oriented programming like C++ and Java in the early days you had to have a get value and a set value or a get value if it was computed.
For example, here we have a boolean property which is whether or not the car's electric and we can compute that based off of the other information.
So here's our read-only computed property, that we can use and then over here we can say car.is_electric.
And this is great we treat it like a field, like it's just data and we don't have to treat it like it's behavior.
Especially if you're modifying it doing like ++ or something like that.
Obviously it doesn't make sense for booleans but you know, if it was a number or something like that.
Can we do that in Python?
How do we do it?
Well, we don't have this get, set syntax but we do have the ability to have a property.
Let's do that in the very base car, here.
And it has to do with these decorators.
So, the way we use a decorator is to change what this does.
We also can use a decorator in a shortcut as well to create a method that is actually a property.
Same thing that we had in C#.
Let's go, right is_electric, now we can compare.
It will return self.engine_type == 'electric'.
That's it, same thing.
So over here we have, going to say this is a read like, this implements the get, basically.
The set is a little bit different.
It implements something that we can treat like a field or like data, but is actually a method that runs.
So let's, over here, and we'll just print out.
We'll say, the car.model_name is electric question mark and then we'll just print out this property, like that.
And we run this, and bet is going to say you know, False, False, True, True, or something to that effect.
Like, Corvette is electric, false.
Winstar is electric, false.
Tesla electric, true.
Bolt electric, true.
How cool is that?
So just like we have properties in C# exactly the same thing, we have properties over here in these classes.
And you define them like this.
You say, app property on a regular method.
That defines the get.
Obviously, it has to be a void method, right?
|
|
show
|
3:14 |
Time to review creating a base class or super class in Python.
Our car class was our super class and we wanted to be abstract so here we derive from ABC, Abstract Based Class.
Has a couple of features has a constructor and C# terminology, an initializer or sometimes referred to as dunder init, as in __init__ Though dunder init, you'll hear that a lot dunder methods for this class of methods.
And these are the special built-ins like the operator stuff I showed you and so on.
So we are going to create one of those and it is going to take whatever data we have to pass to the class to get it started model name, engine type, cylinders and base price.
We went ahead and used the typing to make that really obvious.
And to define 'fields' by the same names we are going to say self.model_name self.base_price, self.cylinders.
Now, we didn't talk about this in the demo but I'm going to mention it here.
IF you want this to be a private field use the double underscore at the beginning.
It is a weird convention Python is all about the underscores and sometimes they are just separators like base price.
Other times they have important meaning.
If the underscore is a single underscore in the beginning it's still publicly visible the field or the method that you gave that name but it is indicated, like its you probably should stay away from it.
It's kind of an internal thing leave it alone.
You put a double underscore the runtime will actually change it's value so you effectively can't simply can't easily without jumping through a bunch of hoops you can't get to it from the outside.
So when you say car.__engine_type should not appear.
Depending on your editor, but certainly car.__engine_type and working with it won't work from the outside.
We also defined a method drive, we're overriding.
This one is a virtual, it has the ability to be overridden, but we don't say virtual.
Basically, imagine everything is virtual in Python.
We have a property we used a @property decorator to indicate that this is meant to act like a computed value not like a function.
We also have an abstract method refuel which we indicate with the @abstractmethod decorator.
This is our base class.
If we are going to use it, we just derive from it.
class ElectricCar(Car).
Now we are deriving from it.
Here we create a specialized, initialized initializer __init__ that takes only two values, model name and price and then it calls the superclasses one and always passes electric and zero so you don't have to worry about that when you use the ElectricCar.
It overrides drive, and it overrides and must override, refuel.
On both of those, there is no key words around it.
You just do it, and it happens.
It's easy to forget, but you really need to call super.__init__, there is no implicit constructor chaining.
Like, if I just create a constructor it's not going to automatically call the default constructor the bass class, and it's bass class's default constructor.
And so on.
So make sure you always remember to call the bass class init in your initializer.
You saw that Pycharm helped out with that as well.
Overridden methods, you need no modifiers.
There's not like override and virtual or anything like that.
But, as we saw, you must implement the abstract ones or you cannot create an instance of this class.
It in itself will be effectively abstract as well.
|
|
show
|
3:53 |
Here we in our program application.
Let's break this up a little bit.
So this part is for creating cars and driving them.
We also want to park the cars when we're done.
I want to do that a little bit separately.
So let's go over here and create a method.
It's called drive_cars, or let's call it use_cars.
Then we're going to have another one over here called park_cars.
And we'll pass cars in like so; and we'll just go ahead and write it here, I guess.
And let's be really clear, so it helps us out a lot.
We're going to take a list of cars.
Then we'll make sure everything's hanging together.
Yes, it looks like it is.
So, the idea is we're going to create another class that has some other special behaviors called ParkingLot.
It's not going to participate in the object hierarchy of what we had before but it's going to do a couple of interesting things.
First of all, when I create a ParkingLot it's going to have different levels.
Think of a parking garage; there's level A, level B, level C.
Sometimes they get created like the orange level and the green level or, you know the carrot level, whatever.
We're going to go basic; we're going to have level A, B, and C; and we're going to have a spot 1, 2, 3, 4, whatever in those.
We're going to take a constructor.
We're going to create a __init__, and it's going to take a string, a list of strings called spot names.
And be a list of str, like so.
And we can use PyCharm's magic to just add this as a field but we don't actually want to.
Because there's just not enough information for us to store.
We need to know what are the spot names and is some car parked in there.
And if it is, what car is actually parked in it?
So what I'm going to do is create a dictionary called spots, and we can say this is a dictionary like this.
You technically can also write it like this but let's be explicit for a second.
for n in spot_names.
We're going to to set, create an entry in the dictionary and we're going to currently set it to be None.
Okay, this is what we need, we, when later on we're going to put a car into into that spot.
So we'll be able to say the free spots are the ones where it's None; and the ones where it's not None that is, we could pull the cars out and work with them.
And this is totally fine.
We could, we could roll with this but I want to show you something else we can do in Python that's cool.
We talked about the list comprehension, remember?
2 n, something like this.
And it even had an if statement here.
This is a little bit like LINQ.
This generates a list.
We can actually generate a dictionary in a similar way.
So we can say self.spots and we could put a type here if we wanted.
We could say this is a dict of str, Car.
Like that, I think this is what we need to say.
We can say this is equal to not square braces but curly braces and put a little expression here.
So then it, the way it goes you say, key:value.
So let's say n:None and then for n in spot_names, like this, all right.
So what that's going to do it's going to create a, the same thing here.
So, like, we create the dictionary, then we loop over and we put an entry for the name and we're putting None in there.
And we have self.spot_names, spots of whatever.
Notice Car, Car, Car, Car, Car, Car.
Because we told it, it's not just a arbitrary dictionary but key is a string and the value is a Car.
Technically, this should be an optional of car.
We're going to be as accurate as possible but really we'd be able to get away with it, right.
Obviously this is, something we want to indicate.
So here is our parking lot, and we're going to be able to store cars into it.
|
|
show
|
4:59 |
Well, the parking lot is interesting and we could come up with a bunch of names like, A1, A2, A3 B1, B2, B3 but let's have the parking lot create a pre-initialized version of itself giving more primitive data like if I have five levels and six spots per level or something like that, so we're going to create a static function and just like with the abstract function we're going to do that with a decorator, so we say @staticmethod def I have created some spots per level which is an integer and the number of levels which is also an integer.
This is going to return a parking lot notice it's not going to work because the way Python operates as it creates this class is it doesn't really exist as a name until after line 19 so as we saw before we put quotes here it's kind of annoying but that's how it is.
What we need to do is create a list that probably looks like an array to you but that is not an array that is a list that you can dynamically grow just like lists of string or list of integers and have some level names in our world we won't be able to go above, I don't know like six or seven levels but that's all right.
We would have levels A, B, C, D, E and G here and then we're just going to do a double loop.
hold off on this part we're going to adjust it in one second here.
Put a pin into this, a level and then adjust the number, so like, A1, A2, A3.
Obviously we want to only as many levels as there are so if they pass in two, we want to get two if they pass in four, we want to get four.
Let me introduce you to a unique and super powerful idea here in Python so we don't really have a whole dedicated section on this but let's just say we have these level names here like this.
There's this concept in Python called slicing so we have level names, just say L=level name so I can write less, we can say L is 0 and that gives us the first one, how if 1 give us the second but in Python you can put a range in here.
I could say 1 to 3 and that will give me the one index and then this is a non inclusive upper bound.
So 1, 2 and then that's it, so if I did 1 to 4 that would be B, C, D and I put it in the right spot.
Like that, B, C, D.
So what we can do is we can actually use this idea, slicing, to get the amount that we need there assuming that it doesn't exceed the length of our list, which is 6.
What we can do is we can say L, our level names and if you want to start at 0 you can just say colon, you're also saying 0: it's implicit, and then we want to go in the first, if we want to get 4 entries we could put 4 here, we get A, B, C, D and so on.
With that in mind, what we can put right here is we can say colon levels and as long as it doesn't exceed 6 we're going to get what we want.
If it does exceed 6 it doesn't crash it just stops giving us more items.
So either we want to get that back or we'll get some subset of that.
All right so this should create the level names and then we can create a new parking lot and what this one is just the names not the data specified, so we're just going to pass the names, like that.
Let's go and try to use this.
So where we do our park let's say lot = parking_lot like that.
And we actually want to call the factory method.
Let's say we want to have 4 spots per level actually let's go with 5 spots per level and then we want 3 levels.
So it should be A1 through A5, B1 through B5 C1 through C5 and then just to make sure this works we'll deal with more interesting stuff in a second.
We can just print out the spots.
So here we go, notice it's all wrapping along but we have A1, A2, A3, A4, B1, B2 it looks like we're off by one and that is because we want to go there.
Plus one, here we go.
A1 through A5 and right now there's no cars parked in them.
Make sense?
We were able to use our static method to create our staticmethod decorator to create a static method and then we used that to do the little extra leg work to pass over just the names based on the data they gave us, we'll do that here and then the constructor actually uses a dictionary comprehension to convert that to the data structure it needs to do its job.
|
|
show
|
2:47 |
Now that we have our parking lot and over here, we were able to create it.
Let's go and actually park our cars.
We've got to get them out and drive them around have lots of fun and we fuel them back up.
It's time to park 'em.
I'm going to say for c in cars lot.park, and we'll just pass the car over.
Now lot's just lot, like this, easy enough.
Notice there's a little marker here from high term saying it doesn't exist, but we can add a method and it will automatically write it.
Notice it does itself, say car, say car like that.
So now what are we going to do?
How do we get this to park the car?
Well, the job that really we have to do is find an empty spot and then put the car in it.
So what we have to is we have to say for i in self.spots.
Now if we just loop over this this will just give us the keys back and that's okay but I'd like to also have the value as part of the loop without an extra step.
So I can say dot items, then if I just print let's say print I like that, if we run that this is actually a tuple.
What it is, is a key and the value.
So I'm going to say k, v and do tuple unpacking.
And do that, and then we won't really need this, you'll see.
So if we run it, you can see the version of the keys and then that would either be car or right now it's none.
So then it becomes super easy.
We just say if not v, or you could say v is None this may be a better way to say it if that's true then we want to put a car into that spot.
Then we just say self dot spot of that key as that car.
Break, can go over here.
And let's just do a quick print of the lot.spots.
And we can actually use this thing called pp for pretty print to look at this a little bit better, like this.
It prints out dictionaries kind of like nice JSON-style.
Over here, here's our dictionary structure.
A1 to A4, the first level is all booked out.
I guess A5 is left, but here's our sports car basic car, electric car, electric car.
These are parking in a very orderly fashion.
Let's do like one more, let's do two more cars just so we can, let's see what we got.
We can have a Leaf, I want this at 30.
And over here let's do a Camero, probably let's put 40.
Just going to take a guess.
So now we run it one more time.
We can see we're making our way up to the second level.
We're parking our electric cars up there.
That's, I guess that would be our Leaf, wouldn't it?
Okay, so we're able to park our cars, that's really cool.
And we'll go ahead and comment this out 'cause we're not going to really want that.
But here's how we can create this function this factory method and then use the thing that got created here.
|
|
show
|
4:25 |
There were two reasons I added this idea of the parking lot.
The first one was I wanted to talk about the staticmethods and how we can do this to create a factory type of thing.
Here's the second.
So, what I want to do is instead of just printing this out and go Oh, this is a super way to look at it.
I'm sure my users would love to just have a flat dictionary dumped here with no data in it.
There's a couple of improvements.
First of all notice this is not an incredible representation of the SportsCar or the BasicCar.
Whatever, right?
It would be nice to have something better.
So, I can go to the car and I can implement one of the what are called, magic methods or Python data model methods.
And those are the dunder methods.
So, if I go over here and type def and double underscore I have abs for absolute value, add async Enter what ending what something means what boolean conversion, deep copy copy Notice there are a ton of these things here.
In .NET, you might override what ToString mean here.
So we can do the same, like this.
But it actually turns out there's two that get called in different situations.
You can, if you use it in a string this get called if you just dump it out like we just did than this one gets called.
We can actually just return one when the other exists.
So let's just return a string that has the type name here.
And then, maybe it says like the model name and the price.
And if want to have digit grouping not too many zeros, things like that say put a little format, :x.0f,0f I think it is :,0f There is a comma, There is a .0f and over here, we are going to say type(self).__name__.
I think that'll do it.
Let's run it again.
Check this out.
Sports car.
Model.
Corvette.
That's the price.
Basic Car.
Wind Star.
Price, notice the digit grouping and there is no extra zeros even though it's a float.
Pretty cool right?
So this these two methods represent part of Python's special data model their dunder methods.
This is how you implement some of the core interfaces of Python.
So for example, what I would like to do up here instead would say something like this for spot, car in lot: something like that.
I'd like to print out a statement about it.
Like this.
And I would also say if car, like so we got to make sure there is a car here and then we are going to print it out.
We don't want to print the empty ones.
I noticed, PyCharm says mmm We thought there would be a collections.interval here but this is just a random parking lot.
It's not going to go so well if you try this.
Let's see.
Nope.
It didn't go so well.
Nope, not interval.
This should be like C# compilation error object lot is not IEnumberable or something like this right?
So, how do we fix this?
Well, we go back and we use one of these dunder methods.
These magic methods.
Like the constructor is.
So we go over here.
I'll put it below.
And here is called __iter__.
We implement __iter__.
And what we have to do is return something that is the collections we are trying to loop over.
Let's just say i, yield i.
Here we go Created a generator that shoots out the items.
So here we go.
Here is the spot.
It is A1 and it has the car, sports car which has this represents this, this two string equivalent, the __str__, implementation over here.
Pretty cool huh?
This lets us dig into the classes and change the way they basically intergrate the Python language.
Right?
We implemented __iter__ over here to allow us to loop over the lot.
And then we implemented __repr__ and __str__.
This one would be the str.
But if we printed, we just did print car like that would be the __repr__ version we implemented that here to change basically the two string representation for that object.
|
|
show
|
1:28 |
While Python itself doesn't have separate interfaces outside of the concept of just maybe an abstract base class you did see that there are certain types of functions we can optionally implement that have the same basic effect.
For example, our parking lot we wanted to be able to use it in a for in loop.
How do we do that?
Well, we have to add the __iter__ method here.
And that was super easy.
We just created a little generator.
We said for item in self.spot.items yield item, right?
No big deal for us to do that yet this is how we were able to use the lot within a for in loop to get those items back out.
This method here, this __iter__ this is Python's equivalent of what would happen in C# if you implement IEnumerable .
There are tons of these special methods.
There's actually a guide over here by Rafe Kettler who did a really nice job of combing through each one explaining what it does what it's about when you use it, and so on.
This is a huge long article.
I don't want to go through all of them.
I just touched on a few of the key ones __str__, __init__, __iter__, __repr__, alright.
There's a bunch more that you'll want to know about or at least know that they exist and then you go learn about 'em like, Oh, I actually probably could implement this and, you know, make it do whatever it is you're trying to do.
Our parking lot now effectively, in like C# terminology implements IEnumerable by using this magic method which they're sometimes called, or dunder method.
|
|
|
25:26 |
|
show
|
1:26 |
Package management and programming languages is so important.
It used to be kind of a rare thing and it made working with other libraries super painful.
In C# you have the base class library, and all the stuff that's shipped with it, like EntityFramework.dll and so on and that was great because it was right there.
But if you wanted something else, how did you get it?
You went out to GitHub or, after a bit, SourceForge and you download a library and you copy it into your project and you start working with it.
Well, that's probably okay it's not super validated or anything like that it maybe came from somewhere sketchy but let's assume, you know, let's just put the security stuff at the side for a minute.
Just from...
Even if the code is trustworthy there are problems here, right?
How do you know when there's a new version?
How do you know if there's some kind of issue that needs to be fixed?
How do you help someone else get the same version?
If they go and download something from that same place maybe it's a different version and it behaves differently.
So package management is some infrastructure that will list all the available libraries that are registered with it for your environment: C#, Python, whatever.
Now, you see, I'm going to use these libraries and make sure you get the right version you can always get the same version.
In .NET you've had NuGet for quite a while and that's been really important there.
So much so that Microsoft even ships some of .NET itself through NuGet.
You'll be happy to hear that Python has something equally awesome, maybe even more so.
|
|
show
|
3:03 |
As I hinted at before NuGet changed the way you work in .NET.
All of a sudden, there are many, many libraries and I mean many.
When I took this screenshot to put this presentation together a few days ago there was 174,000 unique packages or libraries available and you can just go to Visual Studio check a few boxes, and boom, you've got them added to your app and you always get the right version and it made such a huge difference for working in .NET.
If you're going to come over to Python you're going to want to make sure there's something like this.
172,000 packages, that is killer.
To be fair, a ton of those-- not a ton a lot of those are Microsoft library packages like I hinted at in EntityFramework and EntityFramework Core and that kind of stuff cause a lot of .NET itself is even delivered through this mechanism but there's many, many, many of those that are third-party libraries you can bring in like json.net or something like that and work with it within your application just almost as easy as right-click, add reference.
Just right-click, manage NuGet packages.
So if you're going to come to Python it would be really nice if Python had something kind of like this, right?
Some way to say I want to use these libraries make sure they're part of my project keep them in sync.
If there's an update, tell me that there is and give me a mechanism to get the latest.
Well, luckily, Python has something completely awesome PyPI, Python Package Index.
And PyPI has a bunch of libraries.
You saw how cool NuGet was?
Check this out, 199,000 packages.
Actually, since I took this screenshot but before I pressed record it's surpassed 200,000 packages.
There are so many libraries in Python.
I think this is one of the most exciting parts of this course because so far we've been working with the language.
.NET's great, but where the true magic is where the special stuff happens is when you start to build out of these other libraries.
All of a sudden we can talk to databases and call web APIs and build websites and applications and all these types of things and this all comes from these external libraries we can bring into our applications.
So NuGet is awesome.
PyPI, I think, is even a little bit more awesome.
It has more packages.
Python itself is not delivered this way so this number is maybe a slightly bigger gap as I hinted.
Final word on the pronunciation.
Some folks say pie-pie.
Alright?
Py and then the PI is like the math pi so you say pie, people say pie-pie.
It's not pie-pie.
It's pie-P-I, Python Package Index.
How can I say that with confidence and not go, well, maybe I just say it differently?
Maybe I just have a different opinion.
Hmm, the Talk Python to Me podcast I've interviewed Guido van Rossum the creator of Python.
I've interviewed several times the people who have created and continue to run this website here.
And I ask those people who run the website and Guido just use the words ask them, how do you say this the letters P-Y-P-I, in your world, how do you pronounce it?
Pie-P-I, just so you know when you're talking to other folks.
|
|
show
|
2:25 |
Knowing the language, Python or C# either one of 'em, it's important.
But it's actually a pretty small step to being effective in that technology.
Knowing the base class library or what's referred to as the Standard Library in Python, quite important, right?
There's so much more to know about the libraries that come with these two languages than there is just the language itself.
As we just saw, there's 200,000 libraries that I can go build stuff with.
How do I have any hope of figuring out that which ones I need to be using as a new person?
One of the big challenges coming to a programming environment is I need to do something.
I got to figure out, do I have to build that from scratch or is there some built-in library or is there some third-party library that I can go grab?
So let me introduce you to Awesome Python.
This is just a website, also has a it's backed by a GitHub repository and the idea is people submit cool libraries and packages for Python under different categories.
And this is not just an exhaustive list.
In order for it to make it on this list, it has to get a certain number of votes and things like that.
So let's imagine that we want to build something and we want to talk to a database.
So databases are actually the databases so we'll look at the drivers.
So I could talk to MySQL using these things or Postgres.
Or if I want to talk to, you might care about Microsoft SQL Server, you could use PyMySQL or here's PyMongo for talking to MongoDB.
But, you know, obviously you could just directly talk to them, but you'd probably be better off using an ORM, so we've got a ton of awesome ORMS and little descriptions about why they're interesting.
The one that we use for Talk Python Training is actually Mongo Engine, which is an ORM or ODM on top of PyMongo, which talks to MongoDB.
Absolute joy to work with, not really relevant for this course, but you know, it's listed right here as one of the four ways to do it.
You want to talk to DynamoDB or Redis or something like this, right?
So you just pick something.
You want to figure out how you do computer vision?
Go over here and, Oh look, OpenCV.
OpenCV is a great library for working with computer vision and Python.
But I recommend you start here with Awesome Python if you need to work on some project or you got to get a functionality for some category and you're like, Oh, I wonder what's here?
These are here, not just because they're in existence but they're here because they actually got voted as one of the better libraries for that category.
|
|
show
|
2:54 |
Well things are about to get really fun and exciting, we're going to write a program that uses a bunch of cool libraries and first we're going to check out the equivalent in C#.
So over here I've created this thing called Ch5_nuget and it's an application that does web scraping.
You can see it proclaims it with its title right there.
The idea is that we're going to go visit Talk Python my podcast here, and there's a little shortcut URL if you just go, like, 212 or just, you know that slash a number, it will automatically find its way over to the actual thing that it's looking for.
So what we're going to do is we're going to write a program that's going to go and go right now it's going to go for the ten episodes 220 to 230, and its going to go actually download all of the HTML and it's going to get that HTML and it's going to parse it and try to pull out the title that's in the H1 tag and then it's going to say that it found it.
Let's just first run it and see what it does.
Alright so it's running, look, it's getting it.
Found the title, 'Machine Learning in the Cloud.' Empowering developers with this, and so on, right.
'Twelve Lessons from a Hundred Days of Web.' And off it goes.
And, now it's done.
It went and downloaded all that stuff off the website it parsed the HTML, and so on.
Now, this whole program is 76 lines of code.
Do you think we're able to do that in 76 lines?
No, that's super complicated stuff but the cool thing is, we didn't have to worry about it.
This is something that we're able to just reference off of NuGet.
So let's go look at the packages that we're working with here.
Manage NuGet packages.
Go to installed, and the one that we have installed here is HTMLAgilityPack and because we're doing .NET Core it's the .NET Core version of that.
So the idea is, we're going to go over here and we're going to download this, and then this and then this get HTML, I don't know, get title from HTML it's just this.
Maybe you caught it before.
And we go go in and we create an HTML document we load up the stuff we got from the website and we just select the single node, H1 and get the innertext and trim it.
And as you saw, it works like a champ.
Alright, those are the titles of those episodes, like those are the H1 tags if you went in and found it.
We're also happen to be using HDP client so we use some from there, but also we've got .NET Core itself coming down that way as well from NuGet I believe so we were able to go out and find this library and add it, and we could even do things like see if there's any updates.
I don't know, are there?
It doesn't look like it.
But if there were, it would tell us, right?
So if there were updates for these packages we could push a button and it would change it and get new ones.
This is a pretty cool little program that goes and does that, downloads it, pulls up the title and just prints it out.
It's actually doing a ton of work for how little code we had to write.
|
|
show
|
7:58 |
As we saw, Python has PyPI with over 200,000 packages.
You would think we should be able to recreate that application you saw over in C# that screen-scraping one that just downloads the header the h1 and then shows it to us.
Turns out, of course we can.
We're going to use three cool libraries to do it.
So let's talk about a couple of things here.
First of all, in order to declare it these are the requirements that my program depends upon we typically have a requirements.txt or there's a couple of other formats that are becoming popular but requirements.txt is probably still the most popular.
And the idea is you just list the names of the packages like if I wanted to use HTMLAgilityPack like as you saw, I would just write that boom, it's ready.
Notice that PyCharm's are like we got to install it you want me to do that?
It actually doesn't exist for Python I don't think.
We need something called Beautiful Soup instead.
But you just list the names of them here.
Sometimes you would put the version like 0.1.0.
like that actually double equals and then it says oh you have the wrong version of Colorama and you want me go get that?
Not right now.
So you can put them there.
A lot of times what people do is, they'll just play around and just install them manually and then go back and recreate this requirements.txt, once they have decided on what they actually need for their program.
So let me show you how you would ad hoc install it not just play with this requirements.txt purely within PyCharm.
So lets go over here.
Now first thing to notice if I want to work with it, the tool we work with work is called pip.
And I can thing do like pip list, but the problem is if I ask which pip, notice it wants itself to be updated but kind of irrelevant we don't care about it okay.
Which, hold on, I kind of over wrote it.
Which pip3.
Here we go, got it to save finally.
So it's out of my primary Python 3 right now 3.7 on the system.
This is not the same one if I say pip list as you saw it there is tons of stuff here but the only thing we declared is Colorama what's going on?
Or remember you have to always if you're using a virtual environment then you almost always should you need to make sure that you activate them.
On windows that would be venv\Script\Activate.
Not that, not here though.
We have to say dot to apply to this shell and then venv/bin/activate so on Mac and Linux this is a command and notice we have this now.
Okay, so now we can install stuff we can do a couple things like I said pip install colorama.
We did this earlier so it's already good let's make this warning go away apparently a new version just released.
So you can use pip to manage itself it's very meta, kind of satisfying that way.
Seeing as we already have colorama there and we do a pip list we should have the two tools that manage packages and then Colorama apparently that is the version we actually had.
And maybe we want some more, so if we're going to let's say we want to work with pip install bs4 for Beautiful Soup we hit that and notice it either downloads or used the cache version and it's actually pulling down a couple of things that it needs so it takes the transitive closure of all the dependencies of all the things you've asked for, right?
So this is pretty cool, now we can do pip list and we should have those listed there but if we have one of these requirements.
Let's say, let's put bs4 and let's put one more thing in here httpx.
Would let me making a HTTP client request we could use requests, that's most popular but later we going to use a feature of this library that's not available on requests and I'm going to take this same code and copy it.
So let's just roll with this one, okay?
So if I wanted to install all the requirements of what we got here, I would say.
Where are, that's it, right here.
Yes, we can say pip install -R to say here's a requirements file not the name of the thing I want but a just a file, listing of them.
And I would just say this and it's going to go through one spells install correctly.
It's going to go through all the stuff in there and install them, notice it installed a bunch of other stuff, but many things like this one bs4 with already satisfied.
And the way this works is basically up here these are just, like you can think that there's like an implicit pip install this, right?
So whatever you write, whatever commands you want to issue to pip, you just put them in here like this so you can have it by itself you can have version names and so on.
Right, so that's how these requirements.txt files work.
You can even include other ones like let's suppose, we had over here.
I'm not going to do it.
Or let's suppose we had another file we could have -R other.txt if that also had requirement.
Just imagine, pip install whatever each line in this text file is you can also have comments with hash.
PyCharm itself, if it's at the top level try to run it you can also open the terminal and it will automatically activate the environment and then you just pip and install -R that one pip install -R requirements that right, but they should all be satisfied.
Finally, all that's standard Python finally though, if you're in PyCharm you can pull up the settings you can go to the project, project interpreter and here you have basically a GUI version of pip.
And I really like this, it shows that hey there's all these different things here I can get an update of that one.
I'm not sure I got the old one but probably something I installed that I prefer to have the old one, so I'm not going to mess with that, but like setup's tools I could update that.
Click that button, give it a second, there you go it was updated successfully and so on.
I can even add more probably here and I could look for Async, here's a whole bunch of stuff to do with Async, right?
Like a install package, this is like the manage NuGet packages in Visual Studio and what happens here or here or here this is like, you know installed dash package in the NuGet package console.
Once we have these installed then they're just available to our project.
So let's go over here and mark that directory of the source's route, give it a program I call it Scrape, that's what we going to do.
Now if I want to work with like say httpx I would just say, import and I hit h right there it is, right?
Because it installed in that virtual environment for this project, basically the one that's active now it shows up.
And we can do our main business and over here let's just say using Python packages.
Let's just print out, just to show you that we were able to import this just the version number of httpx.
Alright, so let's try to run Scrape see what happens.
Boom, using Python packages just like that.
Also a quick comment when I run stuff in Visual Studio it pops into another terminal, like a separate window.
And here it runs it at bottom and for some reason you want the separate window, you just copy what it's doing right there open this up and just paste it there you can see same basic thing right.
I find this pretty handy to have it down here you don't have to keep jumping back and forth but if you don't want to run it separately just copy the command that PyCharm's using most of the time that works.
So that's how we use these external packages.
You will identify the name of them you pip install them either by putting them in the requirements document or literally typing pip install the thing and then they're just available.
You import the name and you can start using the things.
Like I could say get and you know get a URL here and off it goes.
|
|
show
|
2:49 |
Well, we've installed and imported is httpx.
The next thing we want to do is to go through and get some of the HTML and print out the headers.
We're going to start by just getting the HTML.
These are two separate steps, so let's say get titles.
I'll just write that as a function.
Define that down here.
Now, let's say something like this.
for n in range(220, 230) because of what we were working with.
Let's just print out n really quick.
Just to see what happens when we run it.
Okay, good, printing out.
Instead of printing out I want to say HTML equals get_HTML(n) and that's going to be another little function we're going to write.
It's going to be an integer and let's say it returns a string, like that that's going to be pretty good.
Now, you'll see the power of this cool little library.
We also want to work with colorama.
And the reason we want to use colorama is when we do print statements you want to see where these are coming from.
Now, let's say we're going to print this out yellow right here.
Alright, it's upset that we're not returning the string yet but that's fine we're not actually doing it.
So here's what we get when we say there's going to be a response from httpx.get and we need the url.
Url will be in the f-string like so, nice and simple.
Now, I didn't point out on the C# side it's actually a lot of work to make the HTTP client follow redirects and this is a redirect of the real url.
But over in Python, a lot of libraries automatically do that.
So this should work.
And then we can go over here and like let's just print, response, status code.
For episode 220, 200, 200, 200.
Hey, that's good!
I'm not really sure what the HTML is but it looks like it's working.
So let's go over here and return, resp.text.
The other thing we probably want to do is we want to check for errors.
We could say if the errors if the request is not 200 or 201 or if there's an error type of request, do something but this has a nice raise for status behavior that we can add.
I'll throw in an exception if it's not some form of success code.
And finally down here just to make sure everything's hanging together let's print out the first 10 characters or something of this.
Remember, we can use slicing so I can just say this to get like a substring which is a kind of cool thing.
Alright, let's run it.
Doc type, Doc type, Doc type.
Not terribly interesting but it looks like HTML and that's pretty awesome.
So the next thing we're going to do is we're going to have to write a function that says get title from HTML.
First half of our program is done.
|
|
show
|
3:35 |
Last thing to make our little program zing is to implement this method.
So let's do that right here.
This is going to be a string and it's going to return a string as well.
So this is where we start using our second library that we brought in.
We had colorama, we've been using that before.
This got us the request, this is going to do the parsing.
At the top we going to say import this, great we're is not using it yet but that will fixed very soon.
Let's do something like this as for now, and put a different color say Cyan or something.
Then getting title, let's also add a flush equals true on these just to make sure this goes out right away.
A lot of stuff is happening, want to make sure that the buffer gets flushed, sometimes it can be delayed there.
And this, we also need to pass, I'll do n watch as an n, I'm not a fan of the name.
Let's just keep rolling with it.
Great, so down here doing n as well.
So, how are we going to do this?
Well, it turns out again with this library it's incredibly easy.
We are going to create a soup which is the bs4.BeautifulSoup like that.
How does it work?
You give it the HTML.
Now it's going to want another thing here and I'll go and run it and then it's...
I'll show you the warning that comes out, it's not a big deal but I'll go ahead and show it.
So, then we want to get the header, the main title.
So, we'll say header = soup.select and let's give it a CSS thing here, how about just the tag name as h1 because there should really only be one h1, this should be fine.
We'll say if not header, we couldn't find something we'll return missing or something like that.
That's what we are going to say what the title is.
Otherwise, we return header.text.strip() and that's it.
Well, like I said, we'll get a warning here but it's not a big deal.
Warning!
Warning!
Warning!
Let's see, it printing it out, it hasn't actually printed the title yet but presumably it's working.
To get rid of this warning, you need to additionally specify the HTML parser or an alternative HTML parser.
So, what we can put in is right here, just this like this, some quotes.
And also, let's print the title, go with green.
Yes, look at it!
It totally works!
Get in the HTML for 220, title for 220.
Boom, there's the title!
Get in for 228, hunting bugs and tech startups and Python, building advanced Pythonic interviews with Docker symbol and so on.
This is the title, if we were to go to that URL that's what's in the h1 tag.
We were able to recreate it using Python and remember how I said it was super cool.
Over here, we were able to do this in C# in just 76 lines of code.
We did 42 over here, well, 41 really.
How awesome is that!
We can completely super power our applications by using stuff off of PyPI, the way we do it is set up a virtual environment, specify the requirement and we either pip install them all here or we just individually pip install the name of the package you want, and you can import it, use it.
You're off to the races, beautiful.
|
|
show
|
1:16 |
Let's quickly summarize installing requirements.
Now I have a quick little shortcut that I'm using here to create a virtual environment.
The idea is you say Python -m venv venv and then you activate it and the way that happens on Windows, POSIX system or MAC OS, and Linux is slightly different but I've shown you that a couple of times.
So we're going to create a virtual environment and activate it.
And then maybe we'll have some kind of requirements.txt or you can just individually install them.
So here you can see that we're looking at the contents of our requirements.txt.
It has three requirements, httpx bs4, and colorama.
And then when we want to install them we just say pip install all the requirements in this file.
The way we do that is way say pip install -r and then the filename.
This can be a full path, but because we're in this working directory, we can just use the requirements.txt file.
We also saw that PyCharm will manage these and automatically suggest installing them as soon as you type them into the requirements.txt file if it's at the top level of the project.
If it's in some sub folder, PyCharm ignores it.
And you can see it downloads some of the files it will use cached ones if you've downloaded them before, and just installs them in all of their dependencies, and your new virtual environment is ready to start working with all these libraries.
|
|
|
23:12 |
|
show
|
1:13 |
In this chapter we're going to talk about memory management in Python.
Both Python and C# basically take care of memory for us.
So, why should we even talk about it?
Well, on one hand, you can just ignore it, right?
Just create your objects, and call functions and magically things are handled.
However, understanding how memory works gives us a better intuition of how our code executes, how performance works all sorts of things like that.
You probably have a fairly good understanding of C#'s memory management its garbage collector.
We're going to talk about Python's as well.
Now, for some reason, talking about memory management in Python is not nearly as popular or as big of a talking point as it is in C#.
There's a ton of analysis of garbage collectors and algorithms and all sorts of stuff.
In Python there's not too much.
So, what I'm going to do is I'm going to give you a little bit of insight into how Python manages memory so you'll have a good working model of what's happening when you write your code and how it's taking care of memory for us.
But like I said, in both of these languages you don't have to actively do too much to make the memory management work but it's a really good idea to have an understanding of how it's being done for you.
|
|
show
|
4:17 |
Let's begin with a high-level overview of how memory management and the garbage collector works in C# and really in .NET.
As I said at the onset there's no explicit memory management necessary for the most part in .NET and C#.
There's an unsafe mode where you can work with pointers and pin references and things like that but that keyword is extremely rare in the language and people almost never use it.
So let's just assume explicit memory management not really a thing, we're just going to let the garbage collector manage memory for us.
Now, one of the really important distinctions in .NET is between the different types of variables or values you can store.
Data is broken into two categories.
Most things are reference types they're allocated on the heap they're dynamically allocated their references can be passed around they can be allocated one place and be kept alive by another.
On the other side we have value types.
These are mostly like numbers and booleans and stuff these are passed by value, they're allocated on the stack and they're not kept alive they're not even subject to garbage collection at all.
So value types on the stack, reference types on the heap.
When a function returns, the stack is destroyed the value types are gone.
So you don't really have to think about this idea of value types for this memory management story, right?
It's like, it's a different thing.
So we're only focused on the reference types.
Now, .NET has a garbage collector it's going to run when memory pressure is high.
Well, what defines memory pressure?
Mostly it's when the generation 0 gets full, right?
It just going to keep allocating at generation 0.
Generation 0 is small, it's like 16 megabytes or something, potentially even smaller I don't exactly remember the exact numbers and they change a little bit over time, I suspect.
But, when enough allocations have happened it's going to run depending on how it's run previously how much memory pressure there is it might do a small collection, like Gen 0 or it might do a full collection, a Gen 2 collection.
When there's enough memory pressure then the garbage collector decides it's time to kick in, go do it's thing and it's going to run one of these collections.
The algorithm that uses what's called a mark-and-sweep algorithm.
So the garbage collector doesn't actually go around and collect garbage.
What it does is it goes around and find objects that are still around and then it assumes stuff that is in the gaps that must have been garbage so we're going to forget about it.
But it doesn't actually track the garbage it actually only tracks the live things.
As I indicated before there's actually three generations Gen 0, 1 and 2.
And as objects survive, a collection they get promoted from like 0 to 1 and 1 to 2 and so on.
There's a few edge cases that don't quite follow that rule like large objects and so on.
In general, things start out in a probably trash bin called Gen 0, and then once they survive that they go to Gen 1 and Gen 2 and there's fewer collections as you get up in the higher bits.
There's a lot more memory that has to be looked at in order to do a Gen 2 collection than say a Gen 0.
A really important part of garbage collection type of algorithms is that memory management and memory clean up is non-deterministic.
That means I could run almost the same program multiple times and under very slight variations it might actually behave differently, right?
There might be different pauses based on maybe you go to a different size of input but to the same algorithm.
Timing and order of when these garbage collections happen is non-deterministic, usually you don't care usually they're fast, but certain circumstances where you're doing real-time type programing or something like that this potentially could cause some trouble.
Finally, a very nice feature of .NET's garbage collector is that it compacts memory.
So, I've allocated a bunch of objects.
They're likely, if they're allocated at the same time to be related to each other.
And the stuff that was potentially allocated in between them when it becomes garbage the memory is actually moved and squished down like an accordion back together and the stuff that was allocated and survived next to each other is even closer potentially than it was before.
This can help a lot with memory fragmentation and it's a pretty cool feature of .NET.
So, hopefully this gives you a good sense of .NET's memory management story this is generally how things work over there.
|
|
show
|
4:38 |
Let's compare this story we just told about .NET's memory management with Python.
Again, just like .NET there's no explicit memory management.
Now, we can do the C API extension thing for Python and down there you can allocate stuff and obviously that's just plain C so, you know, there's a lot of memory management down on the C side but most folks don't actually write C extensions of Python.
So, outside of that there's not really any memory management that you do as a Python developer.
It's all down in the runtime.
In .NET, we saw that there were value types and reference types.
That's not a thing in Python.
Everything is a reference type.
Yes, we have maybe a string which is a pointer that points off to some string in memory.
We might have a customer object which points off to it.
But also, the number 7 that is a pointer off to some object in memory.
Okay, so everything is a reference type.
Technically, some things like the numbers the first few numbers do this, like flywheel pattern to preallocate it because they're so common.
But in general, the right conception is that everything is a reference type and that reference types are always allocated on the heap.
There's nothing allocated on the stack other than potentially, like pointers to your local stuff, right?
But the actual memory that you think you're working with that is allocated somewhere else.
Memory management is simpler in Python.
In .NET, we have generations and we have finding the live objects and all of the roots of all the live objects and asynchronous collection of that when memory pressure is high, and all of that.
Forget that for Python.
We have reference counting.
Super, super simple.
It's kind of like smart pointers in C++.
I've got an object.
There's three variables pointing at it.
One of the variables is gone because the the stack is now gone.
Now I've got two.
One of those is assigned a new value.
So now I've got one, and then the other one maybe it's set to null, or that function returns.
When that count gets to zero immediately that object is erased.
It's cleaned up.
So memory management is just as simple as every time a thing is added a reference to that count goes up.
If that count number hits zero, boom.
It's erased.
That's faster and more deterministic than garbage collection.
But there's a big problem.
And what is the problem with reference counting?
Cycles.
If I have one object and it originally has a pointer then it creates another but then they refer back to each other well, even if I stop referring to all those from- either of those objects from all of the variables that I have they're still always going to have at least one reference.
Item 1, referring to 2, and 2 referring to 1.
And even if those get lost, in terms of references they're never going to be deleted from this reference counting gc.
Most of the time reference counting works like a charm cleans up everything fast.
But there's a fall back generational garbage collector to work on these cycles and catch that extra memory that goes by.
So, you can think of this as much more like .NET's memory management for the cycles but the first line of defense to clean up memory is reference counting.
Again, that makes this deterministic.
However, memory is not compacted after reference counting happens or this generational garbage collector that catches the cycles runs.
No, memory is never compacted in Python.
That's interesting.
It probably makes it a little bit faster most of the time.
You also get fragmented memory which can make caches less efficient.
Though there are some techniques around this reference counting memory management system that lessen the fragmentation called blocks, pools and arenas.
And the quick summary is each object of a given size- so I've got an object and it takes up I don't know, 24 bytes there's a bunch of memory segments that are meant you know, there's one, let's say this one holds the things of size 24 and it'll allocate a certain amount and it'll keep allocating the things of that size into that chunk until it's full and it'll create another one into these pools and then pools are grouped into arenas and so on.
You probably don't care too much, but the takeaway here is that there are mechanisms in place to help break down the fragmentation that you might run into.
If you want dive deeper into some of these ideas I recommend you read two articles both by the same person "Memory Management in Python" by over here at this link here.
And then "Garbage collection in Python: things you need to know" So they talk about a lot of this stuff with pictures and graphs and actually the C data structures that hold this stuff and so on.
We're going to play around with this in some demos but if you really want to see, like what is the structure that defines these things?
Or what are the exact rules in which one gets allocated or cleaned up?
Check out those articles.
It's too low level of a detail for us to really to dive into here.
|
|
show
|
8:07 |
It's fun to talk about memory management in the abstract but let's actually write a little program that explores it.
So here I've got a new little section and I wrote create a file called mem_explorer mostly so I could drop in this beauty right here.
But we got this utility that will let us reach down into Python's internals and actually let us use the C API to actually ask how many references does this particular object have to it at the moment.
Right, everything in Python down in the C level is one of these.
It's a PyObject pointer.
But what we're doing is we're just getting it back and it has a reference count on it.
We can just say, hey, how many things are referring to you?
Remember, if that equals 0 boom, this object is cleaned up.
But we're going to use this little memutil thing to work on playing around with some ideas.
And we also got another class over here that it gives us more diagnostic information.
But I'm going to start out with some primitive types then we'll bring this other thing in.
So we'll start with our standard layout here.
We're going to have two versions.
So I'm going to have a ref counting version and we're going to have a GC version.
I'll just call it gcing, I guess and let's just define that to do nothing for the minute.
And then we're going to work on this one.
We'll come back later and work on the GC story.
Remember, that's only for cycles in this world.
So let's go over here and create a variable and it's going to be the super fun little string like this.
And then we can get information about it.
I can get the id from our memutil.
Why do I want to get the id from it?
Because if I actually hold on to a reference to it that will keep it alive, and what I want to demonstrate to you is under what circumstances will it automatically clean itself up deterministically.
But, obviously, if I hang on to it I'm going to have a problem.
So I'm going to get, basically, the memory address of it and then I can ask, hey, the thing that used to be at that memory address, is it alive or is it not alive?
Okay, so we're going to create this; and then I want to just print out, we'll say step one.
Now we'll go to the F string; I'll say ref out out not going to put the variable there, again we'll hang on to it.
So I'm going to say, we can go over to our memutil and we can get refs to v1_id.
Alright, well, let's just go and run that and while we're here, tell PyCharm that's not a problem.
Oh, whoops, what did I put, not there; what did I put here?
Oh, I want to, I called that a little bit earlier.
I'm going to, there's a id built in that will give us the id and then we're going to pass it here, okay.
So we're going to get a hold of this id and we're going to see if it's alive.
There we go; right now there are three reference counts to this object.
Let's go over here and say v2 is equal to v1.
This should increase the reference count but won't accept you.
Alright, we run it again.
So, step one the ref counts is three; and then now, it's four.
Why?
Because we have a new variable, v2 pointing over to this in memory.
Remember we got that, and we now got this pointing up to it as well.
Now let's go over here and see if we can start to roll this back a little bit.
I guess we don't really need the id to it, right?
That should be the same.
Actually, it would be exactly the same.
So, in step three down here, what I want to do is I want to basically make that point to somewhere else.
It could go to other, or it could go to none.
Alright, I'll just leave it as other it doesn't really matter.
But now, the reference count for v1 should go down a bit.
There we go, we have one fewer there.
And let's do this one other time.
Let's go to v1, put that to None, this will be step four.
Alright, we'll see what's referring to it now.
To, okay, well maybe a string is not really the best object.
Let's put, I think it's interning.
Let's put some, some data structure like this.
Here we go that's a little bit better.
So, we come in, we have some object we're allocating on the heap is one, have two of them over here, right this one; even though PyCharm is telling us, hey this doesn't do anything, it is doing something it's just not doing anything conceptually productive in a programming language.
But it, this new variable now points at this list and v1 also points at the list, that's two.
Down here we're making v2, maybe we could just make it a list of one, like, another list just so it's more consistent.
So, v2's no longer pointing at the original list it's pointing at this list, but it should still be the same.
Now that makes one.
And then once we tell v1 to stop pointing as well well, there's only two.
There's v1 and v2 pointing at it, and now it's garbage.
It should be gone, okay.
Notice, let's, well, you can't really tell if it's finished, right?
Let's print end of method, and let's go flush equals true just to make sure that there's nothing going on here.
All right, so end of method how do we know whether this was deleted?
Well, take Python's word; it's reference count of 0 so it's probably deleted.
But it would be nicer to know, yes this thing actually got deleted.
So let's go over here and change this.
I'm going to go import, let's say from doomed import doomed.
And doomed is a class.
We can go down here and we can allocate a doomed.
The cool thing about doomed, I'll have to go look at it is that doomed allows you to pass other objects it holds onto but, most importantly it tells you, hey, I was created.
Hey, I was the, the delete thing was called to me.
Very basically, it's like it's finalized, right.
The garbage collector is cleaning me up now and then it also has some nice string representation.
So let's use this version and run it one more time, okay?
So we'll have a doomed, and then we'll just yeah, leave it like that, as None; okay?
So we're going to run it; it's going to be the same thing but the doomed object will describe its life cycle to us.
Alright, so run this.
Also, before we could assign it to variable one it created itself and then we ask hey, how many things point to you.
One, v1, points to it.
Later down here, we added v2 to point at v1 then we said, now how many things point at you; two.
Then we set v2 to nothing, so now that rolled it back to just v1.
Ask here how many things pointed to you.
On line 22 right here, when we said, well we don't want anything point at anymore, reference count hits zero before we could even print the statement, right.
Before line 23, when that went to zero, what happened?
Boom, finalizer effectively was called.
That the object was deleted removed from memory, boom, it's gone.
And then we ask, oh, by the way, now that it's gone how many things point at it?
0, right?
We just couldn't report at the same time that it's being deleted; but like the very next thing once we realize it's zero, like, this makes it zero boom, it's gone and then, yeah we confirmed that right there.
And that's the end of the method.
But there's no garbage collection happening at all.
In fact, we can even just like .net, we could import gc and we can come over here and say gc.disable.
If you thought maybe garbage collection had something to do with this.
Run again, yeah, no, it's exactly the same because nothing here had anything to do with the garbage collection.
It's just reference counting.
You add a pointer to it, it goes up.
You remove a pointer, it goes down.
That pointer hits zero, boom, it's deleted, right.
So it's very very deterministic, right.
You can see it's exactly at every step happening just like you would expect.
You would not see that in C#.
You would have to do things like explicitly call gc.collect each step, or like trigger the GC to run because it's non-deterministic here reference counting, very deterministic.
This is Python's primary memory management model.
|
|
show
|
4:57 |
We saw how reference accounting worked and that is beautiful if there are no cycles that get abandoned.
Well, let's go and create some cycles and see what happens.
Alright, first of all I'm going to disable the garbage collector because I want to see that the cycles are a problem.
So we're going to create actually two doomed objects.
And remember, the dooms, they can take their friends so this v2 is a friend of v1 but let's also reverse it v1 has some friends, one of them is v2.
So, v1 points at v2 because of this and v2 points at v1 because of that.
That will create the cycle right there.
Now, we'll go down here and we'll have a link to both of these and let's change this to gc and we'll put two counts in here.
So, we'll do that in v2.
Let's also put the "Here's the End of the Method".
Now, nowhere in this method yet are we actually dereferencing these objects.
Of course, we should get to the end of the method and then potentially these things might get cleaned up.
We'll see.
Go ahead and run it again, actually, let's up a little bit more space so it's super obvious over here.
So, down here, notice we created doomed at 76.
We created doomed at forty and we asked and they both have two references.
What are those two references?
Well, it's the two objects, the two variables, that we're looking at.
Here.
Here.
v1 and v2, these two are each adding a reference but, then this one pointing back to v1 is adding a second to v1 and this one pointing back to v2 is adding a second to v2.
So, they each have two as we saw right there.
And notice we get all the way to the end of the method then this cleans up.
Let's add one more thing here maybe in main.
Let's print.
Program Ending.
Just so you can see actually this is full on shutdown of Python that's actually cleaning up these objects.
So, there nothing about the garbage collecting that is actually happening here.
Let's go and do a little bit more here.
Let's actually empty these things out.
So remember, before the reason these levels went down is we actually...
Well, let's stop pointing at those objects.
Let's see if that still works.
So, v1 equals None.
And what the heck, let's do v2 equals none as well.
Put step two.
That is going to lower the reference count but it's going to go from two to one for each of them.
It's not going to be enough to get them to clean up.
Method ending.
Boom!
That's it.
It went from two, two to one, one.
That's not enough to get it to clean up.
Well, let's turn the garbage collector on as in stop disabling it.
I'll explicitly enable it.
Normally, you don't have to but just encase we were playing with it above and it's disabled, let's just be really clear.
Well, that didn't do anything, right?
What happened?
Unlike reference counting the gc is not deterministic, it runs when it needs to run.
In order to see the gc signals we actually have to force it.
Just like you would have to do in .net we'd have to say gc.collect.
So, we can do the same here to trigger that non deterministic clean up.
Then again, almost all the time we do not have cycles reference counting is all we need it'll clean everything up.
But when we don't, we can have this gc.
So, let's go down here and well put a print 3.
What goes before three, is a gc Collect.
That should go look for all the cycles and find this one.
It should clean them up.
You should see doomed the two doomed objects explaining their death or predicting their death.
And here you have it.
Alright, so we create that object.
It's 2.
And then we dereference the variable that is one and we still have one, one for the cycle.
But, then we call gc.collect, which goes and says Here's the cycle, we're going to clean it up let's detach that and start killing them off.
That's these two lines right here.
And after the gc run now it's zero, before the end of the method.
The first example we looked at reference counting none of this was necessary.
Reference counting completely solved it, but once you get these cycles that's the problem with reference counting.
Here we have Python's backup garbage collector kicking in we kicked it to kick in, but it would kick in on it's own eventually to clean up these cycles and it works like a charm.
That's memory management in Python.
In some ways it's a lot simpler than .net with the reference counting and in others with these cycles it's actually quite familiar.
|
|
|
54:37 |
|
show
|
2:16 |
This is a chapter I'm super excited to present to you.
So far we've been building little, tiny command line-driven applications and those were fun and I think we built some cool things.
But now we're going to build some true beautiful applications as web apps with Python.
Web development in Python is pretty special.
It's actually one of the two areas where Python truly shines.
Python is great at a lot of things.
It's especially great at developing web applications.
Now I can hear what you're thinking and I've come from your space and I know entirely how it feels.
C# is compiled to intermediate language that intermediate language is JIT compiled to machine instructions.
Even ASP.NET templates are compiled down into assemblies which are then JITed into machine instructions.
Surely ASP.NET is clearly a better choice than whatever we're going to choose for Python.
It's going to be faster, it's going to scale better.
That's a totally reasonable line of thinking but it's actually super far from the truth.
Python is really good at web development.
Deployment is fast and easy.
Deployment on Linux is basically the default.
Means super-cheap cloud hosting, easy dev ops.
The web apps actually perform incredibly well.
If you look at what web applications are built with Python there's a bunch that would surprise you.
Reddit is built on Python.
Quora, the Q&A site.
YouTube, YouTube is built on Python and they have millions of requests per second.
Per second.
Even the site that you're looking at now or if you're on one of the mobile apps the site that the mobile app is talking to training.talkpython.fm.
This site is built in Python, as you would expect, right?
But I can tell you it's an absolute joy to work with.
The performance is incredible.
It gets literally millions of data-driven requests through the web framework in Python per month.
And yet, if I log in, the CPU level's at like 2%.
The response times are low milliseconds.
10, 20 milliseconds for data-driven requests.
It's really, really cool.
So, if you're wondering about whether Python makes sense for web development just put that on hold, suspension of disbelief that sort of thing, move it to the side, and enjoy this.
Because it turns out Python is actually really a good choice for web apps.
|
|
show
|
3:55 |
When you think about building web applications in .NET there's basically one, single option.
Yeah, I know there's some open source frameworks that you could technically bring in, but if you go out and survey .NET developers What kind of web app are you building?
You can bet that this is an ASP.NET application.
It might be traditional ASP.NET or it could be ASP.NET Core if those groups have moved over to the newer version of .NET to be cross platform and all that, but you can bet it's ASP.NET.
So here's, Okay, I'm going to go create a new web project, in Visual Studio, what do you get?
Well, I could have a ASP.NET Core Web Application or I could have a Blazor app.
And by the way, Blazor is incredibly impressive.
But it's backed by an ASP.NET Core app.
Or I could create another one based on ASP.NET the .NET Framework, and it's also Web Forms, MVC or Web API, but you can tell it's basically ASP.NET without very small runtime change.
GRPC service, yeah, also in .NET.
There's basically one clear way for building web applications, ASP.NET.
This is both good and bad, right?
Like, on one hand variety is the spice of life, that's really cool.
But on the other, you kind of know what to do, the dueling is all focused around this one thing, so that's great.
Python is actually quite the opposite it's a little bit dizzying and if you come from the .NET world and the ASP.NET world, you go from this clear choice where there's like minor variations.
Oh, is it a Web API or is it just like a MVC app, I don't know?
That just blows up into a thousand options.
So Python is quite the opposite.
We have Django, you've probably heard of Django it's one of the more popular ways to build web apps.
My mind is a lot like web forms, a little bit.
Not in practice, but in that there's a lot of big pieces you can kind of clunk in there and just get going and that's got a certain appeal, it's pretty nice.
We also have Flask, Flask is more like ASP.NET MVC very lightweight, it doesn't put a lot of structure in place for you.
We also have Pyramid, I built my sites with Pyramid.
I absolutely love Pyramid, I adore this web framework.
It's not quite as popular as the other two but it is really, really nice.
It has probably the best templating language out of all the frameworks that we'll see, Pyramid is really nice.
We also have Masonite, Masonite is inspired by Laraval and it's a pretty cool framework with a lot of command line utilities to generate new elements of your web app.
Starlette is one of the new fancy Asyncio very concurrent, friendly type of framework.
A lot of other frameworks are actually based on Starlette, as well.
We have FastAPI, this is a cool way to build APIs and it has it's own API, it's own programming model.
You have Sanic, which is another one of these Async friendly, high speed, high concurrent frameworks.
You also have Tornado, this is the original concurrent framework, it's been around for a long time.
It's used in a lot of interesting ways, as well.
This is actually a small sampling of the choices you get to pick from.
Again, spice and variety and life and all those amazing things, and yet as a newcomer to this ecosystem, I don't think this generally comes across as amazing.
I think it comes across as confusing.
Alright, well what is the tooling?
Like if I pick Flask versus FastAPI what do I, is there a better IDE to use?
Is there like a project that I can create what libraries are available for?
All this stuff is, it's quite overwhelming.
When you compare the two ecosystems, it really comes down to with the .NET world you have ASP.NET.
In the Python ecosystem, you have a ton of different open sources options, none of which has the major backer of Python itself.
Like Microsoft or ASP.NET, behind it, right?
So it's much more of a pick and choose and wild west that we're going to talk a little bit about how to make some choices here.
But once you kind of find your way, whatever you pick really, there's a lot of good options out there.
I think you'll be comfortable.
|
|
show
|
2:04 |
So which one should you choose?
Well it turns out, some are more equal than others.
Here's an excerpt from a cool survey done by the Python Software Foundation basically the organization that leads and oversees Python in concert with JetBrains, who as you know make PyCharm.
So they came together to try to figure out what does the ecosystem for the Python world look like?
Now this is probably the biggest survey with results out at least to date.
So their 2018 survey, which is a little bit old but still totally relevant.
And one of the things they asked was what web frameworks do you use?
And here's a big, long list.
Flask, Django, Tornado, Pyramid.
Well those we talked about.
There's also others, like I said, there's so many.
Web2Py, Bottle, CherryPy, Falcon, Hub, TurboGears, none.
And then somewhere between that 1% and none is all the other ones that I mentioned.
And actually you might thing they're not popular but that's not necessarily true.
Some of those frameworks haven't even been around for two years, so they wouldn't even show up in this list 'cause they weren't born yet.
Alright, so that's one thing to consider.
But another is Flask usage has grown 15% since the last survey a year prior.
So if you look at the framework that has the most mojo or the most excitement, the most growth behind it I'd say it's probably Flask.
It's been Flask and Django were pretty close to tied for a while in some surveys and now Flask seems to be pulling ahead.
So we are going to work with Flask because even though I'm an absolute fan of Pyramid and I actually like it better than Flask I want to give you all the best experience in terms of what you're going to run into out there in the world.
If you pick up some kind of web app and you start working with it and it's Python it's a good chance that it's Flask.
Or you go and look at a tutorial and some tutorial like hey, I want to use Docker and some Python web framework good chance that it's Flask.
So we're going to pick that even though, like I said I'm a big fan of Pyramid, I like Flask as well.
It's got a cool MVC ASP on it, MVC feel to it and it's pretty straight forward.
I think you'll like it too.
|
|
show
|
2:57 |
Let's talk Flask for a minute before we start working with its API.
It's famous for having incredibly simple start-up story.
Create a single-file, put a couple lines in it and, boom, you have a Hello World web app.
In fact, that's technically true but it's not very realistic 'cause, in a real web app you have a lot other stuff going on and you shouldn't cram it all into one file.
You should actually have it structured well so that your controllers are separate from other parts of your app like your data access layer and so on.
Nonetheless, Flask is really easy to get started with.
We're over here on the Flask documentation page.
It's part of this thing called the Pallets Project which is Click, Flask, ItsDangerous Jinja, MarkupSafe, and Werkzeug.
These projects basically make up Flask plus Click is a little bit larger as well.
But it's all overseen by the same group primarily by David Lord but also by other folks.
Now, the first sentence here is Flask is a lightweight WSGI web application framework or sometimes, that is pronounced wiz-gee.
I like to pronounce it as wiz-gee personally.
So what is WSGI?
WSGI is Web Service Gateway Interface.
Now, you saw there were that massive variety of web frameworks: Flask, Django, Pyramid, and so on.
There's also a vast variety of servers that run Python web apps.
So we have Uvicorn.
We have Gunicorn.
We have uWSGI.
We have a bunch of other things.
So, just like we have a simple choice with ASP.NET in the C# world, you also have the simple choice of Well, we're going to host this on IIS.
That's probably the most common way to put ASP.NET web apps into production and host them on IIS, Internet Information Server.
In the Python world, like I said we have this wide variety of frameworks and a wide variety of servers to run them in.
And this Web Service Gateway Interface is the common API that allows an arbitrary framework to plug into an arbitrary web server.
So you'll see this.
You also may see ASGI.
This is the Asynchronous Server Gateway Interface for the async enabled, more highly concurrent frameworks but most Python web apps are these WSGI web apps.
Like a large portion of Python libraries and frameworks Flask is, of course, open-sourced.
So here it is on GitHub, github.com/pallets/flask.
And you can go get the code, check it out, even do PRs and suggest changes to it, right?
Notice one thing over here.
It's pretty popular, 47,000 stars.
I just looked, right before I hit Record over at the ASP.NET Core GitHub Repository has 14,000 stars.
It's a big difference.
This is like four times more and it's even more impressive when you think of the fact, like Well, yeah, there's Flask but there's that huge variety of other options, whereas ASP.NET, that's kind of the clear choice, right?
So this is a really popular web framework and there's tons of tutorials, lots of documentation lots of articles, and extensions that you can plug in this framework.
It's a good choice to get started with.
|
|
show
|
1:27 |
I said I was excited about this chapter and I'm excited because we get to build a really cool application and building apps that are fun well, that just makes me happy.
Here is the web app that we're going to build.
Guitary, going to rock your socks off.
So imagine we've created some kind of guitar company custom guitar manufacturer, and we want to create these and then sell them on our website.
And this is our website.
Notice over here we've got the stuff.
We've got some cool art and pictures of guitar stuff.
But more importantly, you can browse the guitars.
So if we go over here and browse, here are our guitars.
We have our AX Black our Acoustic Black our Weezer Classic.
We have a bunch of cool guitars down here.
Now this is all the guitars.
home/guitars.
All right, get it this way also pass all, doesn't really matter.
We can also just look at just the electric guitars.
Or just the acoustic guitars.
Now you can't drill into them.
Kind of stopped at this point.
Basically have this catalog of guitars and we can go and filter them and so on.
Go back to our home page.
And right now this is static and memory data.
Ultimately, later down the road when we get to the database section we're going to store all of our guitars in the database and query them.
So this is the web application we're going to build for this chapter and we're going to continue to use it in the database chapter.
Like I just said we're going to take this static data and we're going to put it into the database and then query it back for this page and others.
|
|
show
|
3:55 |
Before we start writing the Python the Flask version of guitar, of course we have a C# version, course they built you a C# version.
And why did I build it in?
Well, ASP.NET Core, of course.
So let's just quickly poke around this ASP.NET web app.
Maybe you're familiar with ASP.NET but just because you're a C# developer doesn't mean you've done web development over here.
So I'll try to give you a quick fly over but again this is not the main focus.
The main focus is rebuilding the equivalent Pythonic web version the proper idomatic version of the Python web app.
But nonetheless let's understand where we're coming from.
So let's start with the controller that's probably where you want to start with an MVC framework ASP.NET.
So this app turns out to be pretty simple.
We've got a little dependency injection thing that we're actually kind of ignoring going on here but then we have a couple of methods basically only two action methods and then this Error method which ideally we don't hit I guess, but maybe we will.
So this is the home view, that is that's this view right here with the sort of landing page and we will rock, that sort if thing.
And then we have the guitars and over here we're using view models.
Really nice design pattern that I love.
We're going to create one of these view models and pass it off to the view.
Now, this is for the guitars and we're going to say the style is either all or acoustic or it's electric, right?
That's the part here where we're looking the guitars and we're switching between these three.
So we're pulling that out, it's being passed its part of the URL right up here.
guitars/acoustic.
So guitars, acoustic and it's being passed over here going to the data base and filtering it.
So I guess a good place to go from there would be like here.
So here's our view model it has some basically the input data, and then it has the guitars.
We have a CatalogService like our guitar catalog I guess.
And it can go and get all the guitars based on style.
Let's go check that out.
Now here we have our turns a guitar array pass in a shop style could be empty, like I said this is static data for now it's going to be in the database soon.
If there's no filter just give all otherwise we're going to do a link to objects query for now.
This is going to be a LINQ to Entity Framework later.
Going to say give me all the guitars with the style such and such give me that guitar back.
And I guess the last major thing to look at is just the views down here to round it out let me stop it so you can get this out of the way.
When we go to our controller here and we call the guitar method with the right stuff and we say return view with this view model.
What it does is it goes to this Razor template over here.
And this Razor template of course shares this overall layout that we're using.
So what we're going to do is we're going to say that view model is this that we come in statically programing gets it.
Our little top bit this is just a padding for the linment of around the navigation.
We have our custom guitars and these are the buttons.
So if we were over here those are the buttons you can click there, and then for each guitar we're going to spit out a little div that we're going to float, not float we're going to put as an inline block and it has an image which is the guitar image, the guitar name and the guitar price.
That's it.
That's right here the image, the name and the price.
I guess you could probably use a dollar sign, something like that right there.
Not really required but we'll go ahead and throw it in there for the future.
And that more or less is the web application, round one.
Later when we get to doing something more impressive with the database, but for now this is the web app that we're starting from in C# and we're going to rebuild in Python and Flask.
|
|
show
|
6:05 |
Let's build our first web application in Python.
So we're going to create a new chapter section over here.
We'll call it ch07_web.
And again, we want it to be treated like its own top-level thing.
So I'm going to say 'mark directory as sources root'.
Now, in here, we're going to create our first web application.
And we're going to create that Guitary project.
That's out litte startup name.
So we're going to create a folder called guitary and we're going to basically use this as our top-level webb app.
Now, Flask has a really simple and easy getting started thing.
I could have come up here, even, and said New Project go over to Flask, pick off some of the settings and it will go and create that.
Alright, it will create the virtual environment it will set up the project structure, it'll choose the right templating language but, just to make everything a little more obvious and clear, what all the steps mean the first time here I'm going to go and create it from scratch.
Maybe you're using VS Code, or something like that and it doesn't have a file named Flask project story.
So the way it works is you just want to create a Hello World now and then we're going to create Guitary on top of that.
So what we're going to do typically the startup thing here is called app.py.
And we're also going to have some project structure.
Let's go ahead and make that now.
We'll have templates.
These are like the equivalent of your Razor CSHTML files the dynamic HTML files, and then, eventually we're going to have static content as well and I like to break this up further.
I'll go ahead and copy stuff over but we'll have an image, a Javascript, a CSS folder maybe even more divisions amongst those.
But for now, all we need is this.
So, what we want to do, is we want to import flask.
Now, PyCharm right away says, Whoa, whoa, whoa!
There's a problem here, I need to install Flask.
So we come over here, it will install it.
Remember, the most appropriate way would be to come over here and type flask and then rerun, down here.
pip install -r requirements.
And I would pick up that new change and get it.
But, you know, since I've already shown you that let's just see one more possibility.
In PyCharm, I could actually just hit alt-enter and it will show me hey, you can install Flask.
Let's go in to install it, in a second.
Now we need to go ahead and use it.
So the first thing we need to do is create an app.
The way we do this, this is a really important part of the project.
It contains the decorators and all the stuff that drive the project.
So we say flask.Flask, then we need to give it a name and typically the import name is going to be the same as the file name.
So Python files have this __name__ ambient variable always defined and it's like 'app' or 'main' or something to this effect depending on how it gets read.
It's either the file name, the module name or it's main if you run it directly.
Now we have this installed on Flask now sometimes it'll say you should add it to the requirements but it's not doing that here so, before I forget let's go and add it but I was hoping it would let us show you but again it's, I think we have too many projects going on, all in one giant meta-project here.
So, what we need to do, is we need to define in the MVC, ASP.NET MVC world, what would be called an action method.
In Flask these are often referred to as view methods, but same idea.
Here we're going to define an index and it's currently going to take no parameters.
And then we're just going to return, "Hello World".
Something like this.
Now this seems great, except for how does Flask know that this is a web method and not just some utility function or some other random thing.
You add a decorator, so we say app.route.
Like this is where we specify the route or the URL so this'll just forward-slash and this is going to define just the homepage of our website.
The last thing we want to do, is we're going to run the program, we're going to basically start up the web server and start listening for requests.
And remember I have my cool little extension here so I'm going to create a main, and in my main we're going to call app.run.
We could say even, debug equals true.
It'll do things like, check to see if we've modified the file and then auto-restart it and we'll see how that works in a sec.
Alright, so let's just go try to run our Guitary here.
And that's a pretty good sign.
First of all, it says, this is a development server so just beware, don't put it into production use a production WSGI server.
Remember we talked about WSGI and Web Server Gateway Interfaces?
These are uWSGI, Gunicorn, things like that.
We'll get to that at the very end of the course.
Alright, so let's run it, Ahhh!
Hello World!
We do a 'view source', Hello World not too interesting, it's not really HTML but, you know, the browsers roll with it anyway we can just show that on the screen.
So here is our is our "Hello Flask" what we've done, is we import Flask we've installed it, and put in the requirements.
We create a singleton, super important singleton app instance here that's shared throughout the rest of this entire application.
And then we use it to, amongst other things you know, specify the URL routes on any method.
This doesn't have to be called index it could be called, whatever.
By the way, notice that when I made that change it said, oh, oh this file has changed, it's reloading in the debugger.
Or reloading the project, and now it still works even though I called it some random thing.
That's important because the Python code here if it changes, it doesn't automatically restart the app usually, only in this debug mode.
So, for example, in production, if you make a change to like the logic of this method.
It's not going to reload it.
In some frameworks, even the templates get cached and not reloaded, so that's the big difference between debug and not debug.
Then once everything's all set up, we just call app.run and we have a website.
|
|
show
|
3:51 |
Not to undersell how awesome our Hello World Flask app is but I do think it could be a little bit better.
We're going to use some dynamic templates obviously what we want to do is, do the logic here inside this not the presentation right.
We want to use static HTML and shared layouts so that we can have a uniformed look and feel across our whole site and so on.
What we're going to do is go over here and just help PyCharm be a little smarter we're going to tell it to mark directory as template holder - oh and also this is important it says none of the languages are selected would you like to pick one?
And so the idea here is if this is set, it says if you're going to try to work with some HTML what scripting language do you like?
I absolutely adore Chameleon.
It is so nice, I like it even better than Razor which I think is a pretty clean syntax on the .NET side but unfortunately the only two that are supported by Flask are Mako and Jinja and Jinja is more popular and decent.
So, we're going to go with Jinja, now what we want to do is I typically like to have that name tied together with the HTML file name that we're going to have over here, like this.
So this is the Jinja template and it doesn't have to have any special syntax to start it's just HTML.
We'll put something like this, we'll say Guitary and then we'll just put a little message, like hello there and we want to put the name so that's double handlebars or double curly braces and we just say name.
Now where does name come from?
Well we got to pass it along don't we.
Let's spell our little start up name okay so over here what we want to do instead of this is we're going to say Flask that render template and then we're going to give it the name.
Notice that autocomplete, how cool is that?
Autocomplete in there and then we need to pass in see that star star context?
That's a keyword set of arguments that turns into a dictionary, the keys of that dictionary are then addressed right here.
Ultimately looks like its name is equal to Michael.
Let's run it again, in fact it probably should've restarted itself, yes it did sometimes if you mess up the code it'll crash and it can't continue to restart itself so I like to restart it manually a lot because it's just a hotkey but f*** it let's go here and click this.
Guitary!
Hello there Michael.
Okay, that's pretty cool.
Let's say, we want to pass some more things let's say 1, 2, I say 1, 1, 2, 3, 5, 8.
We'll call that numbers, nums So these are, I don't know, our favorite numbers or something, let's just look over here real quick at a little bit more syntax.
What we want to do is we want to have just put a ul and we want the ul to have an li, this is a cool trick you can do in PyCharm, see I went five li's here and you can just, boom, go out like that.
That's cool but what we want is actually just one of them and we want to generate this for each one of our numbers.
For when we have inline code what we do is we say curly brace percent and when we say four, write just a little bit of Python here and N nums, we want to put our thing, the value and then we say N4, do a little formatting let's go over here and refresh here we go.
Guitary, hello there Michael nums, 1, 1, 2, 3, 5, 8 obviously the Fibonacci Sequence up to 8.
So this is the basic way that we work with these Jinja2 templates.
You can print out the string value of something or this can actually be a Python expression I could say like, lower, for example, right but ultimately it takes the result of that expression and puts it out as a string or you can do conditionals, if I said if num, whatever, I could conditionally show this li, here I'm doing a for in loop I'm just looping through them and spitting them out.
|
|
show
|
2:20 |
Time to bring in our web design and to add another template and view method to get things to look a little bit nicer.
Now, this is not a web design course so I'm just going to grab our CSS, our images and this vendor is the snapshot in time of a couple of things like Bootstrap.
I'm going to drop that over into our static and just copy and paste and it'll do all the right things to put them there, right adds them TicketHub and so on.
And here's our guitars that we saw in the beginning.
But there's our static stuff.
And I want to also copy over couple of templates here going to recreate the guitar one.
But let's copy these two.
Now, notice we have an _layout.
This might look familiar to you as well and then we have the index.
I saved the one we created under old just so you have it but now, we have a new one and it really has no dynamic data in it whatsoever.
It just has some styles, right?
Like, here's that big, colorful section at the top.
It's going to have some navigation.
The navigation comes from over here.
And the way that you create these outer shell and then embedded, little pieces is We just have standard HTML.
This is the wrapper common look and feel.
Here's our navigation.
PyCharm thinks things are misspelled and, yet, they're not.
Then you just come down here and you say, we're going to have a block called main_content.
So this line right here is going to inject whatever is in these other template files into that segment assuming that we go up here and we say this template this one, this one extends _layout and then here's a title section and here's the main bits, right?
So this is how we break this up into a common look and feel and then the things that's special per page.
Let's run this again.
Hit that.
And look at this.
Pretty amazing, isn't it?
I'll zoom it back a little bit so it fits on this screen.
But there we have our cool web design for our project.
We've got our navigation and so on.
Now, of course, if we click on Guitars /guitars doesn't exist.
Maybe we misspelled it or, more likely, maybe we haven't written it yet.
Here's how we get this.
You have to trust me that this is going to be a common look and feel around the whole site but soon as we add another one, the Guitars one we'll see that that is, in fact, true.
|
|
show
|
2:35 |
Well, we have our homepage.
Lets add the really important part the guitar catalog where you can go in and browse and filter in theoretically by so were going to just duplicate that.
And when we go over here and say /guitars like so.
Now, here's a thing that's kind of funky in Python.
You got to be really careful about...
This name exists but if you define it again in pure Python this is not an error.
This one replaces that one.
Kind of wish that was an error, but its not.
So you got to be super careful to remember to change this name because you don't get a compile error.
Although Flask itself does check, if you put an app.route on both it'll complain that you can't have app.route on two different copies of the same named thing, for example Alright so let's go here I'm going to have a guitars and we're going to get that by just copying and pasting that.
And let's change the up here, we don't want all this business.
This is going to be much simpler.
It's just going to be guitars or something like that we just we'll put it in some details in a minute.
We're also going to need to pass some data, so I'm going to say guitars, oh nope.
Again, main conflict here lets say guitar list or something like that, equals i'll put it empty.
We're going to figure out how to get ahold of that in a second, but lets just say guitars equals this empty guitar list over here.
So we could do something like...
Here we go, we'll put the guitars out.
Obviously we don't have any details for them.
This is going to be the basic idea, so we're going to have some buttons.
Right here, like this little comment.
Going to put some buttons here, this is our electric guitars, acoustic guitars, all guitars and then we're just going to show them in a bunch of DIV's; With some CSS styles, and that's pretty much it.
Let's see if we fixed our crash.
Can we browse the guitars?
Yes!
We can, now there's a few stylistic things that are massively broken.
Remember we need something up here to push that down.
So that it shows up and doesn't go behind the navigation.
But notice, we have this cool, calming look and feel and then we do actually have this working, even though it's sort of hidden their.
So pretty cool, we've got this guitar view added, so all we did is just add another method give it a route here that is unique and then we just called the template.
Also notice in PyCharm, that you can jump over to the template super easy like this.
And that's pretty awesome actually.
|
|
show
|
5:08 |
Now that we have this route in place the next thing to is actually have some real data.
The ultimate goal will be to store this in a database using ORM to query it and get it back.
But for now, we're going to just create a class and then some data access layer to get that data out of the memory.
The way we're going to do this in the ORM is we're going to create a class that models guitars and that also makes sense for us here.
So, let's create a data folder and then a new file called guitar then here we'll have a class, capital Guitar.
And it's going to have a couple of things let's say it's going to take a name, which is a str a price, which is a float, an image which is a string and a style, which is a string.
Now I could go down here and type self.name = name but, PyCharm will do it all for us.
So we just start knocking them out.
The only thing I don't like about this is that it puts it in reverse order.
But, that's okay.
So here we've defined a guitar class.
It doesn't really have any behaviors yet but remember, the goal is that we're going to adapt this to our ORM later and it's going to take on more functionality.
For now, it's just going to be like a data structure that holds information about a guitar.
Now the next thing I want to do is add a section I call services.
In services, these are not like web services but these are parts of our application that provide services to the other parts.
So, the part that does all the querying and management of data around, say, guitars or the catalog or something like that.
I'm going to create one of those.
So I'm going to create something called a catalog_service.
It's a little bit like, maybe the repository pattern but, way less structured.
This is where the data access stuff for guitars and other stuff to do with the catalog like categories, goes.
Now we've come to a really big difference in Python and C#.
If I was doing this in C#, what would I do?
I would create a CatalogService class and I would give it either static or instance methods.
Especially if I'm giving it only static methods what is the purpose of that?
The purpose is to group a bunch of functions together that I can call and I have to do that in a class because C# doesn't have standalone functions.
This thing groups stuff together and we can just put functions in it.
So, we don't actually have to create a class but we can just have functions and it's every bit as meaningful as having a static class with only static variables.
So, let's go over here and create a function called all_guitars.
And it's going to take a style, which is a string and it's going to return a list of guitar.
So here we've got to start importing things.
from typing import List and we've got to import guitare data guitar.guitar like so.
There we go.
Perfect.
And let's ignore the style thing for a moment.
In fact, so let's say guitars is equal to a list.
I'm just going to copy some data over because it's not worth you watching me type this all and it's got to be just right with like, URLs and stuff.
So, here are some guitars, and notice it's complaining that we're not returning the guitars.
Again, later, this is going to be a database query but for now, we're just going to jam it here because we want to handle databases separately.
We also want to show the most expensive ones first.
So, we'll say guitars.sort and use the little lambda expression to do that.
So we'll say key=lambda g.
How do we want to sort guitars?
Let's say we want to show the most expensive ones first.
How are we going to do that?
We say, the sorting thing is, we want to have a descending so negative is one option, g.price.
Here, you go, spelling is hard.
Okay, perfect.
So g goes to negative g.price or we can sort ascending by price and then say reversed, reverse=true.
Take your pick.
Maybe this is a little more explicit at what's going on so, I guess we'll go with that.
So for now, we're going to ignore the style and we're just going to return this just to see that we're getting something here.
Let's go to our app and say, up here we're going to say from oops, too low.
from guitary.services import catalog_service.
Remember, if this was a static class the benefit would be I could go, catalog_service.
and get a whole list of the things I could do, like this.
So, here we go.
And for now we're going to pass None for the style.
Which I guess we probably want to tell it that this is an optional string?
Which is what that little warning was about there.
Notice that it's gone now.
Alright, I think this will do it.
Let's give it a try.
Also we made a mistake somewhere.
Moment of truth.
When I click this button, what happens?
Yes!
It's incredible!
Sort of.
This are our guitars.
Remember, that's the default representation of a guitar object or in a class.
It already has a class name, object at some address.
But there it is!
It looks like we went to the database, got our guitars and were showing them here.
Obviously, we have some HTML work to do but, we're on a good path.
|
|
show
|
2:12 |
While we're down here in this all_guitars part of our catalog_service let's go ahead and use the style here.
If this was an in memory thing in C# we would use LINQ for Objects and we'd do some sort of filtering and querying.
We can do something similar here remember we have list comprehensions and generator expressions, and so on.
We'll say filtered guitars.
It's going to be a list comprehension so we'll say g or g and all the guitars And then what do we want to task?
Well we're passing in a style up here.
We'll say, if style is None ah, well let's do a little test of that earlier.
We'll just return it, yeah.
We don't want to put that into our query.
Say, If g.style == style.
That's it, if they pass in electric we're going to get that one.
There's some acoustic ones up here somewhere.
There's the acoustic one right there, so on.
Now let's return our filtered guitars.
So let's get our guitar thing over here to print out a style, like so And run this again, and we'll be able to see when we click on this.
Ah, we're not getting so much are we?
Because I don't know why.
Why are we not getting so much?
Oh, because we're not checking the case.
We're not passing None.
Let's just fix that real quick.
If style is None or style is all.
Here if that doesn't work it won't return guitars.
Alright there they all are.
Electric, acoustic, electric, electric all mostly have electric this is where I could find all the good non-rights protected images so they're mostly electric there.
Anyway we want to pass in something for our style.
Let's just go over here and hack it for a sec.
Instead of passing None, let's pass electric.
See what we get.
All electric, let's go and change this to acoustic.
Save it, it should refresh automatically.
Now it's just acoustic, so it is filtering our guitars correctly we just don't have a good representation in HTML but it looks like our catalog service is doing the job.
|
|
show
|
2:56 |
Well we were hacking in the style here, but let's be a little more specific about this for the moment.
Let's suppose we want to find a way to pass the style over and let's actually go and add some buttons to make that happen.
I put a little thing about buttons here and let me just put a few br.
Were going to have a proper style in a minute that pushes these down but let's just go in here and say we want to have some buttons.
And I already have some CSS style so I'll go ahead and apply them here and some filters and then we're going to have an a that has a class btn, and a class btn-dark.
Is it from Bootstrap and I want three of them and I would really like some new lines but y'know you get what you get right?
This would be all guitars, electric and acoustic guitars.
And then we need some URL's here so this'll be /guitars/all, /guitars/electric were just going to pass the style as part of the Url here okay.
And acoustic.
So this is going to be good let's see what we got.
Here if I refresh this, oh nice these buttons are looking good.
Right?
Now you can see the Url is goin up here but we're again getting this not found thing.
So the next thing we need to do is set up some routes in ASP.NET and we see you have default routes.
It's always /controller/actionmethod and then an optional /id which could be our all electric and acoustic.
We don't have that in Flask.
You got to be super explicit.
It's sometimes annoying but sometimes nice.
Also we can just have another route, something that goes here so we can put it in angle brackets and say style.
Now there's a warning, and what is the warning about?
So lets go over here and say that this is a string, it gets past it like so.
And then it needs a default value because this right?
So were going to say by default its None, but if you pass a style its whatever you passed over.
And now let's go and run this one more time see what we got.
Okay here's our guitars, there's all guitars notice all of them listed here.
If we ask for just electric, I'm sorry just acoustic look at that!
It's filtered down to acoustic, electric, acoustic all.
So nice, so that was incredibly easy to add right?
We added this, we added this, we added a little type-hand that was optional but makes our world better.
You can constrain these to be like integers and other stuff but, this is just a string so there's no real constraints upon it.
And there we go, now were fully implementing this like this is, this is done.
This needs to go to the data- base eventually and this template needs to do a lot more work but from the actual implementation of the guitars view-method, that is it.
That's pretty slick, right?
|
|
show
|
2:40 |
Well it's time to make this guitar content look good.
We're providing all the data and the behavior that it needs, but it's not so nice.
So let's go over here and we already have a few styles in place.
So we're going to have a div, and this is going to be a top section.
We'll just go back and look at this now notice it's pushing this part down just like it should.
The next thing is we're going to put in this section all of that, into a div, and we're going to set the class, the text content, and to guitars, like so.
And then we put that.
That's looking pretty good, let's see how we're doing now.
Alright, looking better, notice it's got some centering going on, some spacing and whatnot.
Now the last thing to fix is this.
That is really a problem right there isn't it?
So we want to do a lot more than just drop the type of guitar here.
Just going to have a class guitar, singular going to have an image, and the image is going to be the image URL that the guitar has.
If we look at the actual data, which is here it's /static/image/whatever.
We want to put that in here, so how do we do it?
Double angle bracket, g.image.
Notice the auto complete, that's pretty killer.
Let's put a br here, I guess we probably don't have a style that says that it breaks that's a display block, that's fine.
We'll have a span, and have some classes on it also.
This'll be a guitar name, and you guessed it we want the guitar name here.
So we put the name of another one which is the price, and here we have price and let's go ahead and put this like so.
Now this might be what we want but it might not be.
So we might want to have a slightly better representation of the price but let's just see the progress that we're making.
Ready for it?
It should be pretty close.
Boom!
Yes, that looks so awesome!
That is really, really cool, and look at this this is it!
That's all we did, and if you look at the web side the data, the server side bits look over here, that's all we did.
This is incredible, and I guess the final bit to look at is this all guitars, right?
Super simple query.
So this is a really simple app but I feel like it looks quite nice and yeah there's some CSS, and images and that does add a lot to be honest but still, pretty nice.
Let's see if it's actually functional.
What about just acoustic guitars?
And electric guitars?
Notice we have our Acoustic Black which shows up at the second one when we have all of them, but not when we have electric.
This is workin' folks, this is really, really nice.
|
|
show
|
1:01 |
Well, this is nice, I really love how it came out.
Except, if you look carefully wouldn't it be nice if there was a comma right there?
1,299.
If we have more than, you know, three digits?
So, it's really easy to do that in Python and we just use string.format.
I don't think in the course yet we've used string.format, we've only used f-strings Python's equivalent of C#'s string interpolation but we're going to do that here to get a better representation.
There's probably a ton of ways to do this but we can just put two quotes here and we can say, we want something formatted in here zero, quoting point, and it has commas for digit grouping and we say .format, this.
Let's see if I got that right, whoops, no, I did not.
There we go, let's see if I got that right.
Try again, ta-da, there it is.
So, you know, nothing major or groundbreaking but if you want to control the two string bit here you just write a little Python expression with a string.format, and you can go crazy.
|
|
show
|
1:12 |
We've been working with our Flask app for a while but lets review the main flow of what we have to do to create, at least the server side Python code bit of our Flask app.
So we're going to start out by defining an app instance.
This is a singleton for the entire application.
We create just one of them and it's a Flask class instance.
Then we're going to define an action or view method.
Here we have the guitar list that's going to show all the guitars.
It's going to accept a style.
Notice it has the app.route there.
So we're going to add the route decorator to the guitar list method.
And we can pass in just static text to match or if in angle brackets we can pass in variables multiple ones if you want.
Here the method takes a style and that route defines the style.
There's no coincidence that those two things go together.
Then we do our implementation.
And finally, we return a rendered template response so we say we're going to render template.
Use the guitar.html input we're going to use and it's probably based on the layout one.
And we pass the data, key=value so guitars is the key and then the values the list we also called guitars.
That's it, that's a pretty simple web app, isn't it?
|
|
show
|
6:12 |
Earlier, I talked a little bit about performance, and that Python is actually an incredible framework or language to write web applications in.
I said, Look, it may seem like .NET is incredibly fast.
It compiles to IL which it compiles to machine instructions.
How could it possibly not just blow Python out of the water?
And there's certain operations where .NET absolutely does especially numerical computation if, big if, if you're not using the scientific libraries in Python but you're just trying to do straight math.
C# is way faster, no doubt.
But that probably doesn't matter to you.
You probably just want your web app to go super fast.
The folks over at Microsoft have done a ton of work to make web processing way faster in .NET.
Back in the a speed .NET web form days where it was regular .NET, it was so much data being created and passed around.
It was terribly slow.
They've done some really good work.
And now, if we go and hit local host, guitar /electric with the right port on it and we do this a bunch of times you will see that the .NET Core 3 version which is what we are running the C# one.
It'll blaze.
It will do that whole thing that non database driven one in 3.5 milliseconds per request.
Or it will do a 1000 requests in 3.5 second for example.
That is really good right.
Would you be surprised to hear that Python does the same thing at 2.6 milliseconds?
Yeah, not only is it not that bad it's faster.
I don't even understand how it is faster.
It shouldn't be faster than the compiled stuff that's happening in C# side.
But it is.
So I think this is pretty awesome.
Going to show you a little program that you can use to check this for yourself.
Over here, in ch_07, I have now added the speed_test.
The way it works doesn't really matter.
You have to have it in your requirement which I've added here in requests.
But what we are going to do is going to run this against our website.
Now.
Also, another quick note about running these websites here.
See how this one is marked as a sorcerer's blue and this one is not.
Because these have the same stuff in them if you mark them both as it, the first one is going to be where the actual implementation comes from.
So if you want to run this one you got to unmark, right click, um mark directory as unmark as sources root.
You can't have them both to set to run at the same time.
It's kind of a unfortunate thing.
Very rarely you will actually have these together like this except for demo code which of coarse this is.
But let met go run this app first.
That was going to be running here.
Make sure it works.
Looks like a champ.
Okay.
Here is the scope of the electric guitars.
Here is what we are going to get.
Now that is running.
Now let's also go run this speed test down here.
Make sure you give a space here or a Python is weird and I'm trying to open it.
Let's say the URL is that.
Let's do this 100 times.
Go request that.
Now remember, this is the Python version.
Boom.
2.98 milliseconds.
Run it again.
Ah, there is that.
Don't forget the space.
End times, 4 seconds this time.
Better run it a little bit more I guess.
There we go.
2.9.
Pretty good.
We got a lot going on with screen recording and stuff so it's probably slowing it down a little bit.
But nonetheless, that is blazing fast.
That is faster than .NET.
Doing quite a bit too.
Remember it's doing it's little query it's doing it's filter, returning stuff.
It's probably even faster if we don't ask for the electric, but we ask for all.
And it is about the same.
It doesn't really matter does it.
But nonetheless, its pretty impressive.
Yeah, I think it is a little messed up.
I don't understand what it's doing there.
Anyway, it's really cool how quick and fast this is.
And you can use this speed test app to test any website, right?
You can put that against Google if you like.
You can try it on my site.
10 times.
Pretty good.
Now, why is it so slow?
It's so slow because it's far away across the internet.
But whatever.
You know, you can't help with PING time.
Nonetheless, it's pretty cool you can test your app locally with that.
Oh, one more thing!
I did forget.
I did forget.
this cannot be helping.
Sorry, let's turn that off.
I think I had turned that off as well.
Let's try again.
Ah, there we go!
There's that 2.5 I was looking at.
We had the debug mode and so a lot of extra stuff was slowing down in there.
Perfect!
Finally, you might be thinking, okay Python is faster here, but that's because it's like this fake little app.
I think it's doing quite a bit in there for a standard web app.
But nonetheless, maybe it's maybe it is doing more.
So let me show you real output from a real production server, that is doing way, way, way, more.
Here is a very small segment of the logs on training.talkpython.fm and these pages do a ridiculous amount of database queries.
Got to figure out what the user is.
Got to go back to get a bunch of courses.
Got to figure out if you finished watching these courses.
All kinds of stuff as you are you interacting with it once have you marked it finished.
What are your preferences?
Do you want the video to play at 1.25 speed?
So there are a ton of stuff happening here.
Look at some of these numbers.
Yeah, some of these are like a hundred milliseconds.
And you know, either thats because the server hasn't really hit that page very often or like the entrepreneurs one, just a huge huge page.
So it just takes a little bit of time to generate it.
But look at the stuff on the bottom.
My course summaries.
Get the free courses.
Or this is probably the api, that the app the mobile app your using.
11 seconds 7 milliseconds, 10 milliseconds, 46 for log in 6 milliseconds to render the log in page.
or to render the account page.
25 milliseconds.
This is real stuff.
In production.
On Linux.
On a pretty small and cheap server.
Right, these are pretty solid numbers folks.
Given the amount of net back and fourth with the database and other types of things that we are doing here.
|
|
show
|
1:04 |
I don't know how you felt about it but having all of our view methods mixed in with the app startup code in that app top UI is kind of icky.
I don't really like it.
I would much rather have a home and contact us, view, file, and one that has just to do with guitars, guitar lists guitar details, and so on, and really break those up.
Flask in its first impression doesn't really encourage that.
That app that you have to use for app.route it's super hard to share.
You can do it, but it's really not super easy to make that work.
So they added this thing called blueprints and blueprints allow you to, in separate files create a blueprint of the routes and layout that you want for a set of view methods and then register those back into the application.
So I strongly recommend that you check out blueprints and use these to organize your application better.
We just didn't go into it because as you can tell at this point, it's a really long chapter we have the whole database chapter after this which is still working in the same app so I didn't want to kill you with Flask but you should definitely use blueprints if you really work with this.
|
|
show
|
0:47 |
Well, like I said about blueprints that's a whole 'nother thing that we can go into.
There's a lot of stuff that we haven't talked about design, performance, and many, many other things.
So if you're really into Flask and you really want to get into Python web development you can go in much deeper.
We also have a 10-hour Building Data Driven Web Apps with Flask and SQLAlchemy course.
You can check it out here.
We also happen to have the same course, but in Pyramid so you can pick one of those two frameworks and dive deep into building web apps with Python in a much more realistic and full-featured way you know, user management, all that kind of stuff.
We're going to pretty much leave it here with terms of Flask.
It's enough to get you started and really show you what it's all about but if you really want to dive deep there's way more to get into.
You can check out our course, or other resources.
|
|
|
50:45 |
|
show
|
2:24 |
Databases are central to most applications especially server side applications like websites and Web APIs.
So when you think about working with a database there's a really good chance that in the .NET world your thinking Entity Framework and Microsoft SQL Server.
That's the most common way in which we work with databases.
Yeah you can do, just open up a connection and do select * from table, et cetera.
That is direct SQL type queries.
People do it but and there's reasons you might want to do it but, most of the time, you want to use an ORM and in .NET the ORM most of the time is Entity Framework.
We saw what happened when we had this one thing in .NET and went over to Python in the web world it exploded in to a huge number of these different frameworks.
Sort of true in Python as well, but it actually turned out to be a little more focused.
In Python we have two primary relational database access ORMs for us.
If you're using Django, there's a Django ORM.
It's very likely you're going to use that.
If you're not using Django, most of the time people just assume you're using SQLAlchemy.
SQLAlchemy has been around for a really long time it's super popular and it works great.
It's high performance.
We're going to talk about a lot of the ways in which it works and that's the one we're going to choose.
If we jump back to our JetBrains PSF Survey from 2018 you can go and find the ORM section and see well, it's a pretty clear split here.
If you're using Django you're going to be using Django ORM.
Other than that, chances are you're using SQLAlchemy.
There are reasons to use other ones like peewee is pretty nice and peewee has good async support.
If you want to use, you know async await to with the database and SQLAlchemy doesn't have that built in.
So there's reasons to choose others but these are the primary two and the decision is do I have Django?
Yes.
Then I use Django ORM, otherwise, SQLAlchemy.
There's other things and other ways to access databases worth throwing out right now that, you know, for example, I said earlier our site runs on MongoDB, and SQLAlchemy and Django those are for relational databases.
They don't make sense.
So we use Mongo Engine.
If you're using Redis or other databases that were not relational this might not be the choice you want to make probably doesn't work with that database.
Assuming you want to use a relational database here's what the landscape in Python looks like.
|
|
show
|
3:45 |
Before we jump into using SQLAlchemy let's talk about some of its features and compare those back to Entity Framework.
First of all, you don't have to use the ORM.
We don't have to create classes that map over exactly to our tables.
There's two layers of SQLAlchemy.
There's the core, and there's the ORM.
People often do use the ORM but they're popular sites that don't.
For example, Reddit is written in Python and they only us the core, the don't use the ORM bit.
You might wonder, Why would I use this core thing instead of just raw SQL?
This still creates the tables and also adapts the query syntax from database to database.
So, if you're working with Oracle, Postgres, SQL Server the fine details of how you specify parameters and stuff actually varies across those different databases even though it's all SQL.
And this, of course, would handle that variation automatically for you.
So it's really nice that this low-level database abstraction toolkit.
SQLAlchemy is not new, it's very polished.
It's very high-performance.
It's been around for over 10 years and a lot of work and refactoring has been done to make this a super fast ORM.
It's DBA-approved, which means the DBA does not have to accept what it's going to give them.
You can swap out the generated SQL with hand-optimized statements and things like that.
So, the DBAs who are very particular or maybe you, maybe you're very particular if you want to change that stuff out, of course you can.
While no ORM is required, it is recommended, at least by me.
I think, even though some ORM, even though ORMs in general have some problems, most of the time the 80, 90 percent use case, you massively benefit from using an ORM, and you don't run into to many problems, as long as you're careful.
And you can always not use the ORM on the few edge cases where it matters, right?
So it has a very powerful and clean ORM.
The way you work with it is very much like Entity Framework.
Code first, you define some classes you put some properties in place or some what would be like attributes put those on, and then you just start working with the classes, and magic happens.
The database comes into existence.
The tables are set up correctly.
SQLAlchemy also leverages this concept of the unit of work.
This is the same way that you work with databases in Entity Framework.
Remember in Entity Framework you say, I am using this DB context, and then I start doing queries and at the end you can commit it or not commit it.
And when you leave the using statement, it rolls back.
So you prepare a bunch of work you make a bunch of changes and queries and then you call Save, Commit.
Boom, and then all those go in at once.
This is, as opposed to, say, the Django ORM which is an active record.
You make changes right as you go like, right on the objects.
It also supports a ton of different databases including SQLite, Postgres, MySQL, Microsoft SQL Server you know, Firebird, Sybase, a bunch.
SQLite and SQL Server, those are probably the two that are most important for you.
Finally, one of the big challenges of ORMs is this lazy loading, n+1 problem.
I do one query, but then I'm looping over the objects that come back, and I'm trying to actually I don't even realize it, but I'm accessing a related entity through there.
And each one of those through the loops is going back and doing another query and another query and another query.
So I thought I did one query but I actually did 21, if I got 20 objects back.
This n+1 problem is solved with eager loading.
You would say, I'm actually doing this query but I'm also going to be navigating that relationship so please do a join and just do it all at once.
Entity Framework supports this, so does SQLAlchemy.
So these are a bunch of great features and they're actually really similar to Entity Framework.
If you're coming from that world I think you'll be super comfortable over here.
|
|
show
|
4:22 |
Hey, look where we are.
We're over on Windows 10.
So we're not on my Mac right now, and it's back to I moved us over to Windows 10 for this particular chapter for the C# side of things.
And the reason that I did is we're going to be using SQL Server.
So over here I have Visual Studio 2019 and we have SQL Server just running SQL Express but, you know, could be anything running there.
I have that and if we open it up you'll see that we're going to have a database.
Here's our guitars, and if we go to the tables you can see, just going to select some stuff out of here.
Here's our guitars and guess what?
This should look super familiar.
Our Axe Black and our Wheezer Classic and our Acoustic Black, the URLs this is just the same data put into this table here.
Notice that we have some various keys we have some indexes, and then all that kind of good stuff.
And I guess we could also, real quick look at the table like this, there's not a whole lot to see 'cause there's no relationships, but this is what we got.
Okay, so we have this running over here and the way we created it was we went and defined a guitar class just like we had before but instead we're going to use this with Entity Framework code first.
Though this part is the Entity Framework code first and then here's the leftovers that lets us sort of create more easily.
We have to have a empty constructor here's the things we're storing in the database and there's a convention that that's going to be the primary key over here and see the primary key right there.
That's going to be the primary key if we just have an id.
Okay, so nothing too fancy.
But then we're going to go define a DB context and this is the unit of work and a little bit more and on here we're going to say there's a DB set of guitar called Guitars.
And that's why over in our database we have a Guitars.
Also just setting up to go use SQL Server.
Okay, so it looks like everything is all set up and now to see how we might use it let's go to our home controller and it just creates this Guitars view model which ultimately goes to the CatalogService.
Remember this, AllGuitars, with the style here?
So what we do in C# is we okay, we're going to create a new DB context the specific one that has our tables.
And this is the, basically unit of work, rolls back or we have to go and commit it here.
And we view our code.
If it's, there's no filtering we're just going to do a LINQ over to Entity Framework say I want to go to the guitars table order them by descending price and then convert that to array, boom that goes back.
But if we're filtering, then we're going to do a cooler LINQ expression here.
I just love LINQ, this is a beautiful part of the language.
So from the g in the guitar, the guitar in the guitars we're going to just say where the style is equal to the style order by price ascending and select g to array.
Well, that's probably all we really need to see for the moment, let's go ahead and run it, see what we get.
This is another ASP.NET Core web application just the one we had from before but swapping out the data access from in memory to be the database.
Well, here it is, if we click on this, what do we get?
Hey, hey, look, we get guitars.
We have electric guitars, we have acoustic guitars that's running our LINQ expression right there as I click between those.
If I click All Guitars, it runs the first one.
So you can see, our little database access it's a thing of beauty, it's working great.
The only other consideration is how did that data get into the database in the first place, right?
Here it is, how did it get there?
If we look, I also created this data loader thing and it does something very similar it goes and ensures the database is created.
Make sure you do that, otherwise it might crash.
And then we're just going to go down here and really quickly see if there's any data already loaded if there are, we're done, otherwise we're going to get this basically the same data we had in memory and we're just going to add it to the database and then call db.SaveChanges to close out our unit of work.
Alright, so we run this once during app startup and then once it's been run, it's already populated then our app just runs like talking to the database with existing data.
So this is the C# version how we went from having just the in memory version over to working with the database in an Entity Framework.
|
|
show
|
5:41 |
It's time to get started with SQLAlchemy and Python's ORM version of our website.
So, the first thing that I think is, makes sense is to take this class and convert it from a standard class over into a specific SQLALchemy entity.
Right, right now all we're doing over in our services is we're just creating one.
This is like a standard Python object.
Nothing special about it.
So, how do we convert this to a SQLAlchemy class?
Well, first thing we have to use a slightly different way of defining fields.
We are going to define static fields.
And the way that you do that over here is you would just say things like name, lets just say equals empty string.
Now, if we do that, we still have self.name.
This is a bit of difference between Python and C#.
In C#, you either have a field which is static or it's an instance one.
In Python, the instance ones are just instance things.
But, the static ones have a static copy and then also, when you create instances they get their own copy.
It's a little bit weird, but that's how it works.
So, what we are going to do is, we're not going to define these as instance versions.
We are going to put them up here.
Like so.
Alright, with that gone, we don't need or with that moved, we don't need this anymore.
But let's go ahead and be super specific that these are still strings.
The way they're set up now yeah everything's going to know it's a string or float but you know what?
In a moment, we're all going to change things so they're not.
Now, the next thing that we need to do is we need to create a bass class, so like this.
We need to derive from it.
Like so.
Now, that would be totally normal looking if this is how we're defining the class.
But, SQLAlchemy's ORM will, at runtime, create a type which is the bass class, but then the way SQLAlchemy knows what classes and tables to create is it goes and says well, what objects derive from my bass class?
Those are the ones apparently I'm in control of.
So, we can't create the class this way we're going to do something a little bit differently.
We're going to go over here and say import SQLAlchemy.
Apparently we don't have that in our project, right?
So let's go through the process to put that there.
And then we'll go down here and pip install.
Okay, great!
So we got that installed.
We're going to go to sqlalchemy.ext.declarative.
So, we're going to import that and then here, this is the funky runtime bit.
Say declarative_base.
Just like that.
And this is going to create a new type just as if it was the way I had typed before.
But, it's a special runtime class that we can derive from.
Okay, so now this is what we want to do.
We got to go over here and change these into columns.
Though we're going to go over here and say import sqlalchemy as sa we use a little alias here so we can type less.
So we are going to say this is a column in the database.
And it is an integer.
It's a primary key and it's an auto-incrementing primary key for us.
Actually, sorry this is a, We're going to need this.
Now let's go to the name.
Going to be an sa.Column(sa.String).
And that's that.
Now we'll put the same over here, for style.
Image going to be a float.
Now, we might want to do queries against these things.
Remember, we're doing a query for one style or the other.
So you would, like an index on style of course that'll make it much faster if you have a lot of data.
So we just say index=True.
Then, when SQLAlchemy creates the table it's going to add an index there.
Similarly, we're ordering by price that's faster with an index as well.
So we can go like this.
Probably we won't ask by name.
Unlikely, very unlikely we ask by image.
And I guess we could say show us the ones that have no image but probably not.
But I think that this is what we need.
Now, if I run it like this it's going to create the table and the database called that.
I don't want it to be called capital G singular Guitar.
I want it to be lower case guitars.
So we can go over here and add another field.
Table name, __tablename__ = 'guitars'.
And this is what's actually going to be created in the database.
Okay, this is it.
This is how we define entities.
If we wanted, like, something else we'd have a class user, which is a SqlAlchemyBase.
And so on.
If we're going to just leave all of these entities and classes in one giant file, we could do it, then this is fine.
We'll just put our SqlAlchemyBase at the top and we just roll with it.
But, I hate that style.
I want a class in its own file.
And, in order to make that work well in Python what we need to do is go over here and have another file sqlalchemybase like this.
This is going to look silly, but here we go.
We're going to take this and put it there.
And we're going to take that and put it there.
This is the entire implementation of the SqlAlchemyBase.
And so, why am I doing this?
In Python, you can't have circular import dependencies.
Okay, so like, if I put it here I need the thing that creates the guitar.
To, like, there's a few situations where this might have to be imported in multiple places and so on.
So we just need to import that up at the top, like so.
There we go.
Now, our guitar is ready to be saved into the database Used for queries and actually generate the table that it represents with indexes and primary keys and all that.
|
|
show
|
4:40 |
The next thing that we need to do here is create the database context equivalent into SQLAlchemy.
You saw in the C# version we create a class that derives from DB context.
It talks about the connection things like that.
So we've something like that going on over here and it has mostly to do with the unit of work and the database connection.
So I'll call this session_factory and the idea is we're going to set up the connection create the tables, and then we're going to use this thing to make the unit of work that we use.
The thing that we say begin and we do the querys and inserts and all that then we say commit or rollback.
So let's go ahead and get started by importing sqlalchemy.
We're also going to need sqlalchemy.orm like that and that's probably good for now.
So we're going to need two things that we want to keep track of.
There's this thing called an __engine and that's going to be something and there's going to be a __factory.
So what is goin on with the double underscore?
Remember that makes it private.
So from the outside someone that's session_factory dot they don't get access to these variables.
These are like static variables of this module more or less.
And then we need to have a function that we call at the beginning to say set up the database here's your connection stream if the tables don't exist go and create them and so on.
So I'll create a function called global_init and we'll pass a db_name which is a string and it's going to have no return value.
Much like we had non local previously we want to say global __engine, __factory you have to explicitly say that otherwise you'd just create local ones.
We tend to assign values to these.
We'll just create local ones that shadow the outer ones.
So we're going to do it like this, and say no no I want to mess with the global data and we also want to check if this has already been called: don't do anything just bail.
So we'll say if __factory return nothing to do.
So for this part we're going to create a connection string.
We're going to be working with SQLite.
SQLite is sweet because it's literally built into and shipped with Python.
So if somebody can run your code at all they have this embedded in memory database called SQLite.
And the way we construct connection string to SQLite for SQLAlchemy is you have to say what type of database are you talking to.
Is it Postgres?
Is it SQLite?
Whatever.
So it can use its' dialect for that database.
And then we give it some sort of path here.
What we're going to do is we're just going to store that into a folder called DB.
I want a really simple way to get access to that folder no matter where this code is run from.
So let me go and create one more file a little utility.
I'll call it db_folder.
So I'll just throw it in here.
So what we're going to do is you call this function it's going to say where is this file located you can see where it is right there right now and it says give me that path and then we're going to take whatever database name your path, saying basically generate that path.
Here is a nice platform independent way to work with paths.
We'll say from guitary.db import db_folder so put here the DB path from the DB name.
So that's going to be the connection string.
Then we can create our one and only singleton instance of the __engine.
We go to sqlalchemy.create_engine.
Now there are one of these places where it says you can pass any positional parameters or any keyword argument parameters.
I hate this thing.
Why do people create apis like this?
What am I supposed to pass here?
I don't know.
You can pass anything.
Well how do I know?
I have to go look it up in the documentation.
If I'm lucky I can go to the definition over here and it might give me information.
You can see that there are some params and so on but I just don't like this pattern.
But what you got to know is You can say you pass in the connection string you can say echo=False as a keyword.
This is a debug feature I could say true and you would see every single SQL statement sent to the database spit out.
So if you want to see what's going on set that to true.
I'm going to set it to false but it's like a reminder.
Oh here's how you turn on debugging.
And then from the engine we can create a factory.
Create the sessionmaker.
I'm going to go to the sessionmaker and you say bind=__engine like this.
So what we did here is we're creating something a function basically that when we call that function by that I mean like this and we do this what we get back is one of these units of work.
This will connect us to the database and we'll be able to start creating sessions and doing queries inserts, updates all that good stuff.
|
|
show
|
3:56 |
So we can initialize our connection in our session factory which is great.
The next thing that we want to do is create tables.
Now, you can't call create tables until you can connect to the data base.
So we'll say, if not __engine, or __factory I mean we know it's the engine raise exception you must call global_init first so you haven't initialized the connection so you can't call this function.
C# we have throw, in Python we have raise.
The next thing we need to do is we need to show SQLAlchemy all of the classes that derive from it.
It's a little bit weird but that's how SQLAlchemy knows what tables it should create so we have to say something like this.
from guitary.data.guitar import Guitar Now PyCharm's going to say that does nothing cause we're not going to actually work with it well I want to tell it no, no, no this time I actually really need this here please don't change that.
We're going to make this better in a sec but just roll with it for a moment.
We also need this thing right here.
And on here it's super easy for us to go and create one of these tables.
We'll just go to our base class and there's a metadata and we can say create all, and pass the __engine.
Again, for some reason, this doesn't come up in auto complete but it's fine it's there, it's going to work.
So let's go ahead and actually try to run this.
So let's go up here.
Let's import like this.
Down here, before we call app.run let's say global_init, this is going to be guitary.sqlite, the file extension doesn't actually matter but when I look at it I want to know hey this is a SQLite database and we can say here create_tables.
If the tables already exist there's no harm, it's just like okay well nothing to do we'll keep on keep on going.
So watch over here.
Right now there's nothing in that folder but if I run it, first of all hooray it didn't crash that's cool, secondly now we have this guitary.sqlite which is pretty cool, huh?
Notice it has a data base icon as well.
This is not because of the SQLite extension the extension doesn't matter this actually is because we can come over here and throw this in and expando it out and it knows that there's this table.
It actually looked at the file and said Oh that looks like a SQLite file.
In order for that to work you have to do one thing really quick.
You have to go over here, I think you're going to try to add a data source the very first time.
It only happens once when you install PyCharm on your machine which is why I don't do it often.
You see SQLite and then right there where it says jdbc, SQLite, et cetera, we could test it.
It looks like it works, when you first run this you'll see that it'll have a button here.
It says you must download the drivers you got to click that button until you do that it won't identify this file and it won't be able to open it here so don't forget to do that.
But once you do PyCharm has an insane number of cool cool features around the database.
So like for example, watch this.
If I just wrote plain queries like query = select * from look at this, guitars, that's auto complete within a string.
What if I go over here and I say What I kind of select it's like name, price, this just blows my mind it is so incredibly cool that we get both color highlighting and auto complete within strings within Python.
Anyway, you get a bunch of benefits by doing that.
We don't need that auto complete when we're doing SQLAlchemy but nonetheless, here we have our tables created, our connections set up and then our tables created by just going base class metadata create all.
Remember it's super important you show it by importing like loading the guitar stuff before you call that line or it won't create the table.
|
|
show
|
3:30 |
We're almost able to start doing queries.
The last thing we have to do is be able to call this factory to get the unit of work.
But we want to have a little bit more control than just making this a field they can work with.
So we're going to go down here and define a function called create_session and it's going to return.
Well, let's hold off and see what it returns for just a second.
Let's also put that here.
And let's say a factory.
'Cause that's we're really using.
In the end, we're going to say session = __factory() like this.
And you would think we're going to return session but there's actually two little improvements we can make really quick.
I think this says, None can't be called or something like that.
but it is.
We can go and put a fairly complicated type annotation on this thing, like this.
We can say this is a nullable optional function that can be called and it takes no argument but it returns a session.
Like that.
We put that down here then oh it's happy again and actually, this will know that we're returning.
At first, if we return it like this it's going to return a session.
OK, we're not going to stick with that.
So this is really good, or pretty much like we could call this the way it is now but there's two things we want to change.
One, there's a weird expiring sort of thing when you do an insert and you get an object back so I want to say, Expire on commit is false.
Right, this makes data a little more portable outside of just doing a query and then throwing it away.
We're doing an enter and then throwing it away.
And the other things is, I would like to be able to model the using DB context...
thing we have in C#.
I would like to be able to use a with statement on this session to say this with statement this context manager, defines what the unit of work is.
Doesn't work that way with SQLAlchemy but remember, Python is flexible so, we can fix that really easily.
Let's go fix it by adding a file called context_session.
so a DB session that can be used within a context manager.
I'm just going to drop this in.
So, we have a session and the way it works is we create one of these classes and it takes the session and then it upgrades it into this thing that can be used in a context manager.
In C#, the way you do that is you implement IDisposable.
Here, we implement an __enter__ which returns some object that has an __exit__.
And, this object is itself a thing that has an __exit__ that takes the exception details if one happens.
Doesn't really matter, but it says Look, if there's an exception, we're going to rollback whatever and then either eat it or we could raise it again.
I probably should raise it here.
I'm not sure.
Actually, I'm just going to let it pass.
I think it re-throws it and just calls it on the way out.
But either way, regardless whether there's an exception or not we're going to close it.
Ok.
So we can return one of these, which will let us write cleaner code.
Like that, we're going to import at the top and then the way it works is we allocate a new one like that.
So we can just say, later like this, with create_session as unit of work and then unit of work stuff right session dot you know query or whatever.
So this context session gives us this design pattern which I think is just perfect.
Okay, now we can create these sessions create these unit of work right, session is the unit of work name in SQLAlchemy nomenclature.
Our database is ready to start doing things.
|
|
show
|
1:42 |
Let's review how we created our various entities our classes that then were mapped over to database tables and actually were used to even create those tables.
In SQLAlchemy, everything happens in that world for entities by deriving from this declarative base class that is generated, not with the class keyword but as a runtime object.
So that's easy to do in Python and that's how they do it in SQLAlchemy.
So we just say sqlalchemy.ext.declarative.declarative_base.
This creates the type and you can give it whatever name you want.
I like the name SQLAlchemyBase, since it represents a type.
I give it this camel case type of naming convention.
Name it whatever you want.
It doesn't actually have anything to do with anything just this object is the base class and you need to inherit from it.
So this is a singleton, you get one and only one of them.
So make sure you don't create two.
And then to create new classes, like a Guitar or User we just derive from SQLAlchemyBase.
Then what we have to do is fill out this guitar class for example.
So it derives from SQLAlchemyBase, as we just saw and then we have these static fields which represent the columns.
So we have an ID, a name, an image, a style, and a price.
We set these to be all SQLAlchemy columns and then you specify the type like a sqlalchemy.integer, or string, or float or whatever you want.
And then we can optionally set other values like the ID's going to be a primary key and it's going to auto-increment in the database so we don't have to worry about that.
That's cool.
We're going to query by style and filter by price so we want index there.
So we say index=True.
You can also do relationships but we only have one table in our example so we're not really going to get into setting up relationships but they're pretty easy to do here, as well.
|
|
show
|
1:54 |
Once we have our entities defined the next thing to do is to connect to the database and create the tables.
We're going to create a connection string.
You saw before we were using SQLite, so it's sqlite:///.
That would be whatever database connection string that you put on, in there.
SQLite is just the file.
You know, SQL Server, maybe it's like trusted connection equals true, server equals such-and-such.
Initial catalog equals...
you know, whatever it happens to be that goes there.
That's the connection string, after this scheme, /// Then we create a singleton engine.
Going to create that.
And then we're going to go to our SQLAlchemyBase and we say create all, and we pass it the engine.
Now, I mentioned there's a really important thing that we've got to make sure that we import all SQLAlchemy derived classes.
So all of our entities have to have been imported so by the time we get to this yellow line the SQLAlchemy base has seen the fact that it derives from all those things.
Remember, Python is not complied.
The only way it knows about its other files is by importing them.
The way we do that, so we just imported the guitar.py file, but there's actually a better way if you have a real, true application.
So what we can do is that we can create this module called all_models.py, and we can just import guitary.data.guitars.
Import guitary.data.users.
And we've got 20 tables of 20 classes.
You import them all here, and then down in this you just import that all_models.
Why this level of separation?
Well, it turns out that you have to do this several times in real applications.
You might have to do it in this part where we're creating the tables, but maybe you're doing migrations like database migrations to keep your database in sync with your classes.
You also have to do something like that there.
So it's easier to put it into this one file you can import and always have this convention.
I just go back to this one place, all_models, and import a new model that I happen to create.
Of course, if you forget it, then you're going to end up with, basically, SQLAlchemy not creating those tables because it won't know that they exist.
|
|
show
|
5:35 |
Well, we have our database with our table.
Let's go see if there's any data in here.
select * from guitars.
We just run that.
You can see zero rows, totally empty.
So we don't have any data yet, but we want to insert it and that's the next thing that we're going to look at here.
We already have this data and this is in our catalog service, right?
This is instead of having a real database.
So let's just go and insert this data into the database and then, get rid of it here and we'll just load it out of the database.
Maybe we'll make a minor change just so you can see what happens.
Let's go create a file here called data_loader.
It's not going to do much.
In general, it's just going to preload the database the very first time and then, it's never going to do anything again.
Let's go over here and say, def load_guitars_if_empty.
We'll have our guitars right there and we can import this class.
Now, there's a small change that we have to make here.
We can try to call this function and see what we get.
It's probably not going be so great, so let's go down here.
We're doing create_tables.
Actually, do the right import.
We'll say load guitars if empty.
If we try to run this, what happens?
Boom, crash.
This right here, we no longer have a constructor, remember?
This constructor here, we're just now using the default one.
We're not using the one whatever SQLAlchemyBase happens to provide.
We're not using the one that we wrote.
So we can't do it that way, but it turns out we can almost do it that way.
Watch this, we come down here and say, name equals this and just use keyword arguments, price, IMJ and we're already setting style explicitly for some reason.
Apparently, we're doing that before.
We run it again, it'll still crash but now we get to one more line, so the first line worked.
Let me just go and do the rest.
Alright, that looks better.
Let's try to run it again.
Hey, it runs.
That doesn't mean anything got imported.
That just means we're able to create this list.
Okay, everything is great and also, let's change the price of the acoustic guitar to 1,298 instead of 99.
So when we do the query, we see that number, we know hey, this one came from the database.
Alright, well, this is really good.
The next thing that we need to do is to get our session so we can create a context manager here.
We'll say with and let's do the import more explicit up here.
We're going to use this session factory so we'll say session_factory.create_session as maybe it should be session context or something like that.
So remember, down here, it's not a session itself.
We have a session that has a session property.
Now, the stuff that we do inside this ctx, this with block here this is going to be within the unit of work and at end, we'll say ctx.session.commit.
OK, so we're going to do work here and then, eventually, before we leave we can commit it, assuming that we're happy.
What do we want to do?
We want to insert all those guitars, so for guitar in guitars watch this, not too hard.
We're going to do a bunch of inserts to the database ctx.session.add(guitar).
Done.
That's it, that's all we have to do is go and add the guitars and then, call commit.
I believe there might also be a bulk insert but you use that much left often, so this is how you create a single object and then, add it.
And we just happen to be doing it eight times or something.
Now the other thing we want to be real careful about here is we only want to do this if there are no guitars.
So we'll say if ctx.session.query jump ahead a little bit, we say query of guitar count is greater than zero, print, not adding new data.
Let's maybe save this.
There are count guitars already, something like that.
And don't forget a return here.
Alright so, we have our data.
Going to go do our tests.
I guess we could go do that the beginning but then we would have to move stuff around.
I don't know, whatever.
Let's just go ahead and make it better.
Here we go.
Alright, so we're going to start out this method.
Get in the session and if we don't need to do any work, that's it, we're out.
Otherwise, we'll create the data and we'll add it and commit it.
We're not doing anything with this yet, are we?
Yeah, we are.
We're actually calling it here.
So let's go ahead and just run this and see what happens.
Remember.
Currently, there are no rows.
Let's run this again.
Looks like it started, that's cool.
Run this one more time, what do we get?
Tada.
There's all of our rows inserted and there's our acoustic black for $1,298.
That's cool, let's run this again and see what happens.
Here we go, we're not adding new data because there's already nine guitars.
So that's it, it's super simple.
We just create our session and session context.
We use the session from it and we can do a query, get the count of items.
And if we need to insert some, we just go to session add then finally, session commit.
Done.
|
|
show
|
3:39 |
Now that we have data in the database let's forget this stuff here about this in-memory data.
We want to go and get it from the database which could also do the sort, and just return it.
It should be super easy now we have our context, our database context all set up.
So we're going to start again, by using our session factory because that's how we get started.
When we're able to say with session_factory.create_session() as ctx and then we want to do some kind of query.
We're going to do a query if there's no style if there's no filtering, we're going to do one thing otherwise we're going to do a more of a database filter down here.
So if we just want all of them the guitars, equals, the way it works is we go to the context, the session I say query of the type we want to do a query against and we could say filter and do some kind of limiting but we're not, we just want all the guitars.
So we're going to do an order by and this is where it gets kind of interesting.
We say order by guitar dot, what do you want to order by?
You want to order by price, how do you want to order?
Descending.
Pretty cool, right?
Want to go to the guitar, get its price order by descending.
So, it's not exactly the same as LINQ but it's quite similar to it.
And then let's read this all out into a list because once we leave here basically, the connection is closed and we can't get any more data.
So when I go over here and just convert that to a list, it'll be clear if we do it like that right there.
Okay, so this should work for all the guitars what about if we need the filtered guitars?
Well, this is no longer necessary.
It's going to be something like this.
I'm going to have this order as well.
Now, these start to get really long and we can do some nice little hang breaks here so let's go, just hit enter here.
It's a bit of a weird language feature but it's how it works.
You hit enter, and you do a continuation you have to do either a backslash there or since there's no semicolons or if there's parentheses then you actually don't have to do that.
So if we just wrap the list here we can say dot order by this and we can also come here and say dot filter whatever you want to filter on.
Remember, we were testing style, so guitar.style and what do we want to test?
Equality, not less than greater than, or whatever, done.
So, here's what we're going to write.
We're going to take, we do a query against the guitar tables going to filter on style, which we have an index for we're going to order by price, which we have an index for and we're going to make sure we read through the cursor turn it to a list, and return it.
Alright, well, that's not too much work for us to write these two queries let's go ahead an run it, and see if our website still works.
So here we are, this page tells us nothing about whether it's going to work but let's see what's on the next page.
Cross your fingers, boom!
There they are, how awesome is that?
And how do I know this is real data not that in-memory stuff?
Because it's the new version in the database.
Well that's all of them, let's look at just the electric guitars.
Just the acoustic ones.
Beautiful!
How killer is that?
That is just so sweet!
And that is all the code we had to write to implement that.
It was just as long as just doing it with fake in-memory data but now we have the full power of a real database and right now just SQLLite, but it could be Postgres or Microsoft SQL Server, or whatever we connect it to.
Super, super cool.
And the way that we consume it, remember, over here this does not change at all.
Or as it knows, it still gets a list of guitar objects and those guitar objects have all the properties that it expects.
So we didn't even have to touch this code which is great!
|
|
show
|
2:38 |
Running our web app in PyCharm makes it a breeze.
Click here, look it's looking perfect.
However, watch what happens if we try to run this outside of PyCharm.
I'm going to copy this path here.
I'm going to go over to it.
Now, it's super important to realize our virtual environment is not active yet so we need to do that.
So we have to say, dot, go back a directory say .
venv/bin/activate or on Windows, forget the dot just venv\scripts\activate.
But the outcome should be this.
Alright, let's go back to our ch08_db.
So here's the Guitary thing, let's go in there and let's just run it.
And the easiest way to run it is to say Python app.py.
Wait a minute, no module named guitary?
Weird, okay, what if I go up a directory and I say Python guitary/app.py?
Well what's going on here, well what is going on is that with Python these directories are basically treated like name spaces.
Where our app.py is relative to the other stuff it's a little freaked out.
There's ways we could fix this.
We could set it up as a package rather than as just a set of files which would fix it but that's another thing that we'd have to worry about.
It turns out fixing this is super easy we can just go up here to the top, copy a couple of lines because they don't really matter.
So go to anywhere before the first Guitary and I'll just do it at the very top here.
We can do a little bit of messing with the path basically add the top level directory.
So for, here, and go up one directory and add that to the path.
Actually go up one directory and add that to the path.
So guitary/app now exists.
You're going to get some warnings here that say, you're not supposed to put this at the top but obviously if you don't put it at the top it's going to crash.
So, I'd rather break the convention and have it work than not break the convention and have it fail right?
That seems silly.
So now, here we are if we, lets' just go in here doesn't really matter.
At this point we just say Python app.py Boom.
It's off and running, except for it's already running over here it's can't share that address.
So try it one more time.
Beautiful, and let's just make sure things work.
Yes, they do.
Still talking to our database, just like you'd expect.
Right here you can see a bunch of stuff coming back.
Uh yeah, it looks like we need to do little cleanup on one of our SQLite objects.
But none the less, everything is other than that little detail there, which I'll look into it looks like we got it running just fine.
|
|
show
|
2:14 |
A quick followup to that error that we were just seeing.
It turns out I made a super subtle mistake.
There's a big bug on this page, do you see it?
Yeah, I didn't either.
Let's run it real quick just so we can see the error again.
We come over here and we run this.
Click around, it still works except for that bit right there.
But let's click on these pieces.
Nah, I'll open 'em up, whatever.
I've been fiddling with it and it's kind of broken a little bit but we should see these errors here about your SQLAlchemy type.
Well, I changed one thing which made it run into a problem which is different.
So I will show you over here on this when we close the context I said, you know what?
Actually, when we leave the width block there should really be no session so let's make sure the Garbage Collector cleans it up.
Not this, let's run it over here so you can see it more clearly.
But now that I put it back the way it was you'll see if I'm clicking around that is shouldn't have ever gotten that error, right?
We never tried to access the database out of the database context, did we?
Well, what did we get?
We get these crazy errors here.
And that was the bug.
Yes, we actually were accessing outside of the context 'cause see this little line?
That is the boundary of the context manager and this, I had indented the wrong way.
There we go.
Now, very subtly, you should be able to see.
Yep, it's inside the context manager and later we return it.
If we just save that, it should run again.
Notice it saw that there were some changes.
And let's go and actually clear that.
And we go click around we'll see a bunch of requests coming in.
First of all, our None AttributeError thing went away.
There was like a no reference exception, basically.
And notice, no more errors.
Okay, the problem was we had to close the session and then we were trying to do more work with it which was causing issues.
Oddly, it was working, but it was saying you know, not so much though.
Like this is going to cause trouble.
Anyway, sorry about that mistake.
Just make sure that this is within the with block.
Obviously, once we get the data just into memory we can do all sorts of stuff outside the with block there.
|
|
show
|
0:56 |
Let's review quickly how to insert objects using SQLAlchemy.
We have our session_factory and we're going to use it to create a unit of work in our session.
You saw we caught a really cool method called Create Session in our session context and all that but we're just doing it from, like, bare you know, SQLAlchemy types here.
We want to create a session want to create the object we want to insert in this case, a guitar, and we're going to set some properties like the name is Ax Black, the price is 499 the image is this image, the style is electric.
So we're going to set all the properties but notice we're also not setting the id because that's auto-incrementing.
When we save it, it's going to just go get set in the database so we don't have to set that but all the other properties we do want set, set them here.
Then we just go to the session and say Add, give it the object, and it's nothing has happened yet on the database until we say, session.commit it goes to the database within a transaction that inserts this record.
Super easy, right?
|
|
show
|
1:42 |
Once you have some data in the database well, of course you want to get it back and you want to query it, and slice it, and dice it and group it, and all the cool stuff.
So, here is our function, a simplified version that says given a style, a string, we want to get all the guitars that have a style that match that string.
So we're going to go and say create a session.
Again, bare metal stuff, just SQLAlchemy types not our cool, higher level version.
And we say ctx.session.query(Guitar) and then we're going to put the where clause basically the filter clause is guitar.style == style.
It's really cool how these are called descriptors these columns are able to, at run time, be one thing and then at, kind of, evaluation time like this be yet another.
So that's a pretty cool language trick and then if we were calling list and wrapping it we could also do an equivalent thing by saying .all and that'll turn into a list.
Basically, get all the stuff back, and then just to be safe, we're wrapping it in a list at the bottom here and we're turning that.
So, this is how we go and query the database.
We're not doing our order by, but you saw that of course if we wanted to, we could order these easily.
Once we run this query, if we had echo term to true which could look at our database tools, you would see that we would issue a SQL command over to the database.
select * from guitars where style = @style.
Pretty cool, huh?
Couple things to note: First of all it's using parameters so SQL injection attack and little Bobby Tables not a problem with SQLAlchemy, which is great.
The other one is it doesn't technically say select .
It specifically lists out everything that it expects.
Get a slightly different more complicated-looking output, but effectively, there's like a simplified version of what happens when we run this code here.
|
|
show
|
2:07 |
Let's round out this chapter on SQLAlchemy by showing you some more advanced query syntax.
It was pretty cool that we could say guitar.style == style, and that was great.
But when it gets more complicated how do you do that, right?
How do I do, like, an in clause a not-in or how do I do maybe a greater than or something like that?
Right, substring matching and so on.
So here are some of the more common ways where you might want to do a query.
So the first one is equals.
So that's just ==.
Not equals is !=, straightforward.
Like, on the name or on the column in this case it's the name.
On the column, we saw they have .des for descending.
We'll also have like.in_.
You may wonder why there's an underscore on the end of in.
The reason is in is a keyword.
In Python, it's not very selective about when it thinks a keyword is a keyword, right?
There's no context, so in just basically cannot be used so they use in_.
You see, like, class_ or _class.
Sometimes like when CSS, if you've got to specify CSS class and code, you can't use the word class.
Anyway, we're going to say, we would like to go find out if the name is in this array or list here, or not in.
Notice the tilde in the front of the query.
It's null; it's equal to None.
Now, you can do ands by doing multiple filters or you can do an or, a little bit more complicated by calling the or function.
This is just some of the stuff you can do.
When you check out the docs the link is at the bottom below there's a bunch of things you can do.
It's a very, very flexible system.
Finally, get the full story.
SQLAlchemy is created by Mike Bayer and a long time ago, can't believe it back in April of 2015, I interviewed him about what he's been doing in SQLAlchemy, the origins.
So back then, it was created in 2005 that was the 10 year anniversary of it.
If you want to listen to the creator of SQLAlchemy talk about it, why he created it his thoughts on unit of work, for example drop in over at Talk Python To Me and listen to this interview with Mike Bayer.
|
|
|
35:20 |
|
show
|
1:31 |
Are you ready to make your code more reliable?
Well we're going to talk about unit testing in Python.
I just wanted to start this whole chapter by saying this is not a sales pitch.
If you don't want to do unit testing, fine don't do it you don't have to but if you are interested in doing unit testing we're going to go through it and show you how it works now in C#.
And then of course, how we're going to do this in Python.
Python has great features for testing and continuous integration, all those kind of things.
It's also worth pointing out that in Python unit testing is more important than it is in C#.
Remember, Python doesn't validate the code until you try to run it.
And it could be that the code itself has some problem like with a type mismatch for example like you're trying to add a string and an integer and it's like, no you can only add strings.
For example, like that would not get detected by loading up the code.
You'd have to actually interact with that part of the code to make sure that type check effectively is going to to happen.
In C# we have the compiler, in Python you need to do a little bit more to make sure it works.
If you're kind of on the fence about unit testing in C# you probably want to do at least a little basic unit testing over in the Python world so things like continuous integration actually make sense.
I'm sure you've seen graphs like this.
Testing is good for you and I also laid out why in a dynamic language, like Python an interpreted language like Python it's much more important, even than in say something like C#, or Java, or C++.
Where the compiler does some of the validation for you.
|
|
show
|
1:02 |
Python comes with a built-in testing framework.
It's called unittest.
All lowercase, just import unittest.
And it's good.
I've used it for many things.
There's another one, though an external one that has definitely got more momentum and is used, I would say, more often at least at the high-end level.
So there's a project called pytest.
Pytest is a super simple way for us to write unittests in Python.
You can see in this first little code block right here we have a function called increment.
And in order to test it we just have to write a function with the word test_, the prefix test_ and pytest will find it and run it for you.
Totally easy.
So you can definitely use this built-in unittest one and there's no problem with that.
You would certainly choose that if you didn't want to have external dependencies.
It has a JUnit style of programming so if you like that kind of style then you'll like the built-in unittest.
But you also might want to look at pytest.
There's a bunch of cool plug-ins and other things that really enhance it and make it probably the best choice these days.
|
|
show
|
3:09 |
Before we see pytest in action and write unit test in Python let's see the equivalent C# version.
Like all the other chapters, we have the C# version and then we're going to create the Python version here.
Remember our guitar project we were using in web and the database stuff?
Well, I took the essence of that core data access library and I put it over here and I called it lib and I made it a little more proper and a little more official.
It does logging and other things like that.
So I didn't want to mess with what we were doing there and I certainly didn't want to go into all the complexity of testing true web apps and stuff like that.
So I just copied it over here to keep it isolated.
So the idea is, we're going to use this lib and by default it uses this dependency injection style here that's like a super simple lightweight way to do dependency injection.
It just says if you can call the constructor with no agreements, 'cause there's defaults and if you do you just get the production database and the production logger, but of course you can pass in something else for either of these.
If you need to change its behavior say for like, I don't know, a unit test.
Okay, super lightweight dependency injection there.
And then when we can just ask for all the guitars.
It's going to go and call this, get the guitars from the DB and this DB is the one that is past in right there.
If the styles are all of them, we return.
Otherwise, we do a LINQ query to write them down.
Another suggest LINQ to objects with some fake data here.
Again, we're keeping it a little bit simple.
But if we look at this notice it's trying to indicate, hey, this is like a proper data access layer faking going to a database.
So it prints out this big warning.
We're getting this from the database.
You shouldn't see it in a test, right?
So lets just go and run this program.
And it's running, Welcome to the guitar app.
What kind of guitars do you want to see?
Let's see electric guitars.
Boom, we found a bunch of cool electric guitars from our database, but notice we're logging a message and the message is this.
If I go like that it looks a little better.
And we're also getting stuff from the database.
Now obviously we'd never do this in a real app but I put this huge over-the-top sort of logging messages here to indicate these are dependencies that do not belong in a test, right?
They're necessary for the app to run.
They're the real database, the real log in and so on.
But the idea that you're suppose to interpret when you see that is, Oh, that's kind of a problem if we were to try to test something and that dependency were still active.
You could also ask for acoustic.
Get our one acoustic guitar back.
You can see our message saying Hey, this is real app stuff, right?
It's just there to mostly let us know that we're doing the right thing if we could make those go away in the test.
All right, so this is the application and it works with this database that we got going here, right there.
And the logger, I just threw it in the same files just to keep things a little less cluttered.
We have our logger and obviously it logs to the console and practices a private log to a file or maybe even to a database.
So our goal is that we want to test this library to make sure that it behaves correctly but we don't want to do so with the live dependencies.
|
|
show
|
4:53 |
Well, we saw our C# app ran well and it seemed like it was working.
Are you sure it's working?
We know every edge case works?
And what if I type in something incorrect I only type the right thing acoustic or electric, when it asks.
Of course, we should have tests for this.
So let's go look at our unit test over here.
We have our lib and we have our lib_test.
Probably I should have broken this into a testing project and a lib project to be proper but this is already so cluttered.
I'm trying to keep it simple here.
So here's some tests that we can write in C# And first of all we could do this the wrong way.
Let's go and comment these out for a sec.
Show you what happens.
So here we're going to go and test and it says test run me wrong.
Why are we doing it wrong?
Well we're just creating the library and then we're calling functions on it to run those tests, see what happens.
Over here on the right well it looks like our tests passed.
That's pretty cool actually so our tests run me wrong didn't work but if you look at the output down here.
Here we go, you got to check the right things off.
If we look at the output down here in the test results, here's the test run for run me wrong into bug and then check this out.
Logging this to a file you shouldn't see the center test.
We're getting the guitars from the database.
Shouldn't see the center test Oooh well maybe this line seemed nice and easy but maybe we shouldn't be doing that.
So let's switch these back here.
There we go, I comment out that one.
Then we've written it the proper way.
So what did we do?
Well over here we're now I'll show you this test data in a second but we're going over here and we're creating a mock database implementation and a mock logger.
Now logger just says, You try to log to me I'm going to do nothing.
But the database has to have fake test data here.
So we're going to mock out the database implementation and we call this, it should be going to the database and getting it, right?
But doing its validation and its filtering, and so on.
It's a little bit contrived, but hopefully it's good enough for you to get an idea of what you might really do, right?
Then we're going to go over here and we're going to create a hash of these things.
I'm going to create a hash of the styles.
And the idea is the HashSet is a unique collection of items, right?
So we're going to just say all the styles that would be electric there should only be one item when we pass in the style electric and that set should contain style.
That let's us test the electric guitars.
It could also test all the guitars and do the same HashSet and see that there are exactly two an electric and an acoustic guitar.
And then finally, it's not usually enough to test just the good happy path.
We also want to test the error case.
What happens if you do something wrong?
For example, if I ask for all guitars passing null I would expect some kind of argument exception.
The way this works is if you don't throw an exception this test fails, right?
So let's just run it one more time.
Get everything working.
Here are our tests over there and if you look at all the output, check it out.
We're running this one, this one, and this one but we are not seeing any of that logging information.
Good we're using our mock data.
So the last thing to check out is how are we creating this mock data?
And I've put this over into it's little own class to create this, so we want a mock logger.
We use an external package mock that we installed from NuGet and we're going to go over here and create a mock of ILogger and just return it return the object.
If we don't tell it to do anything it basically just does nothing but doesn't crash when you call things like log.
But for the mock DB we're going to say we're going to get some of this test data down here and this should look familiar.
All right we have a limited set of test data.
And then we're going to create a mock IDB and then we're going to tell it Hey if somebody goes to the mock and they call get guitars from DB return that test data.
And then we're going to return that object.
So that's how we avoid going into the database but have it return some data down here when we ask for guitars.
There's not as much data but it's plenty to test with.
And that's it, so we wrote these well we were at four tests, and we realized one of 'em we shouldn't write so we really wrote three correct tests here using our mock data, our Dependency Injection Open Close Principle, all that kind of stuff here with our lib class and our test data and then we just did the test and it turned out everything works just like it should.
I guess we could do one more thing down here, maybe if we for some reason forget our filter, right?
Let's just see that some tests fail.
Here you go the electric guitar test failed.
No surprise, right?
'Cause we're getting acoustic guitars as well.
Here we go, I'll put it back and leave it in a happy state.
So this project and this idea of testing in this way we're going to recreate this in Python using pytest.
|
|
show
|
1:48 |
You saw the C# version now let's see the Python version of just the app and the library.
Of course, we're going to write the test and put all of that stuff in place but here we have the guitar app and here we have the lib that we talked about before so we have our guitar class and down here we have the function that given a style, which is a string and it will return a list of guitars.
First off, we're going to lowercase the style we'll log out a little bit of information here.
The log oh yeah the log is down at the bottom, down here and of course it's just going to say print out just like we saw before.
Some color output and we also have, if we go back up here we have the get the guitars from the DB and down here we have a function which simulates getting data from the database and of course prints out this should be shown in the app but again, not when we're actually trying to do testing cause that would mean we're depending on a dependency which we want to mock out or something to that effect.
Alright, well lets go ahead and run the app here see what we got.
Should look pretty familiar I want to see an electric guitar alright, here are your eight options and notice, here's your log and here's your note that hey we're going to the database.
You can see acoustic.
See one write one, but of course we're using our dependencies and in the app that's exactly what we want.
In the test, it's exactly what we want to avoid.
So this is the code that we're starting from for this chapter and our goal is to write some tests that basically, the same test that we had before tests that we can filter for a particular type of guitars tests that we can get all the guitars and tests that there's an error if we try to access a guitar that's not right.
Let's actually take this out so we can maybe discover a bug that way it just introduced and test that with out Python code and our pytest test.
|
|
show
|
3:02 |
Now, let's get started with pytest.
So, we come over here and create our test code so we'll go over here and say test_lib and we're going to test our lib.
And as it where we're going to write our test.
We're going to want to start by saying, import pytest.
And in fact, you don't unless you're using some of the stuff that we'll start using the very, very beginning, we don't have to say that.
But notice, PyCharm says, mm, no pytest.
So we're going to go and add into our requirements that are building up here pytest and we're also going to have a couple more.
We're going to have to do mocking.
Remember, we had MOQ from the C# side.
So we're going to pytest_mock here.
And that's really sufficient, but we're going to have one more.
We'll have pytest-clarity.
pytest-clarity will give like a colored dif if I, say, I expected this collection or this string, but I actually got that other one it will have better reporting for us.
And of course, let's tell PyCharm, pytest is not misspelled.
Up data in here, pip install -r requirements or just click the button if you're in PyCharm.
Super, so, those three dependencies are installed here.
And let's just do a really simple test.
The way you do tests here is, we say test something and then you have an assert statement.
Now, Python's assert statement has generally, by default, nothing to do with testing or pytest.
It's just an assert, right?
It's part of, just, data validation that we could use.
But pytest co-opts that for its behavior.
So, let's say assert(7==9).
Now, if we want to run this, it's a little bit harder.
So, I could click Run here, but that's not going to run the unit test in it.
We can do a couple of things.
Let's go over here, and say edit configuration and we want to add a Python test, pytest thing.
We say script path and go over here.
But was it sufficient to do that, let's see.
Try to run that, see what we get.
Yeah, perfect!
So we just pointed at the directory and everything named test_something gets run.
Notice there's a problem here, the test failed.
7 is not equal to 9.
We expected 9, we got 7.
See how it's not just, assert is false when it should be true, but it actually gives you a really nice error message.
So pytest is super smart like that.
It just uses this assert statement like so.
There's more complicated tests that we can do like I expect an exception, or something like that but if we're just doing this basic one notice we're not even importing pytest.
We're just executing.
If you want to try to run this just from the terminal you go here where our test file is and just type pytest.
And it runs again.
So that's how we get started with pytest.
You install it, you write something with a method that is test_.
You have a file that is test_.
And then you do one or more assertions in the code.
Let's make that run.
You don't want to stop with a broken test, do you?
So we run this again, woo-hoo!
Looks like they pass this time.
|
|
show
|
3:03 |
This silly assert number equals number.
That was fun, but let's actually write our first real test.
So let's say we went to test, say, test electric guitars.
Wrong.
Why wrong?
Because we're not going to get rid of the dependencies.
We are just going to call it.
So this turns out to be pretty straightforward.
We can go over here and import lib.
Right, that's this library right here that does all of the guitar stuff and call all_guitars.
We can come and say guitars = lib.all_guitars.
Let's give it a style and let's say that the style is equal to electric because we're going to want to use this below as well, eventually.
We can do a suite, a little generator expression.
Remember we talked about list comprehensions and generator expressions and so on?
And we could do something like this, assert all.
This is a built-in that tests for every one of these, this thing is true, all right?
So we can say that g.style equals style for g in guitars.
So what we're doing is we're getting the guitars back.
If this is working correctly it's going to give us only electric guitars.
So we could say all the styles here and we're go through every guitar and we're going to test, that for every one of them we get the true statement that yes, the style is the style we're expecting.
We could also do something like this.
Create a set.
It's going to be something like g.style or g in guitars.
That's another one that we could do and maybe I'll just print that out so you all can see what that is.
But I kind of like the first one but we can just print them out so you can see there's a couple of varieties here.
Most importantly, we're going to run this and see what happens so let's run it.
Look, our test passed.
Electric guitars, wrong.
It looks like it worked and know we printed out this thing so what this does is this takes all the styles in this collection and then, it reduces them down to just the unique elements like a HashSet like we did in .NET.
Same thing here, those both pass but I definitely like this one best.
I think it's the clearest.
Every single style is equal to the style we expected which as you can see, we're getting back to the electric.
Run it one more time.
Woo-hoo, it worked great.
Well, it worked great except for we're logging and we're going to the database.
Go ahead and clean that up.
Leave you just with the essence here.
Let's create that small, create another copy of it to work on here in a second.
I'll leave it commented, but this is not good because we're using our dependencies.
What did we do in .NET?
If you recall, this lib we actually allocated it in a sense.
It was something like this and we said we're going to have a mock log and a mock DB or something to that effect.
We're not doing that.
In fact, we can't even do that.
This is just a module, not a class.
What are we going to do?
Well, Python has some really simple answers on how to write this test correctly.
|
|
show
|
2:33 |
Now, over here we got our guitar data because we went to the database.
Remember in quotes, our fake simulator database but we went to the database, our true dependency to get the data.
Well, if we don't want to test it wrong but we want to test it right we need to somehow get some test data that we can use.
And Pytest has a really interesting way to factor out things like setting up little applications in test mode or creating test data mock objects all those kinds of things and they're called test fixtures.
I'm going to go over here and find a class called test fixtures and I'm just going to drop a method here.
And so, this is a method that will give us some fake data and let's just make sure that all the imports are working.
So, what we could do, easily enough is we could go down here and we could just call test and data equals import that.
We could call it directly, right?
However, we're not going to have to do that.
Over here, we can say, import pytest as well.
And what we could do is go put a decorator here and say this is a Pytest fixture.
Like that.
And when we say this, we can just use whatever that is called right there as an argument to our test.
So, lets just go like this and let's just print out what we get right here.
We might have to import yeah we are importing test fixtures up there, okay, great.
So, let's run it and see what we get.
Oh, whoops, I got to change this to explicitly import guitar_data but once we do that if we run it notice this passes and we go down here and click on the one that we're seeing.
Look at this, here's our list of object, object, object.
You can bet that those are our test objects.
All we have to do is make Pytest see our pc and then we just pass it as an argument and it's that method is run and the resulted value is passed into each test.
So, that's a pretty good start.
We should be able to somehow work with this method here and pass it to test data.
But again, we saw a dependency injection open/closed principle, all that kind of stuff.
They don't apply here and that's not really the way we're going to do it.
We're going to use mocking in a slightly different way, next.
Now, that we have our guitar data we should be able to make progress on eliminating the problem that we see here like, getting stuff from the database.
We'll be able to use this guitar data instead of the real database.
|
|
show
|
6:05 |
Well we made a good shot at it.
We tried to write some test where we set the style as electric.
And we call all guitars and we just do this cool little assert statement to check that Hey we only got electric guitars back.
That worked but as you recall we were actually touching the two dependencies that we were not suppose to be touching.
The Pytest-fixtures come to the rescue again here.
So what we're going to do is up at the top, is we're going to.
Let me put it like this.
import pytest-mock.
Remember that's one of the dependencies we added in addition to Pytest itself.
So if we have pytest-mock we can come down here and we can say that we have a mocker.
This is another test fixture but instead of us defining it Pytest-mock does it.
So let's just print out what this is.
And it's a little bit hard to see with the formatting.
But we have a Pytest plugin mock fixture right there.
And we can help us ourselves a lot if we put some type annotations in this.
So this is a list of guitar.
And this one is a pytest-mock.mocker mock fixture.
Here we go.
Those are the two we're using.
And so far it says you're not using this but you know what we're about to.
So we need to do two things here.
We need to mock out or patch in terms of Pythons terminology the two dependencies.
So with .NET what you do is you actually pass interface objects in.
And instead of passing them real production ones you pass test one.
Python is very easy to reach down and redefine what a function does.
And C# had some crazy post-compilation mechanisms to do this as well back in Team Foundation way back in the day.
I don't think they really push that much anymore.
But the idea is we're going to reach down and basically forward this function this test function here.
We're going to redefine what a couple of the functions are.
So here's how it goes.
We say mocker.patch Sigh.
Here's *args, **kwargs.
Again I hate this.
While you're working it tells you nothing about what we have to do.
But what you have to do is you have to put the full name.
So module-name.function-name.
Now let's go down here and have a quick look.
Hit guitars from db.
So this is the function that we're working with.
We can say autospec.
If anything here gets called its fine.
But what we actually want to specify is return value is this guitar data.
That's our other test fixture where our test data's coming in.
We already saw that that's great.
Let's see if this will run at all.
Well that's a good sign.
What's an even better sign is notice here.
Our hey we're going to the database.
That should never happen.
That's gone.
Why's that gone?
That's awesome!
So let's go down here.
And when we call all_guitars a couple of things are happening.
We're logging it that's the message that still is there.
And we haven't replaced that dependency.
But this used to be a simulated database call.
But now it's just a mock.
And all it does is say hey if you call me, you'll get that test data that we defined over in the other.
No not that one.
Our test fixtures.
Here you get this test data that we're returning from our test fixture.
Okay so we call this one no longer going to the database as it were.
Actually here.
call this one we're no longer going to the database.
Because for the scope of this function Pytest-mock has redefined what that function means.
And it'll put it back when it's done.
The other job we got to deal with here is we have to do this also.
For lib.log we don't care about any return value.
We just need it to not get in the way.
Let's run this again.
Boom!
There it is!
It's passed and you know, if we dig into this we go look at the output there's no output.
Alright just to show you if I put this back and run it again.
There's our logging patch out again no more logging.
Because we're no longer going to the database or going to the log.
We replace what those two dependencies mean.
Now you might think oh we better put this into a try.
Finally where we rollback the patch or into a with statement Pythons equivalent of a using statement.
Normally that would be true but the way this mocker works is it's already implemented that.
There's some clever stuff happening in the implementation for the test fixtures.
So just assume this is totally safe to do even if we have some kind of crash.
Alright, cool, right!
So we've finally got this working.
And now lets do one other test.
Maybe it's a little silly lets just say test all_guitars.
And the style is going to be all like that.
And we no longer can do this cool thing.
Instead, we're going to have types.
do a set comprehension.
So a little inline expression that generates a set.
And this will just be g.style for g in guitars.
Like that.
And then our assert will be that types is equal to the set with acoustic and electric.
Let's run that and make sure everything is working.
Perfect!
It passes.
Just to verify we're actually testing something let's see what happens if we weren't quite right.
Problem is we expected acoustic and we got what we're really looking for.
So perfect.
It looks like this is testing what we're looking to test.
The final thing is these two are good here.
These are great but we're only testing the happy path.
In a moment we're going to test the not so happy path.
To make sure that even when we pass bad data maybe it doesn't succeed.
But it fails in a predictable and expected way.
|
|
show
|
0:53 |
Let's talk quickly about building a basic Pytest test before we get too deep into testing for errors.
All we have to do if we want to write a test is we have to define a function which has the word test_ and then whatever you want.
Here we wrote test_run_me_wrong because we saw this still uses the dependencies.
But as long as the name has a test, underscore and are ready to go, Pytest will try to run it and then we make our asserts using Python's built-in assert statement here.
So, assert, all guitar.styles == electric for all guitars in, for each guitar in guitars.
Really, really clean, this is a simple way to write a test, as we saw it's not sufficient because, well, that goes to the database and does the walking and the other dependent things that we probably want to control and factor out of our test.
But this is the basic anatomy of creating a simple test.
|
|
show
|
2:16 |
While our simple test was nice in real applications it's usually not sufficient.
If you're testing just basic code it's fine but we're actually testing things that work with databases and logs and there's a lot of stuff, a lot of dependencies at work deep, down below that we can't even directly work with.
But don't fear, pytest and Python have plenty to offer to solve this problem.
We have two things that we need we need to have some test data.
If we're not going to get it from the database where are we going to get it?
So we created a test fixture to pass in this test guitar data and we want to mock out, temporarily redefine what certain functions mean and what they do so that we can actually call them but they don't call the dependencies.
They go get redirected to something here like for our get guitars from DB where we're returnin' our test data.
So what we do is we pass mocker, as an argument and we have to have pytest_mock installed for this to work, of course.
Once we do that, we can then call mocker.patch give it the functions that we need it to work with we can just say log, we don't care about what you do just don't crash, just let people call you and then for the get guitars from DB that has a return value we got to work with so we pass our guitar data over to the mocker like that.
And then, from thereon, we just proceed as normal basically continue with the test as we had tried to write it in the first place but now, get guitars from DB and log no longer are working with our dependencies they're just working with our mock data, our test data.
Super simple, right?
One thing that's worth noting that is not visible or apparent here is there's no dependency injection there's no IoC container there's not even an open/closed principle at work.
That's because in Python it's more common to patch out methods like this.
Rarely do it in production, but for testing it's considered kind of the way to go.
And also, it is much harder because /lib is not a class with a constructor; /lib is simply a module, and it has functions, all right?
So it's a little bit harder to create these natural seams like you do in C#.
We'll just pass stuff to the constructor although we could totally do it here it's just not that common so this patch style is more likely what you'll see.
|
|
show
|
4:39 |
Well, it looks like were testing for the happy path.
All the things we expected, just fine.
So, were testing if we go ask for just electric guitars we get those back.
Then if we ask for all the guitars we get both electric and acoustic guitars.
A super important part of testing is to test the air handling and the air conditions and the off by one errors and those types of things because you want to make sure that if invalid data goes into your function or your class that it deals with that and it doesnt get corrupted.
It just says, No, Ive checked for this.
This is invalid.
Were not going to proceed.
Or something to that effect, right?
This is really important, and its often skipped.
Were going to have another test.
A test for an invalid style.
We dont have to have anything passed in we dont need the data.
We also dont need our mocker at this point.
Although, to be completely safe maybe we would pass it but it turns out were not going to need it.
So, lets go over here and say our we dont even need to work with our guitars well just say lib.all_guitars and were going to pass in none.
None in style for which theres no guitars.
I think that should just return an empty set.
But none, is not a valid style in our world.
Okay?
So, were going to test this but what should be the outcome?
What should we assert?
We just want this to crash.
We want it to throw an exception to raise an exception saying No, no, no.
This is an invalid argument.
Or something to that effect.
Thats what were going to do.
Now, pytest has a way to deal with this but lets just first run it and see what happens.
That one didnt work.
Lets see, weve got an attribute error.
This is the most common exception that youre going to see in Python.
Its equivalent to .NETs most common exception NullReference type.
This says, None type.
None.
This says, None type.
None.
You know, Pythons an equivalent of null.
Does not have a function called lower.
We passed in None.
It tried disable the style lets lower it so we can create a canonical representation of it and then test it in our test here.
But it obviously didnt test for this.
So, yeah, we did get an exception but attribute error.
This is not, this is like a, this is almost never the right type of thing for us to throw to indicate this.
So, lets go over here and tell the test we want it to have that problem so say with pytest which we havent yet had to import but I told you once you get far enough down the path youre going to actually have to import pytest.
So, first time were doing that.
There it is.
We can just say this raises and give it an exception type.
And the exception type that I think is going to make the most sense is ValueError.
Its like, argument exception or something to that effect.
So, were going to say we expect this to throw a ValueError, meaning with a message like Hey, none is not a valued style, a valid style.
Lets see what we get.
Well, it crashed.
Yeah, it basically just didnt even catch it.
Lets go and fix this so its not doing that.
And, obviously the problem is were just not doing any validation.
So, here were going to say if style is none or not style.strip.
Basically, this is the equivalent of string.IsNullOrWhitespace.
Theres not a single function in Python that'll do it.
But these two, they'll do it.
Is it None, or is it an empty string?
And then, just going to say raise ValueError.
There we go.
So well just say whatever they passed in whether thats None or just empty string or whatever, is an invalid style.
Currently, our tests are failing.
Lets run it again, see if that fixes it.
Boom!
Of course it does.
Our invalid style passed so we can come down here and look at it.
We could probably turn up the logging to get details about whats happening but let the default level of verbosity.
It just passes, right?
So, it is passing because it does throw this exception.
We can test that here if we just said something like this return empty list.
Well see that this test failed because it did not raise this exception.
Makes a lot of sense.
There we go.
So, thats like the expected exception attribute in C-Sharp.
There we go.
So it looks like were testing both for the happy path and the failure path.
Because the firs thing this method does is validation it never gets to try to call get guitars from DB or log or anything like that.
So, we dont actually have to mock it out.
You know, if you want to be super safe go ahead and throw that in there but its really not needed so Im not going to do it.
And thats it.
This is how we write tests in Python with pytest to test this library right there.
We created our test fixtures and all sorts of cool stuff to make our life for testing much easier.
|
|
show
|
0:26 |
Testing for errors with pytest is easy.
All we have to do is try to call the code that's going to crash.
And make sure we do that within a with block here we say, with pytest.raises say the exception type or something that derives from that exception type.
And it'll get caught here.
If that doesn't happen it just floats the exception, fly through which of course is going to crash the test.
Just use pytest.raises and you can test for errors.
|
|
|
37:43 |
|
show
|
1:49 |
Time to move on to asynchronous programming one of my favorite topics, actually.
We'll see that asynchronous programming in Python has a lot of similarities back to C#.
Do you love the async and await keyword?
Well, guess what?
You can have exactly the same thing in Python.
Literally, it's the same syntax.
We're going to talk about all the ways that Python asynchronous programming is amazing and a few of the drawbacks, as well as some libraries that alleviate many of those drawbacks.
So asynchronous programming in Python is really great.
I would say it is one of the areas where C# and .NET actually clearly have Python beat.
There's a couple reasons for that but nonetheless, it's still really good over here.
In a lot of situations, they're pretty much the same.
Fill in all those details as we go through.
When we think about performance and writing asynchronous code, we usually do that for one of two reasons.
First, we might be looking for greater scalability.
Maybe we're writing a web application and the web app is talking to the database and some external APIs, and things like that.
So most of the time, it's waiting on other external things but if we do this all on just one thread or try to just line it all up the website won't be able to do other processing while it's doing that.
Threading, threading requests, do help with that but we can actually do even better with async and await.
That's one side of the story, really common the other is we want to go faster.
We want to compute things faster.
And with modern hardware and modern CPUs with multi-core systems, that's truly possible.
Some of the time we'll see what requirements and limitations around Python there are but that's certainly one of the reasons you would want to add more concurrency to your system to take advantage of the hardware you have the multi-core system that you already have.
|
|
show
|
2:06 |
Let's start with scalability.
Suppose we're running a web app, a web server and we want to do as much processing with the minimal amount of resources as possible.
One option would be to spin up a whole bunch of threads and have each thread handle a request.
That would work but that's actually a lot of overhead.
Threads take up a lot of memory the contact switching can be high.
If we do them in order and request one then request two and request three like in this example here.
Well, we'd have to stack them on top of each other and that would be super slow.
But as I laid out in the beginning these requests are generally for web apps mostly about waiting.
I got a little bit of stuff from the requests and then, oh I need to take this user id in the cookie and go ask the database for the user details or once I get the user details I'm going to call an external API like, you know charge a credit card or something and I wait on that.
And then I get it back and then I'm going to say, Oh here's the, you know update their record, yeah, now they own this thing in the database and wait again on that.
And then I'm going to return some stuff over the network.
So all of this waiting means that we can break up our requests and while though we're waiting we can just immediately pick up the other thing.
So if we look at the response time yeah, we're going to take however long those waiting periods take for response one to execute but we don't have to wait for response one to be totally done.
There's probably right at the top of two is we're probably already just waiting on some database or something.
So we can go ahead and just put that work aside using the async and await keyword pick up request two and get it going down the road until it starts waiting on the database for something.
That pretty much means the latency of the additional time for two requests versus one at a time is really, really low.
Again, request three here we can respond pretty much right away.
With just a little bit of AsyncIO a little async and await, we can do a lot of magic to speed up this execution and we can do this even with just a single thread without adding all the overhead of many, many, many threads.
This is the fundamental idea of how Node.js is actually able to handle a ton of concurrent requests and you'll see that's also what Python's idea is.
|
|
show
|
3:53 |
Maybe you don't care about scalability handling many requests the same speed you would handle one.
You care about raw speed, you want to take the one thing you're doing and make it go faster.
And threading and parallelism is definitely powerful for that, let's see why.
Here is my MacBook, as viewed from the virtual machine that I was running Windows 10 on, and if we ask it how many CPU's it has, it says I have 12 CPU's.
By the way, how awesome is that?
It's actually a core I9 with six cores and then those cores are hyper threaded so the OS sees those as 12 CPU's.
Look in the background, I've launched Python the REPL, the read eval print loop.
Here just type Python, and then I wrote a really simple program.
I said, x = 1 while true increment x.
Look at the CPU level of the system.
That program is absolutely pinned working as hard as it possibly can, right?
It's while true adding one just 'til you stop it.
But if we look at the CPU level the CPU level given the recording of Camtasia the entire operating system plus this is only 9.57%.
This program at max can only do 1/12th of what the operating system believes that it could do.
That is not great, so how do we solve that problem?
Well, we have to do more stuff concurrently.
This algorithm obviously doesn't lend itself to concurrency, but many algorithms do.
So if we were able to write, break up our code our algorithm, into multiple concurrent steps and run those say on threads, then we would be able to take advantage of all of these cores and make our code truly fly, that's the promise.
On Python, there's a limitation it's not as bad as it sounds, but there is this limitation that really effects thread performance and computational speed.
You probably have heard of this thing called the GIL or the Global Interpreter Lock.
And we talked about reference counting, right?
When we create an object or we de-reference an object we increment or decrement that counter that reference count.
Now, Python had a choice to make.
Could it support concurrent interaction with all of those objects where it would have to lock on every one of those references?
Or it could say, You know what, we're not going to allow more than one interpreter operation to run at any given moment.
In which case, we don't need to lock that because only one reference or de-reference could possibly be happening there.
That's the path they went, and it's really about protecting memory management consistency, not a threaded sort of thing.
But the effect is that Python can only run a single Python instruction at the byte level at a time no matter how many threads you have.
So that means for pure Python threads do not really do anything other than add overhead for computational speed, that's a bummer.
Well see, there's actually a couple ways that we can get around that and make our Python take advantage of all those twelve cores and fly, as well.
One of them is something called multiprocessing which is where you take what would be a thread and you kind of copy it into a separate copy of Python along with the data it's using, run it over there and then just get the return value.
That way it's a separate process which does not share the GIL, right, the GIL is a per-process type of thing.
So that's Python's main way of getting around this GIL situation and we'll see a little bit about that right at the end, okay?
But if you want to read more about this there's a great article over at realPython.com/Python-gil.
What you'll find is that even with this GIL in place if the two threads are working in Python and one of them is talking over, say a network or doing certain types of C code down at a lower level it can actually release the GIL and regain some concurrency so it's not as bad as it sounds.
But it is something you definitely need to be aware of and it's something very different than C#.
|
|
show
|
2:21 |
So let's bring this all together this asynchronous landscape in Python graphically for you all.
We can have basically a divide into two things.
Can we do more at once, that is like while we're waiting can we go do other stuff, basically or can we actually do them faster by taking advantage of those multiple cores?
On the do more at once side, we have Async IO and the async and await keywords, so this is really about IO driven concurrency basically what we're going to dig in to.
We also could do threads.
Because of the Global Interpreter Lock the only time threads really add concurrency is when the thread, one of the threads releases the GIL.
This is if you put a thread to sleep if you talk over the network there's certain types of things that will trigger that to happen, but pure computational stuff just running in parallel across all the cores like you can do in C# with a task that's not something you can do in Python.
It doesn't work that way.
There have been many attempts to remove the GIL.
There are new attempts which actually look promising.
The previous ones didn't go anywhere.
There's some new work being done that actually might bring Python threads on par with C# and .NET but for the moment, this is the world it lives in.
To do stuff faster, to get around the GIL we have to not use threads, but multiprocessing which have a very similar API, it's not nearly as much work as it might sound, but that is what we got to use or we have to write in C.
We could write C extensions or even we can write in something called Cython.
You write in Python, with type hints but then it actually compiles to C.
Cython has some keywords that are like using statements basically, that let you say this part I'm going to release the GIL and go crazy and you can get really good parallel performance by still more or less writing Python code but making certain parts run or be implemented in Cython.
Now there's a couple libraries that bring this stuff together to make it easier to not see all these differences.
One is Trio.
It's a kind of a task scheduling library with cancellation and stuff.
That's also quite similar to .NET and the built-in things there and then there's something called Unsync which is a beautiful, beautiful API on top of Async IO threads and multiprocessing.
We'll actually look at that just briefly at the end.
It was inspired by C#, as we'll talk about when we get to it.
So this is landscape, keep it live.
|
|
show
|
0:34 |
In this chapter I'm going to assume that you know C#'s async and await keywords that you know the underlying task scheduler that you understand how all these things work together and even how async and await in C# run on a single thread for the standard scheduler there.
If you don't know that take a moment, go over and watch this video I did.
It's a little bit old from 2012 but nonetheless it's still perfectly relevant for C# async and await keywords there.
This will give you the background that you need so then you'll be able to appreciate what Python is doing over on this side.
|
|
show
|
3:27 |
As is our custom for this course we are going to first look at what we're trying to build in C#, and then we're going to go build it in Python.
Now, what we're actually looking at here is not new.
Remember, over here in NuGet we wrote this program that would go and it would do this webscaping stuff and it would go download ten different episodes of pages of the pages for the episodes of my podcast, Episodes 220 to 230 and then, it would use the HTMLAgilityPack to pull out the HTML.
Over here, where we have GetEpisodeHTML this line 48, actually, sorry.
This line right there is a blocking called actually, I kind of faked it I don't know if you look carefully there, get async.result.
So I said, Hey, hey, I don't actually want to do async and await stuff, I just want to make this run synchronously so we can compare it to what we have directly over at Python.
I don't think the HTTP client actually has an option for a synchronous version, but, you know this is effectively a synchronous version.
Over here, in the Chapter 9 async we've unleashed the async beast here, if you will.
Over here, where we do GetEpisodeHTML we're actually a way in.
So, we have to do a little bit of juggling here.
What we would like to do is just go for all these ten episodes, just call GetEpisodeHTML and the most natural way would be to, like, loop through it and then just, for each one call this and await this directly.
But what happens is the program would be more scalable it potentially could do other stuff, but it would not make this individual task any faster because we would still be waiting for one and then the next and the next.
So in order to get true parallelism we're creating a list of tuples.
Woo!
Each tuple contains an integer, the episode number and a Task which is the pending download task for this.
So what we do is we go through there and we start the task right there and then we put it into this list so at this line, once that's all done all the downloads have started, and then we go for each one of them, we're going to await the task and get the HTML.
So slightly more complicated but that's needed to get the true parallelism out.
Now, let's go ahead and run this.
So, notice, it gets all, starts getting all of them and then it gets all the responses and it takes only 1.7 seconds.
I don't know if you remember but the other one took almost 10 seconds.
Let's do it one more time.
A little bit longer, 1.9.
Let's do it a third time just so we have the timing pretty reliable here.
1.7, 1.9, 1.6, let's just say it's 1.75 Or something like that on average, right?
This 1.6 is the best we've been able to get.
The website responds super fast but it's actually on the other side of the United States the absolute other side, the East Coast, in New York.
So that means it's going to take, you know, the ping time from here to New York, just on on, each direction, right, to get this.
So, it's, there's a built-in delay from the network but it's kicking off, you can see the thread pool is running different threads and all sorts of good stuff down here.
Alright, this C# version, this adaptation of the static one, the serial one, the sequential one over to this async version that's what we're going to do in Python as well we're going to see how to do this over in Python and it turns out, it's remarkably similar.
Pretty cool, actually.
|
|
show
|
3:11 |
Before we start writing the synchronous version let's just recall exactly what's happening in the Python Web Scraper here.
Change the title this is what stands in as our title here.
It's not asynced yet, but you know what?
Let's be forward looking here.
So what we're going to do is we're going to go and call this function called get_titles, which down at the bottom just says let me make this identical.
Here we go.
We're going to go from 220 to 230 and were going to get the HTML and then we're going to take the HTML and parse it screen scrape it to pull the title out and we are just going to print that out green.
Getting the title is super easy.
We're using Beautiful Soup, we pass it to HTML we just get the header, the H1 and we just get its text kind of cleaned up.
In order to get the HTML, we're using httpx.
Another common option is requests but remember s does not support the async version and I knew we were headed this way, surprise surprise in the end.
So we wanted to use async and you can't if we went down the requests route.
So we just started with httpx which has a compatible API, plus the async stuff.
So down here we're getting the contents of the URL we're verifying that it worked and then we're returning the body of the HTML text.
Let's just run this and see what we get.
Remember it's not asynchronous yet.
Getting 220.
Got the title.
And then we actually print the title.
Get 221.
Got the title.
We're printing it.
All well and good, it works great.
We don't have any timing yet, do we?
Let's put some timing in this.
I'm sure you've seen this somewhere along the way but the datetime features of Python are quite similar to what we have in .NET with the datetime class.
So, here we just go datetime this is the module and then it has a datetime it has a date and a time and a timedelta, bunch of things so it looks a little weird but we will see a datetime.
This is the class .now tO.
And then we'll have the timedelta dt which will be the new now minus the original which will results in a timedelta and then we can print.
Finished and dt.total_seconds.
Here we go, we'll go with say, total_seconds with two significant places digit grouping.
Sure we don't need that.
All right, let's run it one more time.
Super, it took 11 seconds!
Whooh!
Well, we saw that we could do better.
The .NET version was 1.666 seconds for the best outcome that we got.
So 1.6, 1.7 seconds versus 11.
Obviously, we want to make this work better right?
Again, it's because we're just waiting 99.9 percent maybe not 99.9, 99 percent of the time that this program is running, it's waiting on the internet or some server out there on the internet to get back to it.
It's doing extremely little amounts of work so if we could do all of that work all that waiting just in one batch that would be way way better.
Our job is to take this code and convert it to something like c-sharps asynch in a wait with asynchronous methods and so on.
|
|
show
|
7:39 |
It's time to use the async and await keywords to make this awesome.
Let me put a little caveat here.
The first pass, the first attempt that we're going to make here is not going to be awesome for two reasons.
One, it's actually not going to add a lot of concurrency because of the way the algorithm we're using at the bottom.
And second, the direct use of asyncio is clumsy in Python.
It really, really should have been what they did in C# and .NET.
They should've just said, you know what, that's good we're going to copy that exactly.
It's a little bit clumsy, but I'm going to show you a library that basically makes this Python version identical to the C# version in terms of the API.
But I want to show you the native, built in version without that library first, okay?
So what makes it clumsy?
Well, we have to import asyncio.
There's nothing clumsy about that.
But what we have to do is we have to manually work with the scheduler, basically.
This thing called the async loops, it will say get event loop, here.
And then we want to work with that in different places so for example here we'll say, we want to say run to completion, run until complete.
Want to pass this function.
In Python, you can't just start one of these asynchronous methods.
You have to create a task out of it and then hand it off to the event loop to be run.
C#, you can just call these methods that return a task and do a bunch of stuff and that's pretty cool, but here not so much.
So we're going to have to say run until complete and at the end let's go ahead and go down here and say, just to make sure.
Obviously this would happen at the end of the program on it's own but we can go ahead and close out this loop like so.
Now there's a warning saying this is not an asynchronous method here it's just a regular one, right?
We got None, the return type of a void.
Remember they always return None if they return nothing.
And we were looking like a generator or something that was awaitable.
So let's go down here and work on this.
Now from here on out this basically looks pretty much like C#'s style, okay.
So I can say this is an async that was the wrong word, async def.
This is an asynchronous method.
And once we do that, we can then await other asynchronous methods.
That looks like C#, right?
That's cool.
So, obviously now it warns us Hey, this is not asynchronous.
Right?
So we're going to go over here I'll have this as being an asynchronous method here and our goal will be to convert this to be an async call.
In this little tiny regard here, Python actually has something a little bit better than .NET, I think.
So we can have an async with.
It's like an asynchronous using block and down here we are going to say httpx.AsyncClient as client.
We can go over here and call get and when we do, what we get back is actually a coroutine, not the response.
It's a coroutine of response.
So in order for this to work we got to get the value by awaiting it.
Perfect, right?
What does this do?
Why do we have to have an async thing here?
Remember this is calling basically enter and exit.
It gives this class an alternative way to work with enter and exit, which are the implementation details for being in a with block.
What it does is it gets an __aenter__ and __aexit__ that are asynchronous themselves, so this will asynchronously close any open sockets and things like that.
And I'll just put a little note like that here for you.
Alright, now this should run but it shouldn't be any better.
Let's go down here and look why.
So when this Git titles we're going to say I'm going to go through this loop and then I'm going to wait for one to be finished and then go through the loop again and wait for the next one to be finished and go through the loop again and wait for the next one that's the same as the synchronous version.
Other than if more stuff was happening it could happen in parallel.
But let's just see it's working, not broken.
Hehe, look at that!
T23, T24, they're rolling in.
It looks like it totally still works.
And hey, it went a lot faster.
That's weird.
There's no real reason why it should go faster.
Probably just the network was faster or something.
I suspect this is not any better, but who knows maybe there's some small improvement.
So I'm going to duplicate this because I want to leave a copy of the old algorithm there for you.
Now, what we did before was we came up here we said tasks, is equal to something.
We went through and we said we want to create some kind of tuple.
If you remember that from the C# version.
We create this list of tuples of integers and task and-- Oh my gosh, so we went through and we started the tasks and then we created a tuple which holds the episode number and the task.
And this started it, right?
Actually, this started that task running.
Line 31.
So we're going to do something similar here.
But in Python it's a little more complicated with this native API.
Again, this gets better, but I want to show you the native API.
So we go to our global loop and we say, create task.
Then we give it a coroutine that's going to run.
Calling the function here doesn't actually start it which is super annoying.
It just gets it ready to be started.
So what were we doing?
We were saying get HTML.
So that's our task.
And then we have episode, it's going to be N and then into our tasks, what we did is we added a pent here, a tuple.
So what we can put is just the episode number and the task.
Maybe I'll make that a little more explicit.
Like that.
So we're going to add a tuple this is how you do that in Python.
You just put two things separated by commas if they're inside of a function they need extra parentheses to indicate that there's one thing that is an argument.
We're going to have the episode number and the task.
And then down here, instead of doing this we'll do something like, for order episode task in tasks.
We're going to do tuple unpacking here, which is beautiful.
Then we have to await the task and this is going to be the episode number, like so.
Alright.
Well, this one should start all of them.
Going to create the task.
This part starts it running, this defines it.
This starts it running on the loop.
And then we're going to add more and more and more while there's await in, they can all run and then we're going to make sure they're all finished.
That's the await on line 60 that basically says wait for this one to be finished.
We'll take them in order, they probably will finish out of order but that's fine.
And then once we have the string we just go work on it in memory.
All right, let's see if this got any better.
Remember, we at best were on eight seconds here.
We had 1.6, 1.7 seconds in C# as our best option.
Let's give it a shot.
They're all started.
They're all finished.
Boom.
Look at that!
1.03 seconds.
Almost twice as fast as C#.
Incredible.
It's probably just timing.
Let's run it again.
No.
I've been doing this as I've been working on this class over and over, networks have been at different modes it's been different times of the day.
Consistently, Python is about 50% faster than C#.
I don't know why.
It seems like they're both basically doing just a tiny little bit of work.
Possibly Beautiful Soup is much faster at parsing HTML than HTMLAgilityPack?
Maybe the HTTP client, HTTPS versus HTTP client is better?
But either way, look at this we're crushing it.
We are completely getting a nice, stable one second response time or time to do all of these jobs.
Man, I love it.
This is great.
|
|
show
|
1:26 |
Let's quickly review creating an asynchronous method in Python, that actually does asynchronous work.
Here's the get_HTML method given an episode number.
It's going to go construct a URL and then asynchronously call out to talkpython.fm figure out the content of that page make sure everything worked, and then return the text.
So the first requirement, just like C# is that the method is designated as asynchronous.
So we have async def get HTML.
Once that's in there we can then start using the async and await keywords inside the method.
So Python has this surprising syntax about asynchronous context managers but, of course, there's network stuff going on in there you would probably want that to be a asynchronous this is how we tell the run time to do that.
So we say async with right, otherwise that's a standard with statement.
And then the main thing that we're going to do is we're going to go to the client and we're going to call get.
That's where we're waiting for the response from the server so we use the await keyword.
You saw without the await keyword we get a coroutine who's return type when finished is this response.
But if we await it it gets converted into the response directly right here.
This is exactly like the await keyword in C#.
Normally we would get a task of a response a generic task or response back.
But if you await it, you just get a response.
So your intuition from async and await in C# is very, very applicable over here.
|
|
show
|
1:49 |
I told you there is a much better API that we get to use and it's incredibly simple to bolt on to Python.
There's a library called Unsync by a guy named Alex Sherman and Alex said hey I'm working with the Python async and await and it's just rubbing me the wrong way because he has a background from C# and .NET and he expected it to be similar.
So let's look here.
I'm going to go on a little rant about async and await and Python, what's wrong.
And Python 3.5, we got support for async and await.
Unfortunately, I've been having trouble adapting to Python's version of async and await.
Especially coming from C#'s implementation of the tasks parallel library.
There are two big friction points that I have that are it's difficult to fire and forget an asynchronous method you can't just call it and let it fade off into the background and run, right?
That doesn't work.
You have to go to the event loop call run and help complete.
That's a problem, right?
You have to wait for that to finish or you can start it but eventually you still have to juggle this loop as we saw.
The other one is, you can't go remember in the C# one I said, you know, result.
Basically, dot result, on the object to make it block the first synchronous version.
Well in Python, if you try to do that it'll throw an exception and say hey I'm not done yet like could you just wait for me?
I want to be done, right?
So this library does that and several other actually really impressive things.
So we're going to use this library to make Python's async and await much more like C#'s.
Which again, I fully acknowledged is better for, basically, the internal run time and the tasks parallel library and the scheduling and all that and also unify some of the threading APIs like asyncio threading and multiprocessing to achieve some really interesting things as well.
So if you're coming from C# you definitely want to make use of this library.
You can pip install it and it's really simple.
|
|
show
|
2:29 |
Happy days are here because we get to use unsync to make this much better and much closer to C#'s version but we got to add unsync as a dependency here.
We'll go ahead and let that get installed.
Say it's spelled correctly as well.
This library is incredibly simple.
It's one single Python file that's only 126 lines long however, it does a lot of magic.
So let's import unsync from unsync import unsync.
This is the library, this is the decorator.
Okay, this business about the loop that we juggled directly, forget it.
We don't need that anymore.
This stuff about running until complete, forget it.
We don't need that anymore.
What we're going to do is we're going to say result.
It's not there yet cause I haven't added the right change to it.
Stuff about running until it's done and then closing that's all gone.
That's nice, right?
The trade off we got to do is we have to say that these are unsync methods, not regular methods.
Not regular asynchronous methods, but unsync.
And what that means is unsync basically manages the run time for it.
From this line onward, oh stay still.
From here onward, it's unchanged.
You just write regular Async IO code.
Down here, again, we have an async method so we're going to say unsync on it and this loop create task blah, blah, blah, forget that.
Call it like a function like it was meant to be, like it is in C# you call the function.
It automatically starts and runs.
It's glorious.
And that's it.
All the stuff about juggling the loop creating the task, running to complete all of that is gone.
And let me go back up here this get titles, I think it doesn't quite get the type hint that this is what's called an unfuture that comes back but I think it's going to run.
Let's run it.
This is basically wait for it to be finished.
All right, let's go.
It's running.
It's done.
Boom.
It ran in 1.08 seconds.
That is super cool.
And what work did we do?
Not very much.
We just stopped doing all the Async IO junk.
And we just put this one decorator on the asynchronous methods.
Run again, done.
Run it again, done.
Whoo, look at that, we broke sub second time yeah, that is so, so sweet.
We got this working really, really well and we were able to use unsync to do that.
|
|
show
|
6:25 |
Remember this picture?
I said you can use asyncio to weight on I/O type of completion.
You can use threads if somehow those threads are being the GIL is somehow being released like with the thread.sleep or we're talking about some kind of network thing.
But if you wanted true computational and parallelism you got to use multi processing and whoo!
You're like, jeez why is this so complicated?
Why can't I just use a task for all of them?
That's where unsync comes in!
So, we saw unsync already simplified the asyncio API.
What it also does is unify asyncio threads and multi processing all behind async await.
And it's actually super super cool.
So let's go look at that.
Now, there's not really any parallelism that's super interesting here but let's just go and throw in some stuff that we can do.
So, here's the rules about unsync that I haven't talked about yet, because it actually does more than just asyncio.
If I apply this decorator to an asynchronous function it will run on this implicit, behind-the-scenes AIO event loop, that it manages on a background thread.
Okay?
That's pretty awesome!
So, we just put it on asynchronous methods and it does all the loop juggling for us.
But it does more.
If I did this and I put it here this will let's go like this, maybe make a little comment.
So this will run this function on a background thread.
Yes, so if we say unsync on a non-asynchronous method, it means it cannot run on an event loop.
Unsync will go well, you still want to do something asynchronous with it, so let's run it on a background thread.
And see this get title from HTML?
Now, here now we have to await this as well.
As if it's going to complain because the style is let's just not worry about it.
It's still going to work like I said, there's some weird type detection stuff between the decorators and PyCharm, let's just see it works.
Yep!
Oof, took forever that time!
Let's try it again.
You know, that's probably a lot slower, isn't it?
Oh, no no, it's probably just timing.
Look, it's much faster.
It does seem consistently a little bit slower.
Ah, I don't know.
Networks!
If we put it like this, it's going to run on a separate thread, and then it becomes something we can await exactly like if it were asyncio.
Right, that's super cool already.
But remember, this the way this works it doesn't make any sense to run this on a background thread because the GIL is still at play.
Even if unsync is trying to manage the API for us.
It's still not really going to run more than one instruction at a time.
So it doesn't help us.
What we have to do in Python is run in a sub process.
Now it's pretty easy, basically go to this multi processing API and you give it a function you say run that in another process and give me the return value.
Though it's not, like, a lot of work that you have to do but it is a very different mode of execution.
And that's really, at the moment, what's required to do computational parallels in Python.
Well, unsync has a cool thought on that as well.
Unsync says, you know, we can go over here and say @unsync(cpu_bound=True).
And if we say that then unsync knows, hey this is a computational thing Python doesn't do that well with threads.
So let's do that with multi processing.
Just changing that to that converts from using threads to using multiprocessing.
And I guess I can put a little comment here as well.
Here we go.
So, this is the more appropriate one.
It might be slower, there is the start up of the processing and this execution is really fast.
So, let's run and see if it's any better.
I might just comment it out and say we're not going to do this.
But, it's incredible that when these approaches are relevant for the type of execution, the algorithm you have that all you have to do is put an unsync decorator on a regular function, and it becomes something that runs on a thread and you can await it or CPU bound equals true and it's something that runs in another process and you still can await it down here like this.
Nevermind that warning that's Pycharm badly guessing at what it should do.
Alright, again done.
1.10 seconds.
Try again.
1.12.
It's not really making it better.
1.09 It's not making it much worse.
I think this is mostly just the network discrepancies that we're seeing.
Right, well, I guess I'll leave it like this like if this were me and these numbers were coming out that similar I would just just call this the regular function.
I wouldn't go to that trouble.
But, there are many many examples where this execution takes a lot longer.
And this would make a big difference.
Like, lets go down here and say 'time' import 'time' at the top.
Yeah, we can trick it here.
Sleep, this is like, this is like the sleep thread dot sleep and dot net.
Let's say it takes an extra hundred oh, that's seconds Let's say it takes a hundred milliseconds or two hundred milliseconds to do this.
And first, let run it directly.
Down here You're not going to await it.
That's pretty, pretty tall isn't it?
Can't await it if we're going to do that.
So let's just see how long it takes like that.
There you can see we got the the data back right away and it started processing it.
But it could only process them one at a time.
So that was two hundred milliseconds per time.
Right, as that number gets better, or bigger it gets worse.
So let's go over here and say we're going to try to use multiprocessing to do that.
Does it make it better?
it's actually I just have to await down here, remember?
I didn't get I got a coroutine chain back and then I tried to make it green.
Apparently you can't make coroutines green.
So weird!
Any better?
Ah, a little bit!
Three, Four, yeah I'm not sure it's any better.
I don't know how much parallels I'm we're getting out of our multiprocessing there.
Anyway, I'm going to comment these out and put it back just run it normal.
But, this unsync has a really cool API.
Oh yeah, let's take away this as well.
Put it back.
And it goes beyond just working with asyncio.
It allows us to take regular non asynchronous methods and convert them to either threaded mode or multiprocessing mode.
Which is pretty slick.
If you come from a C# background you definitely should check out unsync.
It really is basically an adaptation of Python's model over to what you would get from the task parallel library and C#.
|
|
show
|
0:34 |
We were only able to skim the surface of Python's asynchronous and concurrent programming models ways to optimize that, and put it all together.
We touched on some really good ones but if you want to go deeper we actually have a five-hour course called Async Techniques and Examples in Python.
We do a lot with threading, with multiprocessing.
Of course, we talk about Async and ways to bring that into the web, Trio and even some of the C-level optimizations and the nogil keyword, all sorts of cool stuff so if you really want to dig into that check out our full Async course over here at this url.
|
|
|
43:40 |
|
show
|
2:16 |
In this chapter, we're going to talk about computational notebooks, and there are a variety of options here, but by far the most popular are things call Jupyter Notebooks.
Originally they were called iPython Notebooks and that got broadened out, so Jupyter.
The environment where we run these notebooks these days is a place called the Jupyter Lab and you'll see that right here.
We're going to look at two demos one that's a simple getting started one and one that's more mathematically involved this Lawrence differential equations that we're looking at here.
Working with notebooks is incredibly powerful for visualizing data and telling stories but it turns out that it's a different way of programming than a lot of folks who are used to working in Visual Studio putting together a well factored application with different tiers, and all that kind of stuff.
In order to appreciate these, you really need to kind of take a step back and realize different people are solving problems different problems, with different experiences in different styles in Python.
When I first saw notebooks I thought Well, okay, that's cool and all but I just don't get it.
Just write a file, come on, just write a program.
And as I saw more folks working with it I realized for the way they work with Python for what Python means to them notebooks are perfect.
And the way that I would like them to do it the way I thought that it should be done was totally wrong for them.
There's a really interesting presentation it was a keynote, one of the keynote speeches at PyCon 2017 by this guy named Jake Vanderplas.
Jake is an astrophysicist who has become a data scientist, there's actually a lot of overlap between those things.
He was at University of Washington in Seattle and now he's at Google actually.
He talked about how Python is a mosaic and all the different things that's happening for Python not just in the web development space but in science, and in many other things.
And if you watch this talk, I think it'll give you a deep appreciation for the different styles of programming and problem solving, and a lot of the stuff from his world, from Python in science these notebooks are perfect for exploring data when you don't really even know where you're going but you need a great visual aspect to them.
That's what notebooks are about and we're going to see how to work with them in Python in this chapter.
|
|
show
|
3:56 |
Python really dominates the space of these computational notebooks.
So we're just going to get started and the thing we need to do is actually set up a little bit of stuff for us to do that.
Now, PyCharm does have some support for notebooks but most of what we do is actually just install and run Jupyter Lab and then work in Jupyter Lab but we're going to start by doing at least maybe we'll come back to PyCharm at the end.
So, here we are in our Python code section of the Git Repo.
I'll make a directory.
We also need to make sure we activate our environment.
Remember venv\scripts\activate on Windows without the dot.
So we have that here and we need to install Jupyter.
Notice that in the j's we don't have a Jupyter so we're going to install a couple of things.
Start by installing, jupyter.
Alright, that's a good start.
We've got a bunch of dependencies installed there.
Also need Jupyter Lab.
We can run this by saying just, jupyterlab.
So, it does a bunch of stuff Kicks off our browser and logs us in.
If you are super quick you be able to see like Hey, it's using this temporary token to make sure that we're allowed to access it.
and then it does some redirects over to it.
So right now we've got our notebook section over here.
Let's go in there and we can add all sorts of new things.
I could even click that or I could just go here and say Your notebook, if for some reason you're remoted into the system you could fire up a terminal in say pip list or do various thing like this actually.
See where we are.
Go to our notebooks.
Right, so we can do arbitrary stuff in the terminal here but we just want to create one of these new notebooks, for now.
And now notice it says What kernel do you want to use?
This is a little bit tricky because we installed Jupyter into our virtual environment but this Python 3 even thought it looks like it's going to be okay turns out to be not what we want.
Let's name this notebook something like References, or something to that affect.
So we're going to go and do some work with this and I'll tell you about the problem we're going to solve.
We're going to do some cool analysis and real-time interactive exploration of some data and then visualize it.
Like, that's the role of these things but I just want to show you something really simple.
Well, first of all, we could actually come up with like just let me show you how it runs.
y=2, we could run this little section and then down here we could come up and say x+y let's put 7xy.
We run that and we get an answer, like so.
When you just explore we can import some libraries.
Start working with them.
So this is all well and good except for, watch this.
Going to say, No no no!
Problem!
Problem here!
We don't have this library!
So, we're going to need a couple of libraries and this Python here if we say, import sys then we say Sys.path Python path.
Oh we got to get rid of this.
Notice, what working version of Python we're using.
We're using just the system Python and then the path obviously goes here but it, get it to print out which Python we're using it's turns out that this is the system Python not the Python that we're wanting to work with.
This is not our virtual environment Python.
With everything we're installing.
So, in order to finish setting this up we do need to do one more step to tell Jupyter, Hey!
I want you to register an option for us to select here that is our virtual environment.
but once we get that done we can just go up here and start typing some code.
So far it's not very impressive because it's just numbers as a calculator, basically but we're going to be able to improve upon this and put all sorts of cool interactive graphics and stuff up here.
|
|
show
|
3:11 |
Alright, let's exit out of our little running environment here.
It's important our virtual environment is active so we ask, which Python?
It's the one in our virtual environment.
So what we want to do is we're going to say pip ipykernel.
It's going to let us control the system just a little bit and register things, like our virtual environment.
Alright, looks like that was already installed.
That's great.
So then we want to run that module so you can say Python -m to run a module then it's that one.
I almost have probably copied.
See I want to register something for the user and the name is going to be course_env.
Alright, so what we're going to do is tell IPython or Jupyter about the virtual environment that Python lives at, which is active right now so you got to make sure you have your virtual environment active.
Oh, I forgot to --install here.
Alright, a straight up install.
Here we go, install this over here.
Let's see what happened.
If we look at that /kernel.json it basically says here is the Python you're going to use.
Its display name is course_env and it's Python.
So we go back here and we just run JupyterLab again.
It opens up, let's close this older one.
It'd probably refresh, but let's not mess with it.
Let's open this up and now, if we go an change it, look, here's our course_env.
Woo-hoo.
Let's go and do one more thing.
Let's go ahead and pip install feedparser which is a thing that we're going to want to use.
Notice it's going to install into our virtual environment.
This is a way for dealing with RSS feeds.
We're going to be working with one of those in just a second but it also just let us test, hey, that this thing worked.
So pip list up here.
We're getting a lot of stuff with all the dependencies here but notice we have feedparser like so.
And I guess we got to restart things.
And Jupyter, once again, over here and now, we can go to our course environment.
Forget the stuff we want to import.
Feedparser and just make sure everything's working.
Oh yes, it's working really well.
We can go over here and tell it to parse a URL.
Let's say the URL is going to be something like this.
Going to go to Python Bytes and we want its RSS feed, which is here.
Going to be that.
Check it out.
Well, apparently, that printed the entire thing.
Notice how small this is.
That's a problem because this is like a megabyte and we got the feed and we got the title.
Let's just go print rest.getfeed.
And within there, we get a sub document or a sub dictionary.
From that, we can get the title.
Here we go, Python Bytes.
Hey, we went and downloaded it and we turned it into this JSON document which is pretty awesome.
So we've got our virtual environment working with all of our dependencies installed.
They're not relying on the system one and we've got out notebook system up and running.
|
|
show
|
5:15 |
Alright, let's add a few more dependencies.
Were going to need these later and we can go ahead and install those.
Let's also go and add.
We'll put one of these up here and then, change this from code to markdown and I'll put some markdown code in here Python Bytes, domain reference, like this.
I'm going to say Python Bytes domain reference authority.
If we run it, it turns into proper markdown.
We can put markdown below tech, code, images all sorts of stuff are going to help us tell the story.
Over here, we can add, I guess we can add one more.
This is going to be the same markdown, but a subheading and let's just say get the RSS speed data as a dictionary.
'Kay, so that's what we are going to do right here.
It'll help us sort of talk about it.
We'll put some pictures and we'll go back and forth.
Now, there's really a couple interesting things to do.
First of all, notice that we can run these over and over and once you run them, now see it's got a star and then, it's got an output.
If we have another one over here I could do something like I want to print out the size of downloaded data.
Here we go, we'll do digit grouping and like this maybe if we divided by 1,024 1,024 and put megabytes.
We'll see how much data it turns out that this is.
We downloaded, actually let's put.
So there's something really interesting happened here.
Did you see that this took a while to run and that it has a five and this has a seven?
And I can put exclamation mark here 'cause we are so excited and watch how quick it runs.
Instantly.
These cells run independently.
They're not like a separate program I got to rerun the program from top to bottom to get here.
No, I'm just rerunning this little tiny bit.
Imagine we're doing some huge calculation here and it takes 30 seconds to generate the data but then, we want to graph it and slice it and work with it.
We don't rerun that cell.
We just do work with it and slice it afterwards.
So there's this really cool partial execution thing.
Now,it does make it a little confusing and I'm like, well, what is the current state of the data?
What if I run this and then, this and then, this?
Ah, it's crazy.
So you can always go and just rerun all the cells, notice our thinking.
Boom, now we got the same data, okay?
But this is super different than working with a Python script or an application code in general and it's for the right use case one of the huge advantages of Notebooks.
Alright, so that's how much data we have.
Let's go and add another cell here.
Well, that's interesting.
If it was markdown, here we go.
We'll find another little variable here called entries and we want to go over to our I'm going to rename this to feed.
We got to rerun this to redefine feed.
Here we go, so this is the dictionary and we could get stuff like bracket like this by key but I like the get of this style 'cause it doesn't crash.
It just gives you none if it's not there.
But let's see what happens when we run this.
There is 152 entries.
Let's see what the first entry is.
It looks like that.
Apparently, it has a title and it has title detail and where it gets really interesting is down here bum, bum, bum, bum, bum, bum, and summary.
Here we go, so we have our description for each one of these and this is going to be, basically the text it is on the page.
And notice that it has links out to other things like here's an article I talked about on the podcast on TechRepublic.
So the goal of this exercise is to go through the entire 2.5 megabytes of text.
extract all of these links, convert the links to domains and then, rank the domains by how many times we link back to them.
So that's our goal in this little exploration here.
And we could make this look a little bit less crazy by just getting the first 100 characters.
Maybe 100 isn't enough.
Let's say 300.
There we go.
That looks like it's something were looking for, right?
So once we have that working, we can go over here and just comment this out or we can entirely remove it.
I mean we would want to put something like download it or extracted some number of entries the length of entries.
Someday, who knows, maybe that going to be in the thousands so we'll put digit grouping so let's run this one more time.
You just hit Control + Enter.
Come up here and you see the hotkeys and stuff, like run and so on.
There we go, so we have a little summary like hey, we got 152 entries.
We could go look at the website and that would look correct.
So we're well on our way to talking to the sever getting the data, exploring it and I wasn't totally sure that it was description or summary I wanted.
I can just kind of poke around at it here and I can poke right without rerunning the expensive, quote, expensive.
It takes a couple of seconds but imagine you're doing data science and it's a huge computation.
I could rerun this and explore it without having to rerun the whole program.
It's really different than straight apps, right?
|
|
show
|
7:40 |
Now that we have the entries we need to go through and somehow extract the HTML links the actual source reference of those links.
This is sort of XML, I mean, yes RSS is XML and the XML has actually been turned into a dictionary, which is great.
But the thing that is the description itself is not XML, it's an HTML fragment.
So we're going to use Beautiful Soup again to parse, to screen scrape that and then pull out the entry.
So we're going to do a little juggling there.
One of the things I'd like to do really quick is I'd like to, and I've showed you the Ctrl + Enter hotkey for running an entry.
Notice that that incrementing, that execution count at the bottom there, incremented.
And I can run this over here sometimes it'll show you like what the hotkeys are up here like Shift + Enter and stuff.
But I want to know how to add a cell below here.
Now that's B, but let me show you how you can find out.
If I go over here and I type cell, there's a ton of stuff.
If I want to insert a cell below, I can just type B.
If I want to select the cell above I could hit like, K, if I want.
In the code, I could hit K and then you'll run that and then go back down, J back down.
Now if I want to insert a new piece, I hit B, there we go.
If I want to change to markdown, I can hit M.
If I want to change to code do I hit C?
No, I hit Y.
So I come down here and say, a new heading parse the HTML from the entry descriptions.
We could add even more text, right, this is just markdown.
Then we hit ALT + CTRL + Enter to do that.
B to define another block below, and off we go.
So let's define it all links.
We're going to just pile up every single link that we can find, not worrying about the domains yet.
We just want to get the links out of the body.
So we'll say four E in entries, let's just print to R or something and do that real quick.
Now, that's probably, we'd want a quote.
Here we go, all right.
So it looks like there's a bunch of entries.
So we're going to go through them, and what are we going to do?
We're going to save the description as E dot get description.
All right, that's going to be the HTML.
Then we'll come over here I want to say BS4, if you want auto complete you have to hit Tab, it doesn't automatically come up.
But notice we have a Beautiful Soup and what are we going to pass in?
Well, we can't give it the fragment I don't think, so let's give it proper HTML.
We run that, now, I believe it would prefer that we said HTML dot parser here, yeah.
We won't see the warning that it's putting out but, you know, deep down in the guts I'm sure there's some kind of warning.
So let's go over here and we'll say the links and we'll do a cool little list comprehension.
Now just give it some space, 'cause I want to separate these for a in soup.findall('a').
Alright, And let's just really quickly here print LAN of links, all right awesome.
So it looks like the data's coming along.
See how cool it is?
We can re-run and explore this little bit without re-computing all this stuff up here, granted it's not that bad, but like I keep thinking computation is expense, we're doing a bunch of work but we want to keep playing with it and seeing the output.
All right, so we can just see that, yeah it looks like that's probably decent.
We could even print out what the links are like let's say the first two.
Well, that's messy, but it looks correct, doesn't it?
Okay, so we're on a great path here.
But what we have is the entire hyperlink and I want to get just the href.
So let's go in here in Beautiful Soup we can go like this and, but wait, wait, print links.
Run it again, oh yeah, now were getting just the hyperlinks.
Okay, our data's looking better, data's looking better.
There's a couple of things that I don't like, though.
I don't want to talk about www versus not www don't really care about HTTPS, things like that.
So we're going to do a little bit of normalization here.
Okay, we're going to iterate on top of links and do a little bit of clean up so this part should go away, if we run this.
Here we go, try again, okay, perfect.
So techrepublic and www.techrepublic no longer a difference, we just want the base domain name.
It turns out we also have some aliases that we sometimes use and sometimes don't.
So let's go down here and do this one more time.
There's probably a slightly cleaner way to do this, but we're going to just say replace do.co with Digital Ocean.
This is like a re-director URL.
Okay, this is all good, now what we're printing out here each time we do a print, like notice, like right there and right there, the closing brace we're printing this out for just that one entry.
We want all the entries for all 152 or we want all the links for all 152 entries.
Now what we're going to do is come down here and say all_links.extend, it's like ad range links bottom, lets just print the first, I don't know, ten.
Let's also print out the link.
How many links do we have total?
How many different unique links do we have?
Well, how many times have a link been mentioned?
All right, 2,721, so that's pretty cool.
And see how nice it is to just explore this data we don't have to keep re-running it.
Like we can forget about how we even got this feed data.
Yeah, we're going out to an API doing RSS feed and we're hitting it, but all the stuff we've been doing down here, like, its off the screen and out of mind.
We just have this data magically by the magic of technology, we have it.
We can just work with it over and over and over not concerned about the latency of getting it or the computational cost, or whatever.
Though maybe it, kind of keeping with our style here let's put out a little statement here.
We can we say something like parse some number of links from all the episodes and we just run that again.
Perfect, we've parsed 2,721 links.
And notice when I run this, watch though as soon as this turns to a star and then goes back to 45.
That took like two seconds to run, but now that it's done, we never have to run that code again.
We just work with all links, which is now, remember just the raw links, we don't have to parse that two point five MEGS of HTML, RSS XML, blended weirdness, we're done.
We just worry about the links.
Like, now that we're done with that step, we're golden.
Let's hit B to add another one and then M to convert it to markdown.
All right, so we're going to stay with extracted domain names.
Actually, lets do one more really quick thing here let's make this a little bit smaller.
Let's talk about how many unique links there are.
And we can do that by creating a list and then going through a set through all links.
What does that do?
Well, that takes the links, all of them with duplication converts it to a set which has no duplicates and then turns it back to a list so that we can deal with it, and who knows?
Maybe it's like reasonable to say sort, sort that, right?
We just do it once and we're not going to compute it again anyway.
And lets move this down here so we know how many distinct links we have.
There we go, we've lost about 400 duplicates that were in there, these are like sponsor links and, you know, maybe links back to our profile.
Or who knows what those are?
But they're gone 'cause they were duplicates.
|
|
show
|
2:56 |
Now that we have our unique links let's parse out the domain names because things like GitHub and Twitter and Reddit all that kind of stuff is going to appear many times we want to know how many times each one appears.
So, B to add another code block here.
And we're going to use this thing called urllib which is built in to Python and we're going to use this URL parse.
We'll say the domains are URL parse, hit Tab.
And that's going to be link for link and what unique links, like so.
Now we don't want this, this is going to give us an object.
What we want is net location, like so.
And let's just do a little exploration let's print out domains first ten, something like that.
You know what those look like?
Those just look like broken, broken links.
My goodness, so we have this looks like I got to deal with it later.
So one, two, three that's the first three after we sorted, are broken, so.
Let's do this.
Three onward, run that one.
There we go, fixed, like a charm.
I don't know what's going on we must have just typed in some bad markdown along the way.
We did type 2.5 megabytes of text so I guess that generates a few errors.
Anyway, we we're able too see that really quick and just go back and change that here.
You might want to change this in the future cause I'm going to go back and fix that on the site probably.
None the less, here we have our domains and let's just print out the first ten.
There, that's more like what I was expecting.
We're getting Pycon.de, Python weekly, aka dot ms.
I'm tempted to replace that with Microsoft.com but it could redirect via Microsoft to somewhere else so I'm just going to leave it like that.
Amazon, Amazon, Amazon.
Now, we want the duplication in this list, at the moment.
We don't want duplication in the links, necessarily.
I guess maybe, maybe we do.
But probably we don't, right?
We probably just want to say what are all the things that we pointed at and then how many of them are from any given domain.
So the point is that we want to say well Amazon is more popular than the others because there is three links to Amazon and only one to Python weekly.
So we want this duplication, this is not a problem this is actually the essential part of what we are trying to work with here.
So, there we have it, it wasn't a lot of work it was a lot of talking and like looking at the data but yeah, not so much work, right.
Just a tiny bit of code.
You'll notice we're using a lot of these list comprehensions and other clever little programming techniques that we can write the minimal amount of code like this could be three loops, or it could just be that it could even be less if we did it it we change it I'm sure but none the less we want to have it really focus small bits of code some explanation, maybe a picture we're not there yet, but we're getting close as we go through it.
So this is the notebook style.
|
|
show
|
3:10 |
Alright, we have our domains here.
Maybe we'll go ahead and change this to say something, really quick.
First 10, domains are...
here we go, first 10 domains are like that.
Let's add some of them below, some markdown.
Now we're going to write some code and I think this will impress you.
I'm pretty sure.
It definitely impresses me when I first learned it.
So here's what we want to do.
I want to go through that list, find all the unique names I want to find that, and I want to find that, and so on.
Then I want to count how many there are.
Then I want to sort them by the most common first.
Give me that name and the count.
And then the second most common then the third most common, and so on.
So there's a cool library called collections so we can say, from collections import counter.
And we can say, the counter is going to be a counter of these domain names.
And then we can ask it questions like give me the most common.
And what that does is basically gives us a list of these things.
Say top 25, is going to be common, up to 25.
Why is it going to work?
Because this is sorted as I described it most popular to least popular.
Then we can just print, Top 25.
Are you ready for this?
Look how little code this is.
Boom, actually let's put it out like this.
I think we'll see it better.
There we go, I like the way that looks better.
We could do better pretty printing but you know what, we got this covered.
Look at that.
382.
153.
64, and so on.
That's it.
We've gone and found the popular domains.
GitHub, Twitter, and YouTube, apparently as well as Python.org, medium Reddit.
Maybe we want to exclude referring back to ourselves.
So we can come over here and we can do some cool trick with that, we call it excluded.
It's going to be a set like that and who knows what else.
Empty string, possibly, hash.
Here we could say, if link not in that.
Oh, whoops, we need to...
Probably the best way to parse it is like this.
D4D in domains if D is not an excluded one.
I'm going to run down here, see if this Python by 32 should go away.
And it does, because it's excluded.
We're like, hey, we don't want to count referring to ourself.
That's kind of weird.
No, we're not taking credit for that.
So here we have it.
These are the top 25 domains.
And as I look at this, I'm kind of feeling like this unique bit that we were doing.
I don't think I want to do that anymore.
Because we might refer to a project five times and we'll just go back.
I think this is still probably going to be exactly the same issue.
So many thousand, and we just need to change that little bit right there.
Rerun it again.
Ah, guess we got, one more to get rid of.
First 10 domains look good again.
There we go, and notice we're pointing to GitHub a lot more Twitter a lot more, that's because there are projects that are popular, and I want to count those.
All right, that's it.
So a lot of looking at the data, thinking about it bumping around back and forth and playing with it.
But in order to go from our domains to what ones are popular, it's ridiculous, right?
|
|
show
|
5:21 |
Well we're almost ready.
I guess I'll leave this bit here.
I'm not super fond of it, but it's okay.
We want to add a new cell.
I'll convert that to Markdown summary of popular domains then we hit B to add another.
Now what we're going to do here is we're going to do some graphing with a library called Matplotlib.
There's a bunch of different graphing libraries in Python.
We're going to use Matplotlib for this one.
Now we'll probably have to install it but let's go ahead and drop this import statement here.
We're going to import the plot and we're going to also use this library called NumPy.
NumPy is a really cool library for us to work with.
It does like high performance multi-dimensional arrays so Matplotlib uses that.
Normally if we work with these plots it'll pop up in a separate window but if we say percent, this is a special way to issue commands to Jupyter itself not to Python, so that when you run Matplotlib grab the graphs and display them inline.
So this'll probably crash.
Let's see if it does, yes.
Do we have Matplotlib and NumPy?
No, so let's go over here.
So we want to go over to our GetHub repo and we're going to activate our virtual environment And then we're going to pip install Matplotlib and NumPy.
Alright, super!
That seemed to work.
Let's see if we can run this now.
Success, number 71, execution 71.
Maybe we could add a cell above it that's marked down.
Like that, so we could say import and set up Matplotlib.
Then we could define our actual code maybe this will be Markdown.
All right and now what we're going to do is I'm going to put some Matplotlib code here.
I'm not going to type it out cause it's finicky and it's not particularly revealing.
So I'm just going to drop that there.
So what we're going to do it's going to go down here we're going to define the values which is, you remember lookin' up here the value we want the second element.
These are the numbers, the 23, the 21 the 20, and so on.
And then I want the bins.
These are the categories that these go into and this is going to be domain names Twitter and Google, and so on.
And then I need the indices that we're going to put them in.
These are like popular number 1, 2, 3, and so on.
And then we're going to say we want to do a bar graph with those values and then this is the number of referrals the title is site referral referred to by Python Bytes.
We're going to put the bins as the labels but we want to put them vertically 'cause if you put 'em horizontally 25 domain names on a little tiny screen they just override each other.
So we got to rotate them, so they go up and down and then we're going to just pick the right y-axis based on arranging these values and by groups of 50, NumPy is sweet!
Run that, so cool!
There it is, well what is the most popular site that as far as we're concerned on our podcast?
The one that I do with Brian Okken.
It is GitHub, and then Twitter and then YouTube, and up to us, barely in fourth place we have Python.org and then medium Reddit RealPython, and so on.
Pretty awesome, huh?
Hopefully as we went through this I took my time and I tried not to rush it 'cause I wanted you to get the feel of experiencing this type of environment.
Working through code like this and so on and we could even put more pictures and more results, and more summaries, right?
It's all marked down in images plus code.
I think this is pretty sweet right here.
Let's do one thing.
Let's just verify everything works because something funky about these notebooks is they almost have like a go to style.
Look at this, so this was the 10th thing run.
This is the 15th.
This down here is 26th and then back here was 27 and then down here who knows what the heck is down here.
Here's 67, 68, 69.
These are in order, but I could like run this again.
There's not a real order is there?
So let's go over here.
Let's just say run all cells so start at the top, works it's way over either still I got these numbers.
You could even go close this say do we want to save it?
Of course we really want to save that.
We could create a new notebook from here which would be pretty awesome but we're going to go and launch this one again.
We could say clear all outputs so it resets it, and then we say run all cells.
A little bit better, right?
At least we're running them in order here.
We've got to restart our kernel, I think to get it to completely rerun from scratch.
Maybe there's a better way to do it but for me I probably would just restart the kernel and rerun it, but this is awesome, right?
Look how cool this is!
This is so different than a regular application.
And remember if we want to go play with this we don't have to go re-download the stuff.
We don't have to reparse 2.5 megs of XML and HTML.
We don't have to do all that distinct stuff.
If I just want to make these a little skinnier I can make 'em skinnier.
If I want to make 'em wider.
I can make them wider and I can just focus on this little bit of exploring my data.
I'm going to put it back the way I liked it.
Here we go, that's pretty slick.
We can go through here and explore the data in this really unique and different way in these computational notebooks.
So this is JupyterLab and Jupyter Notebooks, and Python.
|
|
show
|
3:10 |
Now before we move on with a little bit more notebook exploration let's review the steps of getting started with clean machine and I'm going to get started and use JupyterLab.
So first of all we're going to create a virtual environment so, Python3 -m venv, venv.
Remember on Windows it might not have the 3.
It depends which version of Python you have installed.
Older ones you just have to have Python with the right path.
You should be used to that by now.
We're going to activate it on this computer 'cause it's POSIX, it's .
venv/bin/activate no .
on Windows, scripts instead of bin.
And then, while you're at it, they always start out with an out of date pip, so let's go ahead and just fix that so you don't get that warning and you get the latest greatest stuff.
Step two.
We're going to pip install JupyterLab, and let's go ahead and throw in some other stuff that we're going to need talk more about this in a second, in our demo side of things, but, of JupyterLab, scipy, numpy matplotlib, ipywidgets, and feedparser, you don't normally need feedparser, but that's what we're using.
So, we're going to run this command to install the dependencies JupyterLab plus what we need.
Now there's another step that you haven't seen that's necessary yet.
We're going to go look at another notebook that is much more interactive than this basic one that I built.
Because, honestly, I do more web development than Jupyter notebook type stuff, so I'm not that good at it.
I'm going to look at a really cool one in a little bit.
There's some cool interactive live stuff done in this browser session.
And for that to work, you need to have Node installed.
So here, just run version to make sure something comes back, rather than an error.
And then, with the virtual environment active we're going to run some extensions.
Enable the widget extension.
And then we're going to install this Lab extension here.
Okay, so if we do that you'll see, we'll get some more cool interactive stuff.
In order to really run it, and use our virtual environment correctly, our Jupyter notebooks could not locate the actual virtual environment even when Jupyter is running out of that virtual environment.
It's one thing to have Jupyter running.
It actually runs multiple environments and even multiple languages.
So it's not tied to where it's running from.
We need to register it.
So, we won't do the steps here to install ipykernel.
With the virtual environment active we run this command at the bottom.
We just give it some name that we can refer to and it gets everything set up.
After we rerun the Jupyter server, we now have our course venv as an option and that's how we get back to the right place for running our virtual environment with all of our dependencies.
So here's the link at the bottom.
You can follow along, do what they got there but we also did that in the demo.
Finally, it's easiest probably to run from the folder where you want to keep your notebooks.
But you can also have sub folders and so on.
Look where we want to run, virtual environment is active and we just type JuypterLab.
Boom, off it goes.
You notice it automatically starts the default browser and it also has these authenticated links which then log you in.
You can go further and set up accounts and stuff like that I think.
But this is good for local stuff at least.
And that's it.
This is how we set up JupyterLab on our system.
It's really cool, isn't it?
|
|
show
|
4:13 |
Well, I'm pretty happy with this little program that does the Python Bytes domain reference authority let's go ahead and save that and we can stop it from running.
I want to do something more interesting and more interactive, and there's some cool examples on the Jupyter notebook page so we're going to just grab one of those and put them up here.
So, because this is local I could just go to the trajectory and drop the files but if for some reason it's like a server type thing it will even let me upload stuff over there.
So let's put some files there.
I'll take this and this.
Those two files need to be uploaded so we're going to run this Lorenz Notebook.
And notice it has a lorenz.py in the notebook, you can have these Python helper files or libraries that then you reference in your notebook.
So that's what's going to happen here.
And notice we have the Lorenz differential equations and it let's us explore how these work.
So let's start by running this.
We have ipywidgets it looks like we have them installed that's cool.
And here, we can run this, nothing happens but if we double click it, notice this is, I believe this is Luteic here.
You define exactly like the right math and all that, like that's pretty awesome.
I used to live in that world, don't live there anymore.
Don't know how to do it, but it is pretty cool.
So we can come down here and import oh this is not so good.
We're just missing scipy here.
So, no problem.
Let's go and fix that.
We have the right environment so pip install scipy.
Cool!
Let's try to run this code again.
Here we go!
All right, that looks like it worked.
Aw, perfect.
Look at that graph!
It actually took it a second to compute it but that is so cool.
I love how that looks.
Now we can change these numbers and it will change the graph.
Like if I put a .1, or yeah a 10 instead of 50, notice how different that is.
We can put these back, good.
That picture there.
But it turns out that there's actually much more interactivity that we can get out of this.
Let's go over here, shut this down for a second.
Seven, apparently, is not going to shut it down.
And let's run those extension management things I showed you earlier.
We have Node, all right we have Node 12.12 installed.
I don't know if that's the latest but it's pretty close to the latest.
I'm going to have Jupyter, envi extension enabled the widgets, okay so that looks like it works.
The other thing we have to do is set up this lab extension here.
This one could take a moment.
Whew!
That took probably almost a minute.
But now we can just rerun JupyterLab.
Start running our way down these.
And check this out, now when we run it we get this cool interactivity.
Remember we were playing with the sigma but now what if I put it like that?
What if I do it like that?
What if I change beta when it's this?
And the row, right.
Row kind of scales it.
So we'll go like that, and you know the sigma is way too big.
It looks more good like that.
The beta is a little small.
Isn't that cool?
So now, not only can we set up these graphs and explore these datas, but we can put filtrine and adjusting of the variables and create these interactive solvers.
That is so cool, right?
I told you these were very different than the kind of applications you might build that just play with data as an app a command like thing that will just process some data.
No, these are, these are quite different all right?
And maybe this takes a while to compute and we can just play with it.
But down here we can go do a little bit more work.
These are the current arguments past to it from up here, right?
That's 340 right there 351 right there.
So we can then take what you do with the slider and then go do further work, all right.
So I can go, 'oh, let's go do some calculation on the shape,' and we can even import Marplot Lib again and then do some graphs.
Right, and the way it looks, probably has something to do with the little sliders we spun.
Here we go!
Really really cool.
Here's a much more interactive notebook at exploring the Lorenz differential equations.
Love it!
|
|
show
|
2:32 |
Now if you like the idea of these notebooks but you've become accustomed to PyCharm and you want to stay there, it turns out there's a really nice way to work with them in PyCharm.
Watch this.
We'll go over here, here's our domain authority notebook we had, we open it, and look at this it's just over here running, like so.
We have this split view.
We can view just the output like this.
We can go here, we can even rerun it.
As we'll see, you can see it in this kind of half, half mode.
I'll hid the project, 'cause really we're just going to focus on that here right?
And I can come over here and say I want to run, notice I can just write standard import statements.
But I'm going to run this, actually this is not necessary and apparently that is not either?
Yeah, it's not, 'cause we're actually using feedparser.
So look, it already gave me some help to say those imports were not used.
But let's run it and see what happens.
I just open and hit Alt + Enter and it's now starting a Jupyter Notebook right there for us.
Okay, we don't have to work with it or do anything, you can see the server log and how you get to it if you want but you don't even need to go to the browser.
That part ran, and we can come down here and we could just start making our way along run this, run our output notice right here get our Python bytes, and check this out.
Let's go to just this view.
We haven't done anything in the debugger in PyCharm I don't believe, but the debugger is killer and look at this, as you go through it you hit these breakpoints and stuff it actually shows the variables, and then their value.
And if the values change, these actually change color as non-select, this is not text that I can select it's just a visualization as I go through.
I could just run everything.
We'll go down, you can see all the data coming out.
Is that cool or what?
You see all the pieces, right?
You can already see what the values are there.
And then let's go back and look at the actual output there it is.
Looks a little weird in dark mode but nonetheless, I really like this way.
You can even upload it to their hosted cloud thing called Datalore, is what I think that is.
It gives you some help on all the things you can do insert new cells, but notice it's using this editor that we can just type through.
So if you prefer more of a code editor style you can do that right in here and just seamlessly move through, Ctrl + Enter still runs everything and so on.
So if you're a fan of PyCharm and you want to stay in it this is pretty cool.
Look, it's using the right environment it's running on the server, all kinds of cool stuff.
|
|
|
57:48 |
|
show
|
1:19 |
We've seen a lot of awesome things about the Python language a lot of awesome Python frameworks like Flask and others SQLAlchemy, for example that let us build amazing apps.
But our app still has a problem, yeah, this is a cool app and we saw how super fast it responds, how we can filter our guitars, and everything but, what's wrong with it?
That.
This is the problem with our demo app it's just running our local machine and while it runs great this is definitely not what we're aiming for.
We want to get this out on the internet.
We want to host it on a proper domain on a server for the entire world to enjoy.
Right?
That's what building web apps and mini applications is all about.
So in this chapter we're going to take the app that we built in sections 7 and 8 where we did the Flask development and the SQLAlchemy development, we're going to put that out on to the cloud somewhere.
We also have to choose some kind of hosting model what kind of hosting should we pick?
Should we pick platform as a service, maybe infrastructure as a service or even setting up on our own physical hardware.
We're going to talk a little bit about the trade offs there especially focusing on the Python side of things.
And then we're going to pick one and run with it.
|
|
show
|
1:49 |
Let's spend a minute and think of the different ways in which we could host our web app and even amongst the different places we might choose like Azure, or EC2, or something like AWS something like that.
There's actually a bunch of different options in there.
One option would be to go with Azure and we could pick several varieties in there we could go with a software as a service with just their web apps offering or we could get some VMs and run things there.
Over here in EC2, or AWS, we could fire some EC2 instances and do all sorts of cool things there but we're going to focus specifically on deploying straight to Linux.
So I guess that would be kind of the VM story over at Azure, but there's better options that than, trust me, just for the infrastructure side of things.
And I'll make a case for that.
We're going to use DigitalOcean, and we're going to deploy to Ubuntu.
Why are we going to do that?
Well put DigitalOcean to the side there for a minute why are we going to deploy directly to Linux?
Because if you were going to deploy to a platform as a service like Azure web apps or Heroku, or something along those lines you need to know a little bit about the underlying infrastructure and how to make that work.
And you might have to configure things and so on.
To understand all of it, we're going to do it directly on Linux, and it turns out to be actually not very hard at all, the most affordable and the most flexible option.
So the way I see it, if I give you the most powerful outcome, the most flexible and also by the way, the most affordable by a long shot way to deploy your app and you just decide, you know, I'd rather put it on Azure and just push the button to deploy there, or I'd rather just drop it into Heroku and do a gitpush Heroku for my deploy it's totally fine, and you'll understand what a few of the config settings you'll have to deal with will do, but you'll also understand probably what's going on underneath a lot better.
|
|
show
|
7:11 |
Let's talk a little bit about the benefits of deploying directly on Linux and the choices that we're making here.
I told you about some, the features how you're going to learn basically more if you do all the steps and then you can roll it back to use some platform as a service.
The way I see it there's, like, two really good getting started places.
We have DigitalOcean and we have Linode.
And these places are super affordable, super flexible.
They have tons of data centers.
They're really, really good options.
There are certain circumstances where deploying on Azure or deploying on EC2 makes more sense.
If your company's all in on those, fine.
Let your company foot the bill.
But if you care about the price and you care about the flexibility you probably want to come over here.
So we'll look at those options.
Linode's a great option.
We're not going to deploy to it just because I have more experience with DigitalOcean.
Another good option in addition to Azure and AWS is Heroku and it's very common in the Python world.
You can create one of these Heroku dynos which is basically a little virtual machine app hosted app thing.
It's not a dedicated virtual machine that you get but you can, like, run and scale it and so on.
Pretty cool.
It's super easy to get started with Heroku.
Like I said, if you want to get done let's see, we go over here.
So once you're in if you have a git repo you can just type, once you have the API command line interface installed just type Heroku create and that will set up another endpoint for your git repo.
And then to deploy you simply do a git push Heroku master and that does it.
And it does all this cool stuff.
You can see it actually gets all the bits gets it running, and then boom there it is off and deployed.
You can even scale it.
That's super.
So the challenge here with this is it's kind of expensive.
So you pay for this tradeoff, right?
If you want to run those, that sort of code here's a passable minimum web instance, right?
This one is $25.
This one is probably what you really need and the M&L change prices there, as well as those, right?
So these can get pretty pricey pretty quickly.
And then you don't directly get to put databases there.
You've got to, you know, like use the Postgres as a service which also adds on to features.
So you don't have a ton of control and you get sort of bought in deeply to kind of, like a lot of commitment over here.
A lot of people love Heroku.
I told you why I'm not particularly using it here, right?
You totally do what you want.
Let's look at DigitalOcean.
Okay, so DigitalOcean is great.
They let you create virtual machines.
They have awesome networking.
Lots of data centers throughout the world.
They have load balancers.
They also have Kubernetes and hosted Postgres and stuff if you want to use it but you don't have to.
But, so let's check this out.
Let's go to pricing real quick.
So let's go to compute real quick here.
So we have some nice options.
Here's some shared CPU but dedicated virtual machines that you can run into or you could get dedicated CPUs if you really want to go bigger.
So here, check this out.
We've got, like, two gigs with one CPU and two terabytes of traffic and a 50 gig SSD for $10 a month flat.
That's pretty interesting.
That's good.
Let's go over here, like, to the Azure calculator.
I'm going to choose Azure 'cause I'm guessing that a lot of folks who are .NET developers are familiar or considering or whatever something like that.
So let's say we want a virtual machine we're going to add that and we cannot forget about bandwidth because that's a super important consideration.
Let's go over here and say we're going to run Linux.
I don't really think it changes the price here.
Yeah, we'll go, I guess we could go with a low priority.
We'll go with that character right there.
I don't really know all the options that well but let's just say that this one it's pretty close to this option that I pulled over here.
It's not two gigs, but it's pretty close.
1.75.
Let's say that's good enough.
And this is the time for one month.
So it's $8 and 76 cents versus $10.
Oh, well, why don't we pick this?
Maybe this is a great option.
Well, let's go down to the bandwidth side of things.
Did you notice over here that this comes with two terabytes of free bandwidth?
Do you know how much two terabytes of bandwidth costs over here?
Well, let's put two in there.
Oh, 177 additional dollars.
So we go down to our total.
Our total is now $186.
When it looked like, oh this one is actually $150 more expensive than the other one.
No, it is not.
Not in practice.
I find, like, EC2 has the same type of pricing.
It seems great until, like you start to put all the pieces together and you're like, whoa this is super pricey.
And you might say, well, Michael who's going to need two terabytes?
That's ridiculous.
Well, let me just show you over here.
I pulled up the price on my system.
So right now I have eight servers doing a ton of stuff.
So running the Talk Python to Me podcast running the Python Bytes podcast running a whole bunch of little services behind the scenes and running the training websites and the only thing not accounted for in DigitalOcean is the video streaming.
That's completely separate, okay?
Completely separate.
So the video traffic doesn't count here but MP3s, HTML, XML all that kind of stuff shows up here.
And because I'm paying for those eight servers I have some high end ones.
We're paying $83 so far this month and it's mostly, it's close to the end of the month, okay?
So, say two thirds of the way through the month.
Now check this out.
If we scroll down a little bit oh yeah, here we are.
So, up to the 22nd.
We can break this out into the charges.
So droplets are 75 and I have, like, some backups and other stuff going on.
So it's 75 bucks.
But look at this.
Look at that number.
That's crazy.
Remember, this is not videos from the courses.
This is not videos.
Now, last month we used 14 terabytes of traffic without videos.
And how much did we pay?
A little bit less than $100.
Let's go back here and adjust this for, like, my situation 'cause this is the two you got for free.
Over here, we said actually we're not going with this minimum one.
I've got, like, this is probably running about that's probably what I'm running over there.
And I have eight of these.
So 140, which doesn't look that bad until you add in the bandwidth of 14 to 15.
And now what's our subtotal?
Subtotal is $1,455.
Last month at DigitalOcean, I paid under 100.
That is 14 times more to do the same over here.
And if I went to EC2, it's the same.
You know, slightly different but the pricing is almost the same.
That's why I recommend you try something like DigitalOcean or Linode or something like that if you're going to be doing a decent amount of traffic or they're just, they're really nice, as you'll see.
So I wanted to share some actual concrete experience.
I know other people may be having wonderful experiences with Azure and maybe you've had a bad experience with DigitalOcean or Linode or something like that and that's fine.
You know, take that into account.
But my experience is basically what I laid out here for you.
For that reason, we're going to go with DigitalOcean or something like Linode.
|
|
show
|
4:33 |
Whether or not you agree with my assessment just, let's follow along and get started over on DigitalOcean.
We're going to create a Droplet, and we can click here.
Droplet is like, their dyno or whatever It's their virtual machine.
Okay, so we're going to create a a Droplet.
We're going to do Kubernete's Clusters Data bases, volumes, DNS, etc.
We're just going to create this.
When we come over here and we can pick Ubuntu or a whole bunch of other options and down here and or even different categories.
We can pick later ones I'm just going to go with 18.04.
Latest LTS longterm support version.
I'm going to pick a standard plan no-no we don't really need that lets go for this one.
Honestly this is way more than enough, this $5 one for what we're doing here.
Normally I would put it in New York or somewhere on the East Coast.
Virginia, something like that, because it's a good trade off between the US market and the European market uh- the best you can do with one server I mean, you know, focusing on Australia sorry that doesn't help, I know.
It's probably the best we can do by picking any one of these spots but I'm going to pick San Francisco 'cause I'm on the West Coast right now.
All right so we'll turn on monitoring.
We're going to use SSH keys so we don't log in at all.
Uh we just well, we log in but we don't use a username and password; We just register to the SSH keys in our machine and then we're just, let in because we have them and without them you cannot get in.
So, I don't remember which ones I have on this profile so I'mma check them all off, that's a little overkill I'm going to delete this machine anyway.
Okay One droplet we'll call this "Guitary Web".
There we go, apparently that's a valid host name I can put some tags, I can put it in different groups I can have back-ups.
They recommend it, I don't want them.
Alright, so I think that should do it let's go ahead and create our Droplet...
And for this I'm going to leave it running real time I won't speed it up or anything.
You'll see about how long it takes to get this thing up and running and we can log into it.
Boom!
There it is.
Well I didn't have a stop watch but I'm...
estimating 25 seconds or something like that.
Let's go see our new machine!
Let's go pay it a visit.
It's...
excited to meet us, I'm sure.
Who over here at SSH at this.
Still turning on...
Let's give it a few more seconds.
Here we go...
They- Yes we want to trust it It's going to let us log in.
What is the very, very first thing we should do when we log into a computer?
Make sure it is up to date So apt, update, this is like checking for updates on Linux.
Talk more about the commands in a minute but you can tell that there is a lot of stuff going on here.
I think it's also checking for updates on itself.
Okay.
Apt upgrade.
There's a whole bunch of stuff going on and if you see this Linux image thing here this generally means there's a new release of Linux.
If we were to look, go back to the top what does it say we- Oh, ran out of space but we're not on this version of Linux.
This is some old so there's like a deep upgrade along with a bunch of other stuff.
So, let's go ahead and upgrade those.
Say yes.
Sometimes it will say it needs to update this particular file like Bootklub LST.
And I'm just going to leave it anytime it asks me this question I just accept the defaults, that's probably the safest.
All right, well, It's been updated so that's a good thing, let's log back out and then log back in here.
And It's very, very likely because it updated the Kernel, that we're going to have to do a reboot so we just type reboot.
Usually it takes about 10 seconds...
Try again...
Not quite, There we go.
All right, super so here is our machine and we can do all sorts of fun stuff with it.
We're ready to start setting up our web server and installing some monitoring tools and things like that.
Before we start running commands Let's jump back and look at the higher level architectural pictures and things like that.
This web server is up and running It's ready to have all of it's stuff installed and most importantly, it's patched.
So it's relatively secure, there's a few more things we'll do to make it more secure but it's got a good start.
|
|
show
|
3:29 |
Before we start configuring and installing our server let's just take a step back and look at all the moving parts.
So what we're going to do as we said is we're going to set up an Ubuntu machine.
That's the Ubuntu logo in the corner by the way.
We're going to set this up and manage it ourselves.
We talked about all of the different options and there's many things you can choose.
For this course I'm going to show you this because like I said, I think gives you the most flexibility.
So what software's involved and how does it fit together?
Well, there's actually two web servers so we're going to install this thing called, Nginx.
It's a free open-source web server and it is really really awesome.
However, it doesn't run Python Code.
It just handles web traffics, static files SSL, that kind of stuff.
For what we want to do we also need to run our Python code.
Obviously, that's the logic and implementation of our web app.
So, we're goin to use another server called uWSGI.
The U is like a MIU, a little greek, Miu so uWSGI.
Remember Web Service Gateway Interface.
This thing can host any WSGI Python application.
So Flask, Django, Pyramid, bunch of others.
We're going to do a couple of things to improve the scalability of our web app.
One, we could have a bigger server with more cores.
We could actually have multiple servers with load balancers.
All sorts of stuff.
But even on this one server here instead of just running one uWSGI we're going to run uWSGI whose job is to communicate with a bunch of little uWSGIs.
All these little guys.
This is actually where our Python is going to run.
We have all of these little worker processes.
They'll basically be running copies of our app.
Remember Python's GIL and how that can be a problem?
And how multi-processing solved it?
Well, here we go.
We have a whole bunch of parallels have added by doing this amongst other things.
Like, if one gets stuck locked up, runs out of memory we can use all the others.
So our requests are going to come into this world over ACDPS, it's going to talk to Nginx.
Nginx is what the world believes our web server is.
They talk to Nginx.
They never see or know about uWSGI at all.
They talk to Nginx.
That's that.
This is going to handle the SSL maybe even serve them back the static files.
It won't go to the Python Code for that.
If there's a data driven request it's going to go into our Python application it gets delegated over here maybe over HTTP or maybe even Linux Sockets.
It's going to figure out Well, which one of these is free?
Well, that one's free.
Let's let that one handle the request.
Another request comes in, it says Oh.
That one's free.
Another request comes in, it says Oh, this one!
This is the one that's free!
and they're going to be running potentially in parallel, processing the request.
So, this is the overall pictures.
What we have to do to make this work is we have to setup Nginx with the right configuration we have to setup uWSGI.
We have to get our code on the server and setup the server with the right dependencies.
We're actually going to do that in reverse order.
We're going to put the dependencies then put our code, then uWSGI.
Make sure that uWSGI working on local-host and then we're going to set up Nginx to talk to the world and let Nginx actually close that gap there and talk to uWSGI on local loop-back or over our socket.
Before you're done that folks this is not that different than IS.
IS handles all the static responses and so on.
We have the ASP.NET worker processes that run.
You can even set them up in this kind of Farm Mode that we're talking about here.
Though we have ISS and ASP.NET worker processes Nginx is like the ISS.
uWSGI is like the ASP.NET worker processes that would run your .NET C# code but of course, we're running Python code in this Python world.
|
|
show
|
6:48 |
Let me show you the code or maybe the configurations that we're going to be using for this chapter.
So over here we have a chapter 12 deployment.
And what we're actually going to deploy is this version or you could just as easily deploy this one over here.
They're basically the same as far as deployment goes 'cause we're not using a real database.
We don't have to set up one.
We're just using SQLite.
That'd be fine.
So over here we have these files that are basically going to configure our server to run this project.
We have this Nginx file.
This is the configuration file for Nginx.
If you come from the ISS world, you might run Internet Information Server Management I forget the exact name of it, UI, and configure and setup your apps through that.
But we don't do that in Linux.
There's a lot of command line stuff and a lot of config file stuff.
So we're going to set up this actually relatively simple config file here.
And this is going to control how our app runs in Nginx.
And we're going to set it up on gitary.talkPython.com.
They'll see at the very end why we need an actual proper domain, not just some made-up domain.
And this one sets up uWSGI here, you can see right there, uWSGI.
Now, before we can do anything interesting with those we need to do things like, well install Nginx and some other stuff.
So what we're going to do is we're going to go through a couple of steps here.
And I just want to take a moment and comment on these steps, workin' with Linux in this way.
At first, if you're brand new to Linux it's going to probably feel a little uncomfortable.
You're like, well, how do I know to type apt install ZSH and do I need to do this?
In this case, no.
But you can.
And why would you do it?
And so on.
It takes some getting used to it turns out to set up the server I manage it is actually incredibly simple there's not a whole lot going on.
But figuring that out for yourself is a lot of work.
Luckily, you can take somebody's existing setup and just adapt it a little bit.
So here we are, we're going to be setting up Nginx we're going to be setting up uWSGI you want to be using continuous deployment out of roughly use GitHub as deployment I guess we'll have to push a button to get it to deploy.
Nonetheless, something close to continuous deployment straight out of GitHub.
And the steps that we have here I'll explain 'em.
They take a lot of sort of poking around and research to learn them from scratch, from a blank canvas but once you have the outline laid out here, super simple.
And in fact, here's something cool that's really hard to do on Windows.
I could take this script and run it and then my server would be set up.
Actually, I have to change just a couple of settings 'cause I left them generic for you.
But if I was willing to, like personalize this for my server I could run this and then the server would be running.
That's pretty unique.
That's pretty awesome that I could just script the creation of the server completely.
So, there's a lot of advantages to Linux in addition to the fact that its cheaper hosting options that we've already talked about.
The dev op side of things is really interesting.
A lot of scripts, these are ones we're going to run here.
So remember, when we first got it, I said the first thing you want to do is do an update.
So apt is the package manager for Linux so we updated it and then we did an upgrade.
So update is check is for updates and upgrade is apply the updates.
So that's already done we don't have to worry about that.
You might have noticed over here on this computer that I have a cool shell that lets me do all sorts of, like, history and it lights up on Git and when it's on the Git Repo and so on.
That is through Oh My ZSH.
I might even set that up by the server this is totally optional but it just makes it so much nicer to work with.
I really dislike Bash so we're going to install the prerequisite which is Z Shell, and then we're going to install Oh My Z Shell like this so give that just a sec.
It asks if you want to do that and oh yes, Oh My Z Shell, things are better.
So we can exit out and then log back in 'cause we were running Z Shell within Bash but now the prompt changed, so that's cool so this is not required, but it makes me happy.
Now we need to install some stuff like Build Essentials and Git and whatnot so that we can better work with Python packages.
Of course, I could, like, run the entire script but I want to talk you through each step and what it does so, you know, you don't have to go and research it from scratch and it makes more sense this way, I hope.
So we need to set up Python3 pip and the dev tools and virtual environment.
Python on Linux is broken up into way more small pieces.
So this lets us put together the ones that we need for our project.
We can also install Nginx we're going to need that in a minute.
Divert, so it looked like it worked.
And then these here are so we can enable Gzip support in uWSGI.
Not sure they're always needed but last time I tried to set it up it wouldn't work until I installed these so, here we go.
Now that sets up our prerequisites on the app side.
Before we were only using certificates for logging in we're not using passwords.
But if people keep trying to login they can keep interacting with the SSH session so we can set up this thing Fail2ban which will actually ban them if they do too many failed login attempts so let's go ahead and do that.
Another thing we want to do is turn on the firewall.
So there's a really simple firewall that I'm going to do called UncomplicatedFirewall.
And we want to allow SSH traffic we could say this or we could even say SSH, they both work.
ACUP and ACUPS, so let's put those in here.
Right, those are rotated and we can actually turn on the firewall, it says, warning, your SSH is in here and if you did not put allow22, you're gone.
Luckily we did.
We can double check by logging out and logging back in.
Whew, we didn't break the server.
Do that right away, don't wait until it's all set up and doing important stuff.
The other thing is, we're going to need to login to Git so we can actually Git to remember our login and not ask us for our credentials, you know however many seconds that is.
It's like a month or something like that.
So we'll run that.
Run this, optional, but I find it super helpful.
If you need to do commit from the server back you have to tell Git your username and email or it won't work.
I'll let my email address.
A lot of times I use my Talk Python email but that's what I registered at GitHub so I think it maybe makes more sense to use this Gmail one.
And that's it, now we're ready to go get our code from GitHub because we have our credential cache and all that sort of stuff.
Create the project structure and then start setting up our app code.
Our server is basically ready to be webservered it has Nginx, it has the right prerequisites for Python and for running uWSGI, and some good, safe settings.
Yeah, we're in good shape.
|
|
show
|
7:27 |
Now you can use whatever file structure you want but here's something I like to use.
I'm going to create a /apps folder and allow the app to write logs and stuff like that.
Create a couple of log directories like this.
Let's just go run all of those over there.
We have tree, yes, we have tree.
We have this simple structure so far.
The rest of its going to be created by git itself.
So this is like so if you want to run this block we just did that.
Now, we're going to create a virtual environment for our web app.
Yes, we could just mess up Python on the server, right?
But let's have a dedicated isolated place for our Python code.
That way no matter what happens to the system, Python we're pretty much guaranteed to have everything running the way we want So now we have that environment.
Let's go and activate it.
Remember, pip and setuptools are always installed and they're always out of date.
Let's test.
Yup, they were out of date.
We had version nine.
Oh, guess what?
Version 19 is out, only 10 versions out of date.
I don't know what's wrong with that.
I really wish you would update itself.
Then I want to setuptool, httpie, which is a great it's like curl or wget, but it's much better for testing command line stuff.
This way let's check of what our web servers responding to that way we can like check uWSGI on local hosts without exposing it.
Then glances is like Task Manager, or Process Explorer or something like that, that lets us look at our server in a almost graphical way.
Right, a bunch of stuff got installed and now we can do things like run glances which will give us a really cool view.
And if we make it bigger here, it's big as I guess it can be a little progress bar up here and see the servers super efficient.
We've been working on it and it's been running for a while and it's even running this Python app and it still is only using 287 megs of memory including the operating system.
So yeah, thanks.
It's pretty awesome like that.
CPU levels super low and most of its just coming from glances.
That's cool.
Here's our failed band server.
Happens to be running on Python our monitoring agent from Digital Ocean and so on.
We don't have Nginx or uWSGI because we haven't set those up yet.
But you know, we're getting there.
Then for HTTP, we could do something like over here and do a request and notice we get color coded HTTP response.
Let me do that again and so I just go to the top.
You can get information about what the encoding is you know, headers and server like servers and Nginx.
No surprise, because Nginx is awesome.
Alight, so this will let us test our setup, good.
We have our virtual environment, and we have our logs but we don't have our code yet.
So we're going to get that from GitHub.
But before we do it, let's also install uWSGI.
Now, Nginx we did before but it is a system type of package installed with aptitude with apt.
But uWSGI is actually a Python thing we install with pip.
So I'm going to install that into our virtual environment that's why we waited until now for it.
Actually takes a second to build and install so we'll let it do its thing.
Super, so we have uWSGI, we'll even be able to type that is a command.
It's not going to run.
We haven't given it an app and so on.
But it looks like that's up and running.
Next, let's try to install our GitHub repo.
I'm going to notice I'm going to shorten this because this is a great long name, and that's annoying.
So we're going to put it into app repo.
Very first time I have to enter my information here.
Oh, it looks like I used the wrong name it's ended the organization not under my personal account.
There we go.
Now we do our tree command.
We can see that we have our app in our app repo.
If we ignore our virtual environment we can look a little bit further.
Decent stuff about the structure.
Okay, so we have our .NET version we have our Python version there's the code that we're going to try to run and here are the config files that are now copied to the server that we get to work with.
Perfect.
Now we're going to need to make sure our server has the Python requirements.
So let's go over here we go and run, you know and go to our Guitary and we can, I mean, no, it's not there sorry, it's back one.
There we go.
pip install-r requirements.
Now that's going to install a little bit extra because it's for the whole course.
Normally, it wouldn't work like that but whenever we're just going to let it run.
Super how we have a lot installed there.
We can check it out with pip List, it's all looking good.
It's time to get to our config files, so we're going to do those next, but what we're going to need to do is basically copy the config files to the location where uWSGI and Nginx can pick them up.
So it looks like our code is all set up.
I guess we could even try to run it couldn't wait, let's do that.
It's pretty cool, We can even come down here and arrow around.
Love, love, Oh my zshell.
All right, we come down here.
Try to run this.
You know this one, We didn't update it with that thing that path addition.
I believe we just did it right here.
Let's look.
Remember this?
We didn't do it in this one.
Let's update this one as well.
Let's see if this will work.
I think we could do it like so if we go to the terminal.
Yep, looks like it's going to work.
Okay, so if we check that in we come back over here and try our source, we got to get a git pull and then just run it.
Beautiful.
Now I can't interact with it in this terminal because it's already blocked running.
So let's just go back here and do an HTTP HTTP localhost Port 5000.
That's where it's running right there.
That did not work because we did not activate our virtual environment.
Now one option and be well, we need to activate it go over there and do that.
This is something I love to join my servers.
So I'm going to go and edit my startup script here and I'm going to do an alias not do an alias, I'll do a source.
So what's that going to do?
That means when we log back in, it's going to automatically activate our web portal environment.
This is so nice because you almost never want to change the system Python, you want to change your app Python.
So I do this in all my servers, it's really, really sweet.
Now, let's try this again HTTP host.
Oh, look at that jQuery and our Bootstrap our Talk Python Training.
All the magic.
Here's our Guitary.
So it looks like it's working.
You can even see the successful git.
So we've got our code running here, but we need to get it running, not in just our dev server but in uWSGI, production uWSGI server instead.
But we're making it pretty far it's running right?
|
|
show
|
7:29 |
We saw this admonishment here and you should run this in a production WSGI server instead.
Yes, yes we should.
We should also turn debug mode off, which we can also do.
So we're going to do this and that brings us over to a new set of files.
We've got this Nginx, we got this service file.
This one controls uWSGI and it's going to run as a system daemon in the background.
Think Windows service, auto start.
It's sometimes hard to see the errors and where they went.
They're going to be logged over to here, which is cool but what we want to do, is we want to just test this command without the log bit first.
So let's go.
It's resisting scrolling.
Now also, it says we want to use this WSGI app.
We haven't defined this yet so let me go ahead and put that in here.
Put a file called wsgi, you can call it whatever you want.
This is kind of a bit of a convention.
It will say from Guitary, not app.
Import app.
Now, the way that the production servers work is they don't just run your code.
So remember, the way we got it was that we ran our code and it called this function, which called main.
We can turn this to false.
Right, it ran this 'cause we ran this file so it's kind of the startup for that file.
It doesn't work that way.
It just defines these pieces and it imports app.
And then it uses that directly, okay.
Actually, then I guess this doesn't matter down here, does it?
If there's any additional setup, like let's look over here real quick.
Notice this one.
In main we're setting up the global INET and we're creating the tables and we're loading the data and we're calling run.
What we would really need to do, and let me make a function over here, method.
And I'll call this configure.
So, in this case what we call main I'm going to actually run main.
But if they don't do main we need to do something else.
So we can go in and say, else is the production case.
So you still got to do this configure bit.
Put it like that.
Here we go.
Okay, so just in case you run the other one you're going to run into problems running uWSGI without this.
But this world, we're not doing extra stuff.
We haven't gotten there yet, so there's not really anything we have to do.
This is good, but you would do something like possibly run the configure here, or just let it get called the way it was.
Let's go in add.
All right, here we go.
Here's our WSGI.
We should be able to just run this.
So, it's really good to test though.
Test this directly, 'cause it's annoying to go and always check the log file if something doesn't work.
It's much easier to run it, see if this works and then diagnose the other issues.
So, when you're doing this stuff I recommend small steps at a time, okay.
So, let's run this over here and see where we get.
Over in uWSGI, tell it it's using this virtual environment.
It's running in master mode with four subprocesses.
That's like the supervisor and then subprocess worker processes, each get two threads.
Let's then import 5,000.
Okay, think we're good.
Woohoo, it looks like it works.
Let's see, WSGI app added.
All right, it seems like it works.
Sometimes you'll see some errors come through but I don't see any this time.
Must have done it right.
Okay, let's try this again.
Here it is.
Look at that.
First time we got it working.
So, as we make requests here we could do slash guitars.
We can do a redirect.
No, just a 200.
Could do tar slash all.
That'd give us all of our guitars back.
Here you can see the electric wood grain.
So, it looks like it's working.
But we have uWSGI theoretically potentially, maybe working.
That's not quite enough though.
Remember, I just ran it, I ran this command.
I didn't set it up to start when the server turns on and stop when the server shuts down.
So, that's the next thing that we need to do is how are we going to set that up?
But of course, now if I try again error.
No connection.
So, to accomplish that what we need to do is copy this file and it looks like everything's good.
This is not right, this'd be a Guitary.
You never create this file from scratch.
You always get it from somewhere else.
For you guys you can get it from this course.
Okay, so what we want to do is we want to get this and we want to copy it.
Let me just go ahead and put the fixed description.
Not that it really matters but have it working right.
So, to make this do its magic we have to copy that file, which lives here, into etcetera.
Systemd, system Guitary service.
And then we can control it by saying, "Start Guitary" "Stop Guitary" and so on.
So, it would git pull.
Got our updated service and then we, whoops did not copy enough, did I?
Want to do this copy, fingers crossed.
No, got the path wrong, didn't I?
Didn't I, 'cause that is not the right name.
Here we go.
Let's try again with this new one.
Right, no output.
That means it works.
Hasn't quite been started yet, so we have to say, "Start" and see what that does.
Ooh, no errors, that's good.
We can say, "Status".
Alright, looks like it's running and that's cool.
We could run valances and see if it really is running.
Oh yeah, there it is, it's running.
And then we could do HTTP against it.
Yeah, look at that, it's running.
However, if we restart the server, it's going to go away.
That's not amazing.
So we have to say, "Enable" and this will tell it basically now it's an auto starting service.
It's going to start, based on the various signals that we put in the file.
So, over here you can see after.
It's just logged that target.
After the system log is available.
Yeah, this is up and running.
It's great and if it crashes it'll auto start itself and so on, so pretty excellent.
One other thing we can do is we can come over here.
Go look at that.
All right, there's this uWSGI log.
That's cool.
We can even say tail -f for follow -n 20.
This, if we go to the other.
Then we go make that request again.
Now you see, here's all the output.
Live, go in to log file.
So, this is great when you're working on it you can fire this up, put it off to the side and then as you interact with it exceptions will show up here.
Requests will show up here.
All that sort of good stuff.
Our systemd unit file this dot service file let us set uWSGI the worker process and the overseeing supervisor process for them to run and auto start when our system just boots up.
That looks like progress to me.
|
|
show
|
3:25 |
Well it's awesome that we got uWSGI working.
But uWSGI is not appropriate for serving public-facing requests.
I don't think it does SSL.
It doesn't handle static files very well.
So we want a proper public-facing web service web server and that's going to be Nginx.
We've already installed it but we could always just double check over here.
Says yep, it's there.
There's also this other random message we keep getting so I'm going to make that go away.
So what we're going to do is we're going to disable first I guess we could enable Nginx all together so that's going to start it up and let's just make sure it's good.
We can say service nginx restart.
And what was our IP address over here?
Let's go exit and I can come back.
Be easy to copy.
What happens if we request that URL?
Woo-Hoo, welcome to Nginx.
You have a proper web server.
Now go set it up.
So this is like welcome to your IS website, right.
This is just the empty not configured thing.
I don't want it to say that.
So I'm going to delete that setting for handling random arbitrary sites over here, like so.
Let's see if it still says that.
I'm actually not sure what it's going to say but, yeah, it's not happy.
It's not listening.
That's good cause we don't want you listening to that one.
Instead we want it to listen according to this.
So here's the Nginx configuration file.
Want to start out by listing it on just HTTP.
I'm going to listen to that domain but I believe it'll catch everything if you have just one empty domain set up.
We're going to map the static files so slash static is going to go to this folder, right.
This is the static folder of our web app we're trying to deploy and it's going to be cache for a year.
Otherwise, we're just going to pass the URI over to this thing which says we're going to just call basically pass it over to uWSGI and let it deal with it along with turning on GZip responses including uWSGI parameters so they can play nicer.
In order to make this work it's much like we had before.
We just copy that config file over to sites enabled guitary.nginx Let's try that.
Here we go.
service restart Now what do we get?
Ta-Daa.
It's beautiful.
That really makes me happy.
It looks good, right?
So we have it working, this is port 80 there's obviously an implicit 80 but the important thing to notice is that it's not port 5000.
In fact, if we try to go to port 5000 you can bet that that's going to time out because it's closed on the firewall we're not listening to it all that kind of stuff.
It didn't go there.
We can check out the guitars and man, this site is flyin.
Very, very quick.
If we go back over here and look you can see put that away and do some more clickin there you go.
You can see the requests comin in.
I was trying to get fav icon but you know, that's a losing battle.
We should probably put one in but.
Anyway, guitars aug guitars acoustic and so on.
What's the response time?
Zero.
Holy moly, zero milliseconds.
This one apparently was 16 milliseconds but this is some ridiculous performance.
Remember this is a $5 server this is not that big of a server.
All right, well it looks like Guitari's up and running on port 80.
This is actually great.
|
|
show
|
7:56 |
This is really nice, but I would like it to work on a different domain.
Now let's see, maybe, I did try to when we first set this up, register guitary over...
And it's still not working.
Going to take a little bit longer for that to resolve.
When it does, we're going to be able to set up, over here.
We're going to be able to use Let's Encrypt to install this now.
We do a few steps now and then we have to just pause in the background and wait.
So let's go over here.
We can add another app repository.
So when we want to install something it'll come from here.
Do you want to do this?
Yes, let's do this.
I think it already did it but we can just do an app update just to be safe, just to be sure.
What's here?
What can we do?
Upgrade.
Ah, some other stuff that got installed along the way didn't get updated.
Let's go ahead and do that.
Now we can install Python Certbot.
So this is the certificate bot for Let's Encrypt which will give us free semi-auto renewing auto renewing via a single-line command certificate for SSL and it's going to set it up so it manages Nginx and not Apache or whatever.
So there's a whole bunch of stuff that's going to get installed.
We'll let it do it.
All right, that looked like it worked.
Now I might've cached this on my machine.
Let's see what the server thinks.
Nope, server also doesn't know.
So I need to wait just a little bit longer for this to resolve, and then we'll be able to set up our certificate.
The reason is we're going to just run this one command and it's going to add SSL to our server and we won't have to do anything.
It's beautiful.
But in order for it to work it verifies the server making the request actually is the server that is the domain, all right?
The domain has an IP address mapping back to the server that's trying to set up the certificate.
If it's not the case, it's going to fail and say either, I don't know where this domain is, or, You know what?
You don't own Google.com so you don't get to to set it up.
All right?
Play with, like, a safeguard and make sure they at least may have some sort of or some reason to own that certificate, alright in lieu of buying it and verifying it and so on.
Now I'm going to pause the video and come back when that resolves.
Alright, I think this is going to work.
It depends on whether the domain name definitions have made it far enough over to the Certbot world.
But let's go ahead and give it a try and see what we got.
All we have to do is say we're going to run Certbot against our Nginx configuration.
The Nginx config that supports that URL or that domain name right there.
That's what we set up here.
Like that.
And if the DNS settings have made it far enough they should work.
Let's give it a try.
First of all, it says You have to enter your email address.
And I'm going to put demo@talkpython.fm.
Do we agree to the terms of service?
Yes, yes, we do.
Will I be willing to share that?
No, I have already shared my real email address with EFF.
I don't need more copies of their newsletter.
Thank you.
Look at that.
It went and it verified guitary.talkPython.com.
It set up the certificate and it has one final question for us and this one's kind of important.
If someone goes to HTTPS://guitary.talkPython.com it's already going to serve them SSL traffic HTTPS traffic.
However, if they just type in the domain guitary.talkPython.com without the HTTPS it's going to try HTTP.
This question says, Would you like to automatically upgrade that request over to HTTPS?
I think the answer is almost always yes too?
But maybe you don't think about your situation.
I've never not wanted that to happen.
That seems really weird.
Obviously if they just type the domain name you want it to go to SSL.
And it says, Boom!
Congratulations!
You're certified.
This is going to expire in 90 days so you have to basically run something which is certbot renew and it'll actually go and look and see if anything's due.
It said, No, no, no, this one.
This one here is not.
So everything's good.
But you need to make sure you run that once near the end of 90 days.
You know, the last 30 days or so.
All right, let's see how this works.
Let's see if we can get there.
We can say HTTP against that and let's see what happens.
Sad, the local machine has cached or has not yet received the definition.
We have to wait a little bit longer to test this.
I'll pause once again for the DNS settings to make it over to digitalization over to the server.
But when they do, we'll be all up and running.
All right, well, actually, it just took a couple more minutes for that to resolve.
And we're all good.
So now let's try this...
A request to just HTTP guitary.
Let's see what we get.
We get a 301 moved permanently.
Where did it move to?
Location, HTTPS.
Alright, so we try again.
HTTPS, port 443 hitting Nginx.
What's going to happen?
Magic.
Magic happens.
All right, let's go over here.
Here we have our IP address.
I don't know if this will auto-redirect or if it will 404.
Let's see.
Yeah, doesn't find, didn't love it.
But if we just go to guitary.talkPython.com it's going to hit that 301 redirect and then it'll got to HTTPS.
Bam, just like that, we're online!
Official, we are official.
We have our certificate and we have our certificate verified or provided by Let's Encrypt.
And now we can just click around a little bit more.
Before we do though, let's have a tiny bit of fun.
Turn on our tail.
There we go.
And maybe make a little room like this a little responsive stuff.
Let's go over here, check out the guitars.
You can see it flipping through our electric guitars, our acoustic guitars all of the guitars.
You can see we're doing all those requests.
First time we hit this, it was a little bit slow.
But if we just click this a bunch of times...
Notice one and zero milliseconds.
Can't ask for much more than that.
This is really, really cool.
So here we have officially deployed our website to a real domain using HTTPS running over on the server.
And last thing, just to make sure you want quick other tests.
Just restart this thing to absolutely verify.
Yes, everything autostarts, right?
It wasn't just a fluke that we started that service and oh, we forgot to enable it or Nginx doesn't autostart or something like that.
Try again.
Takes a second.
Here we go.
Back.
All right, try one more time.
Does it still work?
You bet it still works.
Because it's awesome!
Okay, so cool!
So here we have our app up and running and yeah, everything is working really well.
Guess what we could check really quick if we want?
Response times over here.
We're getting 36 milliseconds, 39 38, 29.
Yeah, that's pretty good with the ping time over to there.
Let's see.
Yeah, happy enough.
Well, that's it.
We saw that we could use this simple 85-line setup script and these two config files obviously adapted for what you're trying to deploy to set up our server, to run our code our Python code, with Nginx talking to uWSGI running our code exactly like we want it over there.
Now we can do whatever we want.
We have this whole server, anything we need or we want to configure it to do.
Well, a few more lines in this file right here, isn't it?
|
|
show
|
2:57 |
Let's review our uWSGI configuration and how we got uWSGI to run under systemd as a systemdaemon.
So what we did is we created this thing called a systemd unit file and it has this certain format that you can see various things you can set here and so on.
I've highlighted the really important parts.
So we have ExecStart.
This is the command that runs this daemon.
So what we're going to do is we're going to run uWSGI out of our virtual environment and we're going to tell it your home is the virtual environment you come from, so.
Also, go back and just /apps/venv.
Run in master mode with four sub processes four worker processes, that it's going to coordinate and each of those get two threads.
It's going to listen on HTTP on port 5000 as opposed to, say, Linux sockets.
Going to manage the script name.
We're going to add to the Python path.
We actually did that explicitly in our code.
Remember that that imports this in OS and we did that little line at the top.
We could've technically avoided that if we only ran it this way, rather than trying to run it directly in Python.
I kind of like it.
That it's super easy to run just in Python all over but this also fulfills that role so technically, we could avoid it but it seems like not a bad idea to throw in, so I did.
Then we have to say, where is the Flask app that we're going to call run-on, located?
We created a wsgi.py.
File an imported app into it so this mount here is saying, go to that file WSGI.py, the module and then, look for a thing called app and run that.
Then finally, we just said, hey login this directory, right?
And that's the log output.
You want to make sure that this process has permissions to write to that log.
One quick note, this whole ExecStart is just one line.
It just happens that it won't fit on one line.
It's really, really long.
There's no scroll bar.
Guess I could put one in PowerPoint but it doesn't seem like a good idea so just be aware that it's just one huge and long line with wrapping.
And we also put the runtime directory for where our web app is located same as the Python path addition there.
That's it.
And the way you work with these, basically, is you get one, you copy it over, you tweak it make minor tweaks to it, so it'll run and then, you're good.
Now, also remember, when you're doing this infrastructure stuff, little, tiny baby steps.
Don't do it all and then, try to test it.
So for example, I didn't just try to run this.
I copied the ExecStart without the log parts so I could see what was happening.
Then, I tried to run that to see if that was going to work.
If something goes wrong it's much easier to see what's happening by trying to run that directly rather than, well, we're running systemd then we're enabling the service, and it's starting.
Then, it's logging over there, but maybe it won't log there if it doesn't have permissions to log there.
And there's just like a bunch of other layers you want to verify, so baby steps.
Try each thing along the way.
Finally, when we were convinced it was going to work we copied this file over to etcetera, systemd system guitar a.service.
Them we issue the commands the systemd to say enable the service that created this SymLink so that it'll autostart when we reboot the system.
That was it.
We had our Python code running the server.
|
|
show
|
3:25 |
Once we had uWSGI working we wanted to serve that traffic to the broader internet.
And to do that, we brought in Nginx and Nginx has config files that looks like this.
We set up a server.
We say the server's going to listen on Port 80 and it's going to listen in this case, we're saying guitary.com.
I didn't want to go and buy a separate domain so I said guitary.talkPython.com because that's easy to amend just an existing domain DNS record and get things to work.
And you put your domain name here and you have many.
You could have like www.guitary.com and guitary.com.
You could set up a separate little server block for www and redirect over to the bare one or vice-versa.
There's a lot of flexibility we're not going into here but Nginx is really awesome and it's easy, easy to do.
Put a few other factors in here.
Server Tokens Off means don't tell everyone who wants to know all the details about the version of your server and stuff.
I wish you could turn it off or make it lie say some other random server but I haven't figured out how to do that yet, haven't really tried but I don't see an obvious way to do it.
Anyway, this turns off some of the versioning info in the response of every request which is probably a good idea.
Then we're going to set up a couple of other blocks for URL.
So if you go to anything slash static slash whatever we're going to just have Nginx directly serve files out of the static folder which is /apps/app_repo/apps/py/ch07_web I think that should be a seven, not a Y so ch7_web, guitary/static.
And we're going to say this stuff is cached for 365 days or for a year.
So this is the first part.
You see there's a little ...
We'll get to that in a second but when we get this file ready to make it active so Nginx will work with it we have to copy it to /etc/Nginx/sites-enabled and then just its name: guitary.nginx.
Proceeding on with the dot, dot, dot once we get through the static stuff we say, Hey, if it's not /static just do a request against our application here, which is going to say proxy pass over to local host Port 5000.
This is, Look, if it's not static pass the request over to uWSGI and let it handle it.
You can see that we can set some other headers.
We can set Gzip which is a really nice thing for performance.
We're also setting uWSGI buffers and uWSGI params, things like that.
So this is it.
We basically configure Nginx to talk on Port 80 handle all the files but delegate the data driven requests over to our application, like this.
Finally, we wanted to add not just Port 80 but Port 443 with SSL.
You could completely do that here indirectly in your Nginx file.
If you go and get an SSL certificate you put it on your machine you can put the right entry in here to set up SSL.
But we saw that it's much easier to just install certbot from Lets Encrypt and then run the right command to have it reconfigure this existing domain and it'll do all of the rewrites of this file that you need over not the original, probably, that you copied from but etc/Nginx/sites-enabled/guitary.nginx will be rewritten with all the stuff that it needs to do basically SSL traffic over that lets-encrypt certificate that gets auto-installed.
And that's it.
We have our website all set up and running.
It's our own Linux machine.
We can do whatever we want with it at this point.
|
|
|
17:22 |
|
show
|
0:29 |
Hey, look where we are.
We're at the finish line.
You've made it, you now possess the superpowers of the most popular programming language the world.
That's awesome.
Hopefully, at this point, you appreciate Python's uniqueness and also the ways that it's quite similar and comfortable for C# and .NET developers.
So what we're going to do in this final chapter is spend one graphic on each chapter and just summarize what you've learned.
|
|
show
|
4:01 |
The first technical topic that we covered was the Python language and how it is similar and how it is different than C#.
We talked about the language structure, and C# my description here, the language structure is defined by curly braces that define blocks semi colons that define end of line.
Python, as you know by now, has neither of those which is honestly a little bit crazy.
It's also useful once you get used to it.
With the way we define language structure in Python is we have code indented, spaces, blocks of four spaces four, eight, twelve, and so on and then those blocks are begun by having some statement colon.
Like while true colon, indent, and then there's the loop.
Speaking of loops, Python has two basic kinds of loops.
While loops, and for in loops those are the equivalent of C#s or each loops.
Prominently missing here is the for i=0, i
Python has good support for functions that are first class functions.
We can even define generators with the yield keyword and C# it's yield return in Python it's just yield.
We also have a ternary expression in Python these are more English like, remember, small if number is less than 100.
Else, huge.
We can pass around functions that can be called and C# we create we do this by creating a delegate that matches the structure and defines basically the type of function you're passing around by the arguments that go in and the arguments that are returned by, those that do not come out.
In Python we don't define delegates exactly like that we can just take the function name and pass it around.
Sometimes, we want to just define little functions.
In C# we have lambda expressions.
They're super awesome!
In Python, we have lambda expressions, they're super awesome.
Instead of having those arrows, arguments goes to you know, equals greater than expression, which is the C# way, in Python we have the word lambda arguments, colon, expression return by.
We also have closures in both languages where we're capturing ambient local values maybe a function within a function, we're capturing that intermediate value there that is odd, kind of amazing, it gets hung onto, it's kept as persistent ambient information for that function over time.
Remember our counter object.
We can add types called type annotations or type hints in Python.
It really makes it a lot nicer I think especially with the right tooling like PyCharm or VS Code.
To see those and go Aha, here's the auto complete list for one of those, I didn't realize that's what it was but now that you tell me I can give you a lot more help.
We also have runtime type information in Python.
Everything has a type, everything is an object just like C#, it's more the language, the static type system that code writing and compilation time that is the big difference here.
Error handling is actually super similar between both try catch finally C# try accept finally in Python.
You throw exceptions in C# you raise exceptions in Python.
We saw that using blocks were great for discreet blocks of code where something is true, a file is open but then it is closed.
Database connection is open but then it is rolled back if there's an error.
With Python we have context managers in the width block, same basic idea.
And finally, I threw in switch.
Python doesn't have switch, but I made a switch and I like it a lot so I think it's a cool addition to the language and it actually doesn't take any changes to the language, it just uses those with blocks that I told you about just now.
All right, whew, remember this was a long chapter and it's a bit of a long review.
Just to go over this cause there was a lot there.
One of the main takeaways I hope is there's so many cool features of the C# language then you put it side by side with Python, you're like like wow, Python has actually almost every single one of those features in very very comparable or comfortable ways.
I think Python's a beautiful language and hopefully this comparison gave you a sense of that beauty.
|
|
show
|
1:37 |
Next up on our Pythonic journey were classes, and objects and object oriented programming.
And you saw that Python has a rich, proper object orientated programming model.
We have private data, we have public data, we have methods we have abstract methods, we have properties.
It's not as full featured as C#'s object orientated model.
For example, we can't reasonably have protected values without conventions.
We do have these private values here but protected is more of a suggestion.
In Python, you also don't have, for example virtual versus non virtual methods and you can override the virtual ones but not the non virtual ones without a warning.
Right, so there are some things that are not there but it's most of the important stuff you're going to use day to day, it's there.
So here we're going to define a car class that's abstract so it derives from ABC, abstract base class remember how we imported that.
One of the bigger differences is the way we define fields is in the constructor or in the dunder init initializer method here.
So we say self dot model name equals model name and that actually defines the field as an instance variable on this type.
Right, in C# this would be outside of the constructor you would define private field such and such or public field such an such.
Once you get used to this, though, it's totally fine it's just different.
We also have a regular function we have, which is drive.
We have a property, which is electric so that's a computer property.
And we have abstract methods as well and that's our refuel.
You want to drive from car class and be implemented or instantiated you've got to implement refuel.
|
|
show
|
1:09 |
The next thing that we talked about were all the incredible external packages that we could use.
.NET has NuGet, Python has PyPI.
Right now at the time of this recording there's over 200,000 packages on PyPI and we were just shy of that when we took this screen shot and put this presentation together.
But we're well past that now well into the 200,000s.
There's so many amazing things, and we actually worked with a bunch of them.
We worked with Beautiful Soup, with Requests with httpx, with Jupyter, JupyterLab, NumPy Matplotlib, I could go on, and on, and on.
We also talked about awesome Python which is a place that aggregates some of the better libraries out there.
We install everything from here via PIP pipinstall httpx, for example.
Make sure you run that with an activated virtual environment, or it's going into your system Python, not your project Python.
Finally, note on pronunciation, remember this is pie PI, not pie pie, that's actually a JIT compiling runtime for Python that's an alternative to CPython.
Pie PI, PyPI is awesome, it is one of the things that truly makes Python stand out and above a lot of other languages.
|
|
show
|
1:23 |
We saw how Python and .NET varied in their memory management story.
.NET is a mark and sweep generational garbage collector.
Python primarily, for almost everything it does uses deterministic reference counting.
There's no real direct memory management in Python just like in C#.
However, there are no value types and reference types divide.
It's all reference types, even numbers.
Those are all allocated on the heap and memory management is mostly reference counting unless you run into a cycle.
We saw that that's the great Achilles' heel of reference counting.
For that, we're going to have a garbage collector that can come up and pick up the leftover cycles for us just in case.
We also saw the memory clean-up is deterministic.
You can say it's exactly happening here not when the garbage collector decides to run and eventually the background thread gets around to it.
No, it runs right as the last reference is dropped as long as it's not a cycle.
Unlike a mark and sweep compacting garbage collector memory is not compacted but fragmentation is somewhat handled through constructs called blocks, pools, and arenas.
Give you a couple articles if you want to dig in deeper.
You generally don't need to know a whole lot more than that to be efficient with Python but it is important to understand a little bit of what's happening behind the scenes because you're not explicitly doing any of this which is great.
But knowing how it works always good to know.
|
|
show
|
1:21 |
The next topic we covered was building web applications with Python and we saw that Python was an amazing platform for the web and tons of cool apps are built with it.
We decided to choose Flask because it's familiar.
I've somewhat liked the ASP.NET MVC and it's also really popular.
So, in order to do that, create a Flask web app we're going to import Flask and create a Flask instance, based on the name of the file.
We're going to have some main method, which runs it could say, debugger is false debugger is true, take your pick.
We saw also in production that that main method doesn't get run so you can really kind of just do you dev stuff there then if we want to have, method a URL route we're going to say, we're going to define a method that is guitar list.
The name doesn't really matter but it's going to matter to us I guess, right?
This is where we list our guitars so we're going to add an app.route we're going to say /guitars/variable named style.
And then notice how style is passed in guitar list and then we just do our own implementation we go to the guitar service we say give us all the guitars by that style and then we render a template guitars.html which is a Jinja2 template which is a dynamic way to write HTML and then put a little bit of Python logic in there to turn our Python data, into HTML.
That's it, besides the HTML template really the whole app right there
|
|
show
|
1:19 |
What would the Web be without databases?
So, of course, we talked about ORMs and then Python.
Probably the most popular ORM is SQLAlchemy.
We also have Django ORM, but as we discussed it's tied specifically to Django.
SQLAlchemy works anywhere.
On the web, in Flask, Pyramid, or just in a standalone app.
So here's the basic thing we're going to do.
We're going to create classes that derive from a SQLAlchemy base type that we dynamically create.
And these classes are going to map over to database tables.
We set up the columns, ID name, image style and price.
And we set the column type, like an integer or a string.
For the first one we say is a auto-incrementing primary key.
That all happens automatically by just putting that here.
And then we also have the style and the price set with an index so that we can query them, query by them or sort by them quick and easy.
Once we have these set up we can go and just do queries with it.
We create this unit of work called a session.
Say session query of the type, filter.
And then we just use the static version of these values.
guitar.style == style.
And then get us all of 'em, boom!
That's it, that's the query where we go to the database and then turn all of the whatever.
Let's say the style's electric, all of the electric guitars into instances of our guitar class, all populated.
Works like a charm.
|
|
show
|
1:24 |
We all like software that works and one of the great ways to do that, verify that it's going to work, and keep it working, is to write unit tests.
We saw Python has a built in testing module named unittest although we didn't do much with it because we said, pytest.
That's probably where they action is these days so let's write some tests with pytest.
The way it works is we just define some methods test_ whatever you want to call it here we said test electric guitars.
We also used two fixtures, and these are ways to inject data or functionality into our test method.
Really, really cool.
We just have to create those in a separate location and put the @pytest fixture decorator on it.
Then we say the name, here we have guitar data and this is our generated test data and we have mocker, this is our mock object library.
We had two functions to remove the dependencies from our tests.
Get guitars from the db that was our database one.
And that one returns our test guitar data.
And our logging was another dependency but we didn't really care what it did so we just said, autospec = True.
Just go away log, go away.
And then from then on we're able to just do our regular testing and recall that pytest overrides or takes control over Python's regular assert statement.
So you just say assert, and then some truthy thing that you might want to test for, like all the guitar styles are electric.
Boom.
That's our test.
|
|
show
|
1:26 |
One of the most amazing features of C# is it's async and await framework in the task parallel library.
And guess what?
Python has it too!
Over here we're going to create a GET HTML function and it's going to go over to my podcast grab a episode number, use that as the URL and then download that page use a little bit of screen scraping after this to figure out what the title is.
That's the demo that we did, anyway.
So we want this method to be able to run alongside other methods, right?
Really, all you're doing is waiting on the internet, waiting on the Python Server.
Why should your code go slow and do that one at a time?
You can send a bunch of those off.
So, here we're going to asynchronously define a method get_HTML, and then we can use async and await in the context.
In C# you just have await in the body of the method but here we have async context managers.
Alright, so we have async with httpx.AasyncClient() as client and the async part there make sure that we asynchronously close any pending network connections with a keep alive or something like that.
Then we're going to call client.get.
That returns to coroutine that when we await it is going to return a web response and then we're just going to work with that from there.
That's the way we build async methods in Python.
Remember you have to manually manage the loop and all of that stuff, it's a little bit cumbersome so consider using the unsync library to even more like C#s task per the library.
|
|
show
|
1:14 |
As C# developers, you probably write factor applications, data access layers UI layers, things like that.
Compilation, lots of files however there's a whole bunch of people whose job is to not create applications but to create computational outcomes that they don't really know where they're going.
Do a little bit of work, explore some data try it a different way, visualize it do some more slicing, and so on.
And if you work that way, like a lot of scientists do Computation Notebooks might be exactly the thing for you.
We created a really cool notebook that went to the Python Bytes Podcast, pulled down that huge RSS feed and did a bunch of processing on it.
First we said, whoa, what hyperlinks are there?
And then we actually took those hyperlinks and converted them to domains, we used the Counter Class to turn those in to sorted buckets of number of references and then we could use Matplotlib to view that.
And that all happened within this notebook and Jupyter and JupyterLab.
Really a cool way to present and explore data if you don't really know where you're going.
Also, if parts of it are very expensive to compute and remember, these cells run independently and they just remember the value of the previous step so it lets you quickly iterate even though it might be computational and do all the work top to bottom.
|
|
show
|
1:01 |
We created our web app we hooked it up to a database but web apps are super not fun if no one uses them.
You have to put them on the Internet.
So we talked about different ways in which we could host our web application.
Get our own hardware post it on some kind of platform as a service like Azure or Heroku.
But what we ended up doing for our demo is actually setting up from scratch a bare Linux machine to be a proper web server so it has an Nginx front-end web server This handles HTTPS, static files and then delegates over to uWSGI for the Python code execution.
And uWSGI itself is running in this master mode which means that we have a bunch of different processes that it is over seeing and that's where our Python code actually runs.
It's not entirely obvious how to do this from scratch if you've never done it before but once you have one of those scripts or couple scripts and a couple of configuration files to work from you'll see this is actually really easy to run and maintain Linux servers, just like this.
|
|
show
|
0:23 |
One final reminder just in case you didn't do it at the beginning.
Go over and star and fork the GitHub repository right here.
This will have all the C# code that we started from.
It'll have all the Python code you saw created during this course.
Sometimes people even post issues and we talk about problems that either exist or don't exist with the materials and we post updates and things like that there.
|
|
show
|
0:35 |
Finally at the end of this class I want to say thank you, thank you, thank you for taking it, I really appreciate it.
It was great to have this opportunity to share Python with you and to connect as a C# developer who made this journey from C# over to Python and open source, a fairly different world than standard C# world and I really, really enjoy it.
I couldn't be having more fun as a developer these days and I hope that you saw some of that joy and some of the reasons why, and I hope this course helped you make that transition.
Have a great time, get out there and write some Python code.
|