|
|
29:08 |
|
show
|
6:20 |
Hello and welcome to my course Python Jumpstart By Building Ten Applications.
Whether you are entirely new to Python, or you have had some experience with it, but you would like to understand the language better, I think you'll have a lot of fun building these ten applications with me and learning the core language concepts along the way.
I've carefully chosen these ten applications to make them small enough that you can focus on the essential language constructs while you are building the application, and there won't be too much extra stuff to distract you, but hopefully they are big enough and complex enough that they are interesting to you, and you'll see we start out quite simple, but as we build up they get more and more complex, more and more realistic, I would say.
Welcome to the course, we are going to have a lot of fun!
Let's talk about what we are going to build.
We are going to start at "Hello World", and this in not just to test that you can write "print('Hello world!')", that would be kind of silly, we actually do just a little more than that, but we are also going to make the focus of this app to make sure that your setup is right, that you've got Python installed, that you are using the right version, it's in your path, all the various things you need to make sure your environment is working, your editor configured correctly, all those kind of things.
Then, we are going to get to our first what you might think of as real app.
Guess that number game, and the computer is going to randomly choose a number and it's going to ask the user, "hey, what number do you think I guessed" and it will sort of say, "too high/ too low," you may know this game.
So that will let us work with things like user input, converting strings to numbers, conditional statements, booleans, all those foundational language constructs.
Then, the next one we are going to focus on is a birthday countdown application, and this will let us really explore dates, time, differences between dates, the various things like that.
Once you have finished with this app you will be very comfortable working with dates in the Python language, and the idea here is the user is going to enter their birthday, we'll then use that information to figure out how many days their birthday is in the future of that year, or how many days it's already been since it's gone by.
The fourth app we are going to build is a personal journal or diary.
This app is fairly complex, we are going to look at a lot of really useful and central features to the language.
We are going to focus mostly on file I/O, but also partitioning our app into many different files and reusing those files to build up a more complex application.
We are going to dig into functions, and a lot more.
So, these first four apps will just run on our computer, and that's great, but we live in a connected world, so this fifth application is going to be a weather client.
We are going to actually go out to the Internet and get the weather wherever you happen to be located.
You will enter your zip code and out will pop a weather report, a real, live weather report that is up to date to the minute.
Now we are going to use a lot of cool language features and packages to build this one, so we are going to focus specifically on how you go to the Python package index often referred to as PyPi, and grab these packages, install them locally and use them to construct something that will let us get our weather report.
We are going to do some HTTP request, we are going to do some screen scraping, HTML parsing, things like that.
At this point, we are going to need a little laugh.
We've built these five apps and we've worked with the Internet, but we've only gotten text back, so this LOL Cat Factory is going to let us dig into the concept of binary files, so we'll combine the fact that we are going to go to the Internet, grab some binary data, in this case very important LOL Cat pictures, save those to binary files on the disk, and then explore them with Python.
Have you ever played DnD or some of these types of games?
Well, it's time for a wizard battle, so we are going to make a game where we have things like wizards, and dragons and so on, and this is kind of a text roll playing game, we'll create a wizard and the wizard will be part of this ecosystem where he battles different creatures and so on.
Now, for this, we are going to really dig into the concept of classes, object oriented programming, inheritance, doc typing, all of the OO type building blocks that really make Python a powerful language.
Number eight, we are going to build this file searcher so you can enter some text, and it will go through your system, your hard drive, look at all the different files, pull out the various pieces of text and give you little report on it.
Number nine, we are going to build a real estate investor application.
We are going to start with CSV file full of real estate transactions data, historical data for particular location, and then we are going to write an application that will let you answer questions about it, what is the average price of a house that has two bedrooms sold in this location, what is the most valuable house ever sold that had one bathroom and one bedroom.
Those types of things.
So we are going to work with this file format CSV which most of you should be very familiar with, but the idea will be easily translatable to other formats as well of course, and we are going to really focus on two Pythonic concepts here, two language features in the Python language that are fairly unique to Python, that is generator expressions and list comprehensions.
Either way is to do almost database like query work in your procedural code, so it's super powerful, super interesting for answering the types of questions this app might ask.
Last but not least, we are going to have a movie lookup app.
So, in this application, it's going to go out to the Internet again, and give you information about movies, but the thing we are actually going to focus on is not so much the Internet part, we did that in five and six already, we are going to focus on errors, exceptions, error handling, things like that.
What if you can't get to the Internet, what if the site is down, what if it returns data you don't expect, how do you write an application that fails gracefully or maybe it doesn't fail at all?
So, that's what we are going to focus on, these are our ten applications.
If you are brand new to Python I recommend you go in order.
If you kind of know what you are doing, if you look at the second one and go, "I know all these things," feel free to skip around.
But, I would encourage you to go and look through the list of the core concepts, each chapter, each application has a set of core concepts that are list out, and if you don't know those, be sure to go back and look at that first, because they kind of build on each other, even though they are somewhat independent.
|
|
show
|
3:22 |
What fun would it be to build these applications if you don't build them yourself?
So, it's really important that you guys try to build these apps yourself.
There are two ways you can do it, you can either, go to each chapter and see the description of the app, and then try to build it, then watch the lesson, that is maybe an advanced scenario, more likely you watch me do it, we talk about it and then, you put the video aside, you go and you start working on yourself.
Either way, you are going to need a Git hold of the source code.
So the source code is in a public Git repository, it's github.com/mikeckennedy/Python-jumpstart-course-demos.
So let's go over there and have a look.
Here is the repository, you can just simply download it as a zip, or if you are into Git like me, feel free to clone it, you can install GitHub desktop and clone it that way, you can clone it from the command line copying this here.
So the most important part you'll find are the apps.
So if you go into the apps you will see there is 0, 1, 2, 3 and in the setup one, we just have some links, stuff to help you get setup, those will be in the next video, but for each of the other ones, let's say...
let's jump down here to journal.
You can see for each particular app, there is a...
you_try and a final.
The final is exactly what I created in the video, so in the video, I actually created these three files and here is the exact code that I wrote for this particular application, ok?
Not surprisingly, when it's time for you to try to build the app, you go to you_try.
Now, for almost all the apps there is nothing to really get started with, you can just create a file, a new Python file, and get started, it doesn't matter where you put it, but I put the instructions in here for a few of the apps there will be resources you need to get started, for example that real estate app, the data files you need are going to be there to get started with.
But, most of the time, I have something like this, and it will give you a picture of what we are going to build, so here is our birthday app...
we are going to write an app that has this output, and you can see it asks three questions to construct the date, what year, day and month were you born, and then it does its work and it answers the question...
well it looks like you were born on this day, and it looks like your birthday is in so many days from now.
Hope you are looking forward to it.
Down below, you can see that there is always the key concepts that you need to know, so for example, in this one, we talk about functions, so here is kind of the basics of functions, and we talk about "datetimes", so it shows you the various pieces of "datetimes" that you might need to use.
If we look at another one, like, let's look at number 2...
you can see this one talked about string formatting, conditionals, loops, importing modules, and there is just a little example here, this is not instructions that you follow, but these are sort of the key concepts that you need to know, so if you want to try it before we talk about it, you have at least a chance, right?, but for the most part I expect you to have watched the video and then you try it yourself.
So that's the story the source code, the apps, the instructions, how you follow along.
So, LET GET STARTED!!!!!
It doesn't matter what you want to build, I am sure you are going to build something amazing with Python, and the time to get stated on it, and learning, and building, is right now.
Let's move on to the next set of videos where you will learn about setting up your environment and then it will be on to the applications.
Once again, welcome to the class, have a great time.
|
|
show
|
5:09 |
Before we get to the actual apps that we are going to build, let's talk a little bit about the tools we are going to use, how you use them up, and even just a little bit of history.
The first question that you get a lot of times when you start talking about Python, Python apps, Python course, is...
what version of Python is it going to run on?
It turns out this is kind of a big question, so let's talk about that first.
To appreciate this, let's look at the history of Python.
In the beginning, it came into existence as a concept in 1989, it was released in 1991, had a couple of major releases, version 2 came out in 2003, today it's 2016.
So, development started, version 1 was released, version 2 was released, and then version 3 actually was a major breaking change to the Python code.
The developers look back over the 17 years of experience and said, what was good about Python and what things kind of gave people trouble, what would be a nice way to clean up the language, so that it's solid going forward, and they made these breaking changes to Python 3 and expected people to move along pretty quickly.
Well, it's today, sometime in 2016 when this was recorded, and there is still a debate, should people be using Python 2 or should they be using Python 3, there is a lot of large code bases still running on Python 2, the majority of large commercial applications are probably using Python 2, even though it's been 8 years Python 3 has been out.
However, there is something on horizon coming in 2020, they are going to make this a much simpler choice between the two languages.
First of all, the changes between Python 3 and Python 2 are not that major, if you write code in Python 3 chances are, unless it's one of the advanced features "asyncio" or something like this, chances are it's going to work fine in Python 2, but there are older ways in Python 2 that are not forward compatible, and I think that's actually the bigger problem.
So, if you learn Python 3 I feel like you have more well rounded ability to jump into either type of project, but that said, in 2020, just 4 short years from now, Python 2 is going to be end of life.
No more bug fixes, not sure about security fixes, but definitely no more support, and to drive that point home, at PyCon, Guido Van Rossum, the guy who invented Python, started out his keynote with this saying...
"there will not be an extension to this policy", this is it, only 4 years away before this being an absolutely clear choice, Python 3 is the future, and that's why this course is designed with Python 3 in mind.
So, in order to do this course, you are going to need to install Python 3.
And, we are going to talk about how to do that for each of the various operating systems.
One question, is maybe you already have Python 3, maybe there is nothing to do, so just knowing where Python is available, that's kind of good background knowledge.
Before we get into details to set it up each on these operating systems, let's answer the question- If I have Windows, do I have Python?
No, you have no version of Python, Windows does not ship with Python.
Unfortunately, it just doesn't.
So, if you have Windows, you have users who are using Windows, you need to get them Python somehow, right?
On Linux, chances are at least on Ubuntu, you have both versions of Python, that's pretty awesome, maybe it's not the absolute latest, maybe it's 3.4 and 3.5, but still, pretty darn good.
OS X is somewhere in the middle, typically you have Python 2, but you don't have Python 3.
A little like the choice between Python 2 and Python 3 is somewhat contentious in the Python community, so as the editor you choose.
On one end of the spectrum you have people that use VI and Vim and Emacs, and these very light weight sort of shell friendly, ssh friendly editors, and that's actually a very large percentage of the professional programmer's population for Python devs, and on the other end, you have IDEs rich, full featured, large applications that bring all of your development tools together.
Sort of the premiere one of those, is the one I have here, and the one I am recommending for the course is PyCharm, but we'll see a few other options as well.
So, in editors like PyCharm you can edit of course and run your code, you can run unit tests, code coverage, look, you have a graphical visualizations of your git history and commit changes, there is all sorts of stuff you can do here.
So I recommend to use PyCharm, when we get to the particular operating systems we'll talk more about that.
But if for some reason you don't want to use PyCharm, there are other options.
PyCharm has a free option for the community edition, but if you are on Windows, you can also use Visual Studio and then plug in the Python tools for Visual Studio, that's a nice free option, Microsoft has the community edition of Visual Studio and then the Python tools are open source and free, so that's a really good choice actually, just a bare notch under PyCharm, but really good, if you are into the IDEs, if you are not into the IDEs, choose something like Sublime text or Atom by GitHub.
|
|
show
|
0:41 |
All right, let's talk about how to install the various tools in Python on your operating system.
Now, there are going to be three of these videos, there is going to be one for OS X, one for Windows and one for Linux.
You probably only need to look at one.
Just choose the operating system that you are currently working on, and watch that video, you don't need to watch the Linux one if you are on Windows for example, unless you just want to sort of see what the experiences are like across the different platforms.
So, with that said, here is the OS X one, and the others will follow.
So, there is only two tools, two resources you need to take this class outside the source code on the GitHub repository, one is you are going to need Python 3, remember, Python 3 does not come on OS X, Python 2.7 does, but Python 3 does not so you've got to install that.
As well as PyCharm.
So let's go look at these.
I pulled up the websites that we are going to be working with, Python.org, this is where we get Python 3, PyCharm over here on jetbrains.com, we are going to download PyCharm here and I also pulled up the other three options, Sublime Text if you are interested in that, Atom, you've got to watch this video it's very funny, a great little light weight editor.
We'll come over here, download this, quick, just it defaults you the latest of both Python 2 and Python 3 for your operating system, so you pick this, download, save, I've already done that.
So let's go over here and see, if I type Python you will get something, but you'll see that Python 2.7.10 comes up, if I type Python 3, there is no Python 3, so let's install Python 3 and make sure everything setup good there.
So, this is what I got of Python.org, just click through, agree to, whatever it's going to make you agree to...
ok, so Python is installed.
Let's just try a little trick again, we can even do a --version on the end, excellent, so we have Python 3 installed and it's the latest version.
So, that's a good start.
Next, PytCharm...
when you just click download, it gives us a choice between what version do you want, the professional or the community edition, this is up to you, I love this tool, I paid money for it, I am getting the professional, the community is free, if you are wondering what the differences are, just come back here to the main PyCharm page and you can see, it will show you that actually the Python features themselves, there is not too much of a difference, but the web development, and Python web frameworks, and data base stuff, that is only in the professional edition.
But, lucky for you, none of that is actually happening in this class so you can pick either of these that you wish.
Once you have it downloaded, you will have "dmg"...
disk image here, I love their little installer, here is the app, just drag it over here, wait a moment, and you should have PyCharm installed.
Now, that's finished installing, let's check that, and we can just run PyCharm.
First time it will warn you this came of the Internet, beware, yeah, we did that on purpose.
Make sure you get it from right place.
And, here is PyCharm, I've already run it before, but the first time you run it, it'll ask you for the settings, I like mine in this dark theme, so everywhere it ask you about colors, there is two places, you can say Dracula if you want the same theme as me, or pick another one.
The other two editors are just Atom, here is Atom, nice and clean, and Sublime text, again, super small, super clean.
Let me show you a technique that will be helpful for opening these projects in basically working with Python projects, in general.
So, here I have Request Masters, I got this off of GitHub, this is the Request package and this is actually the source code, so here you can see, here is all the Python files, just like the project base, if I want to open this in PyCharm, I just drop it on here, this is OS X only feature, but if I drop it like this it will open the whole project, and see here is all the code that we need.
You do a similar thing for Sublime text and you can do a similar thing for Atom.
So, here is all the packages, same thing.
So, that's a really helpful tip, if you are jumping from project to project and you want to just open up this project, open up that after project, open up before project, and so on, I am sure you will find that useful throughout the class.
All right, that's it, this OS X system is ready to roll, ready to work on this class.
Hope you are ready to go build app number 1.
|
|
show
|
4:18 |
Hello my Windows friends!
Let's get you all setup and ready to work on this class and build those ten applications.
And I have good news for you, until very recently, using Python on Windows has been actually fairly painful to get it setup and everything configured right, but with Python 3.5 the installer and the setup process is way better.
So let's get to it.
To get started you are going to need two resources on Windows, you are going to need to install Python 3, which you can get at Python.org, and you are going to need to install PyCharm at jetbrains.com/pycharm.
Let's go over to Windows 10.
Here is a brand new, completely fresh install of Windows 10 I just got form Microsoft, and I've opened up the various web pages we are going to be working with.
First thing we have to do is install Python, and as I told you, there is no Python on Windows, if I open this up and I type Python, there is no Python.
So, we have to download Python, and we want 3.5 1 or whatever the latest version of 3 is and I've already actually downloaded it so I won't click here, but you just click that, that's super easy.
The other thing we need to download is PyCharm, so here is PyCharm, it actually comes in two editions, the professional edition, or the community edition.
You can pick either for this class, the community edition is totally fine, the things you'll be missing are...
you'll basically be missing on web development and database management features from the professional edition, and the community for straight pure Python has the same features as professional.
If for some reason you don't want to use PyCharm, you want something more light weight, you can use Atom, at atom.io, Atom is from GitHub it's pretty cool, I really like this editor, you can see there is a little video here, I recommend you watch it, it's pretty hilarious.
Sublime Text is also a super popular light weight editor, and I told you about Visual Studio, so you can get Visual Studio community edition this is now a free, full-fledged version of Visual Studio, and you can get Python tools for Visual Studio, plug this together, and you are doing pretty good.
But, we are going to be using PyCharm in the class so that's what I will setup here.
Let's start by installing Python.
So I've got it downloaded, and I'm going to run it, now it has a couple of options in the installer, let's say if you are going to try to just type Python from the command line or other tools like "pip" for installing packages, you will probably want to add this to your path.
And let's customize installation just to see what we get, we get documentation, "pip" which manages packages, we'll talk about that in our apps, and we have the test suite and "py launcher" is really nice and we don't need to install it for all the users.
Let's go ahead and precompile standard library this will give us a little better perf, I really don't like this big long folder here so this app data folder is hidden in Windows so it's kind of hard to discover where these are so I am just going to put a Python folder directly in my user profile and then, in case you want to have 64 bit or 32 bit version of Python or maybe different types 2.7, 3.5 you probably want to leave this specifier here.
That seems like a good setup, let's go.
All right, Python was set up successfully, let's close this and let's just find out, if I type python --version which we should see 3.5 1 and...
SURVERY SAYS?
SUCCESS!!!
Ok, Python is working, last thing to set up is just PyCharm.
So the installer is just a standard Windows installer, just sort of yes your way through, it's up to you whether you associate py files with it, typically, I don't do that, but it's your call.
Ok, it looks like we successfully installed PyCharm, that was easy, let's go and run it.
Brand new, nothing to import, now, normally I would log in with my JetBrains account, but for this purpose I'll just evaluate it, say ok, that's great.
The first time you launch PyCharm it will ask you what theme and keyboard scheme you want to use, I'll say Visual Studio keyboard theme, and I like my code dark, I have the editors dark and the code text to be light, so I am going to pick the Dracula theme, pick which ever you like, and there you have it, PyCharm is up and ready to roll!
this brand new version of Windows 10 is ready to build our 10 apps So without further ado...
let's move on!
|
|
show
|
4:11 |
Hello my Linux friends!
Let's talk about what you've got to setup on your machine to do this class, in the same way that I am, at least, you will see that you actually already have Python and Python 3 installed on Linux if you are using something like Ubuntu, so that's pretty awesome.
I'll show you where to go to get it if you don't happen to have it and I'll show you how to install PyCharm.
It works wonderfully on Linux but it's a little bit of a pain to set it up so I'll walk you through that.
So, here we are over in Ubuntu 15, brand new fresh version I literally just downloaded, and we are on the PyCharm page.
So we can go and download PyCharm, you will see there is actually two versions, there is a professional version and a community edition, we are going to download the professional edition, you can get a 30 day free trial and if you pay for it like I do, then obviously, you can have it forever.
The main difference between the community and the professional edition, the community edition is always free, is...
the community edition does a whole bunch of cool Python stuff but it doesn't do web or database work, the professional edition does, in addition to standard Python things, web frameworks, type script, database designer type things.
So, for this course, you can totally go by with community but for a professional work, well, maybe professional is the thing to go with.
Some of the other editors you might choose if for some reason you don't want to use PyCharm, is you could use Atom, it is a really great editor from GitHub, I really like it and the video is hilarious so check out the video, just for a laugh.
Sublime text is very popular, and of course, you can use Emacs or Vim that a lot of people are using.
As I said, Ubuntu comes with Python 2 and 3 but for some reason if you need to download it, just come over here, Python.org, grab the latest version it'll automatically find the right thing for your operating system, you could also install it with aptitude, you can do things like apt-get install Python3 -dev, there is a couple of packages that you can install.
So first, let's verify that I actually do have Python installed, Python 3 so I can say Python3 --version, and we have 3.4.3+, which makes it even better than 3.4.3, awesome, and then we have PyCharm, we are going to go download it, it's kind of big, so I actually already downloaded it, go over to my downloads folder, and we have the tarball (tar.gz) right here.
So we need to decompress this and copy it somewhere, so I come over here, right click and say extract here, and it will extract it out.
Now it has the version name here, let's find, let's make it new location, let's put it in my home, I like to create a folder called bin in my home and then here I'll make a folder called pycharm and within pycharm I'll put pycharm-5.0.4.
Now if you open it up you'll see there is nothing to run right away but there is a bin folder within there and what we want to do is we want to run this script, so I could double click it, and it will just open in gedit, not the best, so I am going to come over here and just drop it into my terminal and run it.
Now, it turns out, there is a problem, PyCharm is built on the IntelliJ platform, IDE platform, and that platform is Java based, so we need to install Java, before we can carry on.
So on Ubuntu, we'll just use apt-get so we'll say sudo apt-get install openjdk-8-jdk.
And I'll put in my password, I'll wait for a moment, and says are you sure you want to do this, it might take a moment 171 MG's, it's fine, go Excellent, well, that took a minute, but now we have Java installed, let's try to run that again, PyCharm shell, now it's running, you can see it says do you want to import previous versions?
well no, it's a brand new machine so no, not really; normally I would just log in with my JetBrains account but for now, I'll just evaluate it for free, which you guys should be able to do for this class.
When PyCharm first opens, it asks us what keyboard map and visual theme we would like, I am going to leave the keyboard map alone but I like my code my IDEs and windows and stuff to be dark, not bright, so dark background light code, so I am going to pick the Dracula theme for both the code colors as well as IDE theme, and I will say ok, and it can't make the change unless you restart, so yeah, let's let it restart.
Excellent, my PyCharm is running, it's nice and dark with its Dracula theme.
Now the one other thing I'd like to do is notice it's over here, and I kind of like to not be running this shell script anymore straight from the terminal, so let's run it one more time, notice it's gone from the launcher.
Now it's up and running, I can lock it to the launcher, and now this way, when it's gone, I want to launch it again, I can just come over here and launch it straight out of launcher.
Congratulations, you have PyCharm working on Ubuntu, it's time to head on over and build your first app and have a great time doing it!
|
|
show
|
5:07 |
Welcome to your course i want to take just a quick moment to take you on a tour, the video player in all of its features so that you get the most out of this entire course and all the courses you take with us so you'll start your course page of course, and you can see that it graze out and collapses the work they've already done so let's, go to the next video here opens up this separate player and you could see it a standard video player stuff you can pause for play you can actually skip back a few seconds or skip forward a few more you can jump to the next or previous lecture things like that shows you which chapter in which lecture topic you're learning right now and as other cool stuff like take me to the course page, show me the full transcript dialogue for this lecture take me to get home repo where the source code for this course lives and even do full text search and when we have transcripts that's searching every spoken word in the entire video not just titles and description that things like that also some social media stuff up there as well.
For those of you who have a hard time hearing or don't speak english is your first language we have subtitles from the transcripts, so if you turn on subtitles right here, you'll be able to follow along as this words are spoken on the screen.
I know that could be a big help to some of you just cause this is a web app doesn't mean you can't use your keyboard.
You want a pause and play?
Use your space bar to top of that, you want to skip ahead or backwards left arrow, right?
Our next lecture shift left shift, right went to toggle subtitles just hit s and if you wonder what all the hockey star and click this little thing right here, it'll bring up a dialogue with all the hockey options.
Finally, you may be watching this on a tablet or even a phone, hopefully a big phone, but you might be watching this in some sort of touch screen device.
If that's true, you're probably holding with your thumb, so you click right here.
Seek back ten seconds right there to seek ahead thirty and, of course, click in the middle to toggle play or pause now on ios because the way i was works, they don't let you auto start playing videos, so you may have to click right in the middle here.
Start each lecture on iowa's that's a player now go enjoy that core.
|
|
|
13:38 |
|
show
|
1:14 |
Hello and welcome to the first app you are going to build.
It's a Hello World app.
And we are naming it Hello (You Pythonic) World.
So, what exactly are we going to build?
Well, it's going to look like this.
We are going to have a little Hello World app and it's going to have a header that says "Hello App" and it's going to ask a question "What is your name?" And then, the user will enter something like "Michael".
And you can see here, this is a convention we use throughout the course that the yellow indicates user input and the white indicates application output.
So the user enters "Michael", and the app will echo back, "Nice to meet you, Michel" Now, you may not think that there is a whole lot to learn from such a simple application, but in fact, there is really quite a bit going on here.
We are going to use the Hello World app not just to show the minimum number of characters you have to enter into an editor to get Python to generate an application, but in fact, to verify our Python environment, and to get started with our editors, as well as we are extending it to do a little bit more than standard Hello World which is maybe console out, but we are talking about basic syntax, strings, accepting input from the users, variables, that kind of stuff.
So you can actually learn quite a bit, even from this very simple Hello World application.
|
|
show
|
4:39 |
So let's switch over to my Mac and get started.
So to use PyCharm, it's pretty simple, you open it up, and you just say create a new project.
You can see there is a lot of different kinds of projects we can create, we are just going to do a pure Python app for our beginning one, but you could create a Django, Flask, or Pyramid web app, even a Google App Engine app, and there is also a lot of HTML, Javascript options because PyCharm is a full featured Javascript, CSS, HTML editor, in addition to being a great Python editor.
So I actually already have a project I created just by basically saying ok, on the next screen.
And when you create a new Python app, a pure Python app, you end up with basically a blank folder.
So, we are going to come over here and add a new Python file, so right click and say new Python file, and throughout this class, the main program that you'll run, that indicates sort of the entry point into this set of scripts, or Python files that we are building, is going to be named program.py, so I am going to create something called program.py, I am using Git to store all the files and demos that I create for you during this class which I will make publicly available for you to look at and see how they have evolved over time and you can see that PyCharm is actually suggesting that we add this to our Git repository I was going to tell it to please don't ask again.
Now, remember, the first thing that our little sample app had was it had a little header with some dashes and it said "Hello App", and it sort of was bracketed by kind of a dashed line above and below.
So let's go back creating that.
So we are going to use something called the print function.
And this is typically the way that you output things to the console.
We can say print, and then here we can put s a string.
And in Python, you can create string with double quotes, like this " " or you can create strings with single quotes, like that ' ' and I actually prefer the single quotes, it's just a little less typing, you'll see there is some times you might want to use double quotes, you might want to use single quotes, we'll talk about that later.
So, we are going to come over here and we are just going to have our dashes like so, and then we also want to output that little "Hello App" part, so we are going to come here and say print, go over and say hello app, I think pretty much like that and we are going to say print, I am just going to do this one more time.
Let's go and run this and make sure there is some reasonable output, first thing to do is save this.
Now I could go to the console, to the terminal and just run that, I can easily do that by saying copy path, we go down here and I can type Python 3 and I can just give it a path, add hit go, and then out comes our output, hello app, perfect.
But, PyCharm let us run this, and debug it, and do all sorts of cool runtime analyses of our app, right from within PyCharm.
And you see there is a little debug icon here and a little play icon here, but they are both grayed out, and that's because there is no run configuration setup.
So instead of going over here, we are just going to right click and say run the program and it will create a run configuration and save that for us going forward.
So just like before in the terminal, we have hello app, very very nice.
We also maybe want a little space between here, we can actually just call print with no arguments, and I will just do a new line.
Now, the next thing that we did in our app was we actually asked the user what their name was.
So we could actually get down to the system input output streams and directly work with those, but in Python there is a simpler way.
We can just say input, now this also takes a string, and we can say something like "what is your name?" put a space and then, in the console the cursor is just going to stop right there and wait for the user to put some inputs, so let's just going to run that, I can click this, or this or I can say control r "^R", in Windows, I believe that's F5, and on the Mac it's control R "^R", and down here you can see it's asking what my name is, I'll say my name is Michael.
Then, it waits and when I hit enter, it accepts the input and moves on.
Now, that's not super helpful because we are not actually getting the name, we are just asking the question.
So, let's take a moment and look at a couple of core concepts that we are working with here.
|
|
show
|
1:56 |
The first concept is around this idea of calling functions, print is a function, input is a function these are reusable pieces of functionality in our application.
Here we are calling a function called main, and you can see that you call it with the parenthesis on the end some languages have semicolons to indicate the end of a line, thankfully, Python does not have that, and this takes no arguments and it returns no value so you just call like this.
Now, the input on the other hand, we can call it and we pass it a string, kind of like print, but then it returns whatever the user inputted.
So we are to create a variable called saying, we are storing the return value from the input function which is what the user typed in the saying variable we can work with that later.
So that brings us to variables.
And variables in Python are very very simple, they are sort of no-nonsense, so you just say the name, or the word you want to use for the variable and then you assign to it and...
SHAZAM!!
the variable exists.
Some languages you have to specify types and initialization and so on, Python is sort of as simple as it can be around variables.
So here we have a variable called name, and we are assigning the value of Michael, we have a variable called age and we are assigning 42, and we can even increment this value of the age, we are doing a "+=" operations, so we are taking the age, adding 1 to it, now the age is 43, so happy birthday to me.
We have more complex variables of course, here we are creating what is called the list and we are populating the list with 3 strings, so biking, motocross and programming, and then we are storing that list holding those objects in the hobbies.
So, all of these are directly created upon assignment, we can also create variables and store return values from functions, so here we have home address and we call in a function and we are storing the result of that function into home address.
So let's go back and use these ideas to finish up our "Hello World" app.
|
|
show
|
2:14 |
So back in PyCharm we need to grab hold of that value that is returned from input.
Let's be really explicit and call this user_text or something like this...
ok.
So in Python, when you have complex words or variables and functions made up of multiple words, if you will, you typically separate them with underscores.
Other programming languages might write "userText" like this, where they use camel-casing, in Python it's all about the underscore.
So, we are going to call it this, so that gives us the user text and we could actually just print this out just to see what they entered, so let's run this really quick say "I entered this", if I hit enter it just says "I entered this", excellent.
So we have got that value and print it back out.
That's not exactly what we want to do though.
We want to actually store some kind of saying that we want to say and I believe it was "Nice to meet you", whatever they entered for their name.
So we'll say greeting = "Nice to meet you " and we want to sort of somehow incorporate this string here so in Python, they make it really easy to combine strings just using the "+" sign.
So we'll say, nice to meet you "+" user text and we can just print out that greeting, give a little space so it's easier to read, so let's just run that, and see what we get.
So down here, we can enter our name which will be Michael, hit enter, and it says "Nice to meet you Michael".
Perfect!!.
So now, we've completed our first app, you can see we've worked with the print function, we're calling it with parenthesis, passing a string literal, it's what you call it when you create them just in place like this, passing these string literals as arguments, sometimes were not, calling the input function, again passing strings, catching the response, the input into a variable called user text, we are concatenating to another string, a literal string plus a variable, storing the greeting and we are printing it out.
All right, pretty simple this application but it really gets us off to a good start to start building more interesting complex applications that build on things like file i/o and web services and all sorts of cool stuff like that.
|
|
show
|
3:35 |
Now, before we move on to our next application let's take a quick tour of PyCharm.
So, PyCharm has so many features, it's very hard to just sit down and show them to you.
But let's just take a moment and hit the highlights.
So on the left over here we have our project, folder, and this can be quite complex, in real apps you might find many subdirectories, multiple files, all sorts of stuff, we'll get to that later.
But here we just have our directory and our program.py, and it actually shows the external libraries that are set up into the particular Python environment that we are working with.
So you can see that we have things like Pygments, and Chameleon which is a web templating framework and mailchimp for managing subscribers, things like that.
Not super important for us now, but this sort of lets you get at your active Python environment.
We can also go over to the structure, and the structure for this app is frankly boring, because it just has a few variables and that's it, but if we had functions which were using classes and these classes were defined with more functions and fields and attributes and properties, all this kinds of things, you would see this would actually be quite interesting, you can use all these little pieces up here to slice and dice them.
Down at the bottom, we have our run configuration, or a place where we would run our app you can see there is actually two of them running, that's kind of a problem, we'll talk about how to fix that.
So we can come over here, we can run it, we can stop the running one, you can see this one as a green dot because it's running, so we can stop this...
it's all good.
We can also have TODO's so we could come over and put a comment; comments in Python start with "#" hash so I could say, # TODO "this needs cleaned up", something like that and I could say something like TODO.
All right, so now if you look down here you can see it actually shows us our TODO's PyCharm highlights them very nicely up here.
Take those back out but that's what this section is for.
Here is just a Python console you can...
as if you typed Python in the terminal or the command prompt, here is literally a terminal, and here is the version control that we talked about.
There is support for all different kinds of version control, you can see we are actually working on the master branch in GitHub.
Over here, on the right we have a database section, some really excellent database management and design and querying tools built in the PyCharm.
We don't have a database that we are working with right now, but if we did, we would connect it here and like it do all sorts of cool stuff.
Finally we have sort of run, debug and manage version control shortcuts up here.
We come up here, we can look at the configuration you can see we can actually pass parameters like if I want to just hard code a name and pass it like this, this would be passed to our program when it runs, we are not going to do that but we could you could pick which version of Python you want to run pass additional options to the interpreter itself there is all kinds of things that we can do here.
Remember we had two instances of that app running that was kind of weird so we can check off this single instance only and that actually fixes that problem, so you only ever have one running at a time.
Now when I run it, if it's running and I try to run it again it will say we are going to restart it for you, so I often say don't bother me with that just run it again whenever I ask, ok?
So like I said, there are many things you can do with PyCharm, stuff with version control, stuff with managing virtual machines, all kinds of stuff that we just don't need to get into right now but what you saw should give you a really good jumpstart so you can be effective with PyCharm right away.
|
|
|
19:15 |
|
show
|
2:15 |
Hello and welcome to app number 2 this time we are going to build a game, a guess what number I am thinking of game, and it's going to look like this.
The program will start up and it will randomly choose a number between 0 and 100 and then it will interact with the user going back and forth until they figure out what that number is.
It will ask them to enter a number and it will say, no, that's too low, no that's too high, and then finally you can see the user was able to figure out, hey, 71 was the number the program selected.
So, this is a pretty straight forward app, but you are going to learn a lot while we build it.
So what specifically?
Well, we are going to focus on boolean statements and switching between code blocks.
So we are going to start up by talking about boolean expressions, things like x > y, y is not nothing, the users entered some valid text those kinds of things.
And we'll use that in two basic places, one is going to be an if, else if categories so if the number is greater than the one you guessed you want to print one thing otherwise you might want to print something else like the number is too high no in other case print the number is too low but also in while loops because we want our app to go around and around until the person, the user has selected the right number they've guessed the right number.
We are going to use a conditional test to keep going until the numbers match.
Closely related to this boolean concept is something I'm calling truthiness.
You'll see that objects within Python are embued with a truth or falseness we'll leverage that as a key building block in our boolean expressions.
We'll also learn about type conversion users will enter text but we need to actually work with numbers.
Previously we composed strings by using the plus symbol but Python has an extremely rich formatting API and we'll start looking at it here we are going to just get into the very basics of function as a way to compose our app into more understandable and maintainable building blocks and part of that is going to be understanding code blocks which are a little, let's say unique in Python.
|
|
show
|
4:26 |
So let's jump into PyCharm and start building this game.
Remember, our convention is that we are going to start out with the main thing we run called program, so our new Python file called program.py and our app, as the very first thing it had at the top a header so remember we'll do print and we had a bunch of dashes here.
Now in PyCharm, if you want to duplicate a line or a selection or a set of lines, you can hit command D in OS X or in Windows or Linux control D.
So I will hit command D a few times, it's a little bit of a shortcut.
And then it said guess that number game, something like that, let's do some centering, and finally let's do a print, just to leave some space there at the end.
Before we go any further, let's just run this really quick to make sure everything is working, again, there is no run configuration up here because this is a new project, so we'll right click and say run, and then all right, a little header is working fine.
Now, the next thing we need to do is come up with that random number and the number the computer guesses so I will create a variable called the number and now, somehow we need to...
here come up with this random number and one of the great things about Python is it comes with all of these modules and libraries that we can use one of them is called the random library, or the random module Now, PyCharm is not happy, because we need to actually tell Python we intend to use a module that is not what is called the built in this is like an extra part of Python so we need to explicitly state I'd like to use this part of Python this works identical to whether it's something in Python with the standard Python implementation, or something external, like some other library that you've gotten.
So we are going to say import, and we just say random, and then we'll say "random." now we have a bunch of options, a little warning went away, the one we are interested in is randint we'll come over here and call randint and if we hover here for a moment or pause for a moment, it will say it takes two parameters a and b.
Now, so I would guess that those are numbers, and I'm sure you would as well, and that they somehow specify the lower and upper bound that we are suppose to work with here but is this including those n points, somehow between these n points, and we can check the docs, but it turns out that this actually includes both n points so what we want is 0 to 100.
Let me show you something that PyCharm can do for us that really is pretty amazing and one of the things I really like, imagine let's roll this back for a minute, imagine that we don't have this import going on, and we don't have this line, so here we are again and we want to say just like before the number is equal to random.
Now PyCharm has a little red light bulb you can see there and it says, Hey, this is not right, but I am pretty sure I can fix it for you, automatically.
So if I hit "alt enter", it will come up with the list of options to fix it, so I could actually just hit enter and it will say we are going to import random and there is a couple of ways we could do it but this top one is what we had done before, so I can just hit that, and you could see right at the top it added import, random, now I can type randint and off we go.
So, a lot of times when you see an error like this, one of these red squiggles and this little light bulb in PyChram that means that there is some kind of auto fix it's going to propose for you and it is probably worth checking out.
Ok, excellent, so we have our number guessed, our number between 0 and 100 inclusive, now the next thing to do is get some input from the user and we are going to just tell the user hey, enter a number between 0 and 100 so I'll call this guess, and this is going to come back as text so I'll be very explicit and say guess text so we'll say input, just like before, guess a number so guess a number between 0 and 100 and we'll just let the cursor stop right there, and we'll wait for that.
Conceptually, the next thing that we are going to do is we want to compare the number against the guess text.
Now, Python is not a very forgiving about a quality between numbers and strings, these don't really have anything to do with each other, so if we tried to just say print out whether the number was...
let's say less than the guess text, if I try to run this you will see it'll actually crash, so if I put in 42, and it says you cannot sort between or compare the order of integers and strings together, right?.
You can ask whether they are equal, they will never be equal, but you could ask that question, but you cannot do the type of comparisons that we need for this test So the next thing we need to do is actually take this guess text and convert it into an integer and we'll call that integer the guess so this is super easy, the int class is just built in and we can call it, initialize like this we can pass it some text and then what is going to come out is actually a guess so we could say really quickly print the guess text and you can actually ask for the type of these so let's print those two out and here we'll print the guess and the type of the guess so if we run this two, if I enter 42, you can see, the first line where was guess text is actually a string, "str" is the string class in Python we've actually converted successfully to 42 it looks the same when you print it out but it's definitely not the same type in memory.
All right, the next thing we need to do is write some conditional statements with if and else statements and some comparisons and so on in order to determine whether we have won the game the guess was too high, too low, that sort of thing this is one of our core concepts from this application so let's go dig into that.
|
|
show
|
2:39 |
One of the fundamental components of any programming language is the ability to make decisions, decide to go down one path or the other based on some tests you can run.
And in Python, this is the if, elif, else block, so let's look at this co de sample we have here.
We have some command we are trying to get from the user, imagine this is a command line app you can enter commands to run or execute at this point in our application.
So we are going to ask the user to enter either L or X but of course they can enter anything, right, that's just open text they are going to type in, so we also account for that.
We have these two commands we are going to work with, let me say if the command is equal to L the string L, the way you do equality in Python is double equal so command == L then we are going to indent and run some code we are going to list the items if it's not that case that it's L, then we want to do another test, we'll say well maybe if it wasn't L, else if, which is abbreviated in Python to elif, the command == X so maybe the command is X, in that case we're going to call exit and if it's neither of these two things we expect, well we can just print some message like sorry I don't know what you meant by whatever it was you typed in this is not a valid command.
So these conditional statements always start with if, and then they optionally have an else if and they optionally have an else.
And of course, this is a key building block for our game to decide if the number is too high or too low.
So, we've seen that you can do equality test, ==, we can also do less than "<", greater than ">", these types of comparisons that are pretty conceptually straightforward.
But in Python, you can test any object, anything that is a variable you can put into an if statement and it will either evaluate the true or false, and there is a simple set of rules around when a thing is going to be true and false a lot of code in Python leverages this concept for conciseness which I am calling truthiness.
So the rules are like this, there is a set of defined things that are false, obviously the word False capital F is itself false, but also collections, lists, sets, dictionaries, and so on that are empty, by the fact that they are empty, that considers them to be false.
Strings, treat the same way, for all sorts of numbers, if the number is 0 then it is false, otherwise it is true.
And of course any object can point to an actual thing or it can be pointed at nothing, in Python we call this None, other languages call it null or nil, in this case, when you have a variable that points in nothing, that is also false.
So, if you are not in this short clear set of things that are defined to be false, then it evaluates to true.
So we'll see a lot of code leveraging this concept around loops, around conditionals, and so on.
|
|
show
|
3:14 |
So let's write the test for our code to see if the guess that the user entered is correct.
So what we want to do is want to say if, and then write a conditional comparison statement, a boolean statement, so we can say if the guess, not the text remember, the actual number is less than the number, then we want to print something out and for defining these if statements and all sort of code blocks into Python we use a colon to indicate we are about to define a block of code that is going to run in some situation maybe it's a function, maybe it's a loop, in this case, it's a block in our if statement.
So when it's true the guess is less than a number, we are going to print out something like too low, the guess is too low, the guess is lower than the number the computer came up with.
So that is one part, next, we are going to ask else if it's the case so we'll say elif again, if the guess is too high, if it's higher than a number we'll print too high, like so, and notice, when I hit enter, when I type this block, I will just go back for a second, if I type x and hit enter, or complex or whatever, PyCharm wants to correct it for us and I say test and I hit enter, it goes back to the same line here, but because PyCharm knows about the structure with colons, if I go and I type something like if x: enter, it automatically indents for me, so you will see these Python intelligent editors like PyCharm make it really easy to write code correctly.
So we have if, indent, print too low, else if "elif" guess is too high, print too high, and the last thing I want to do is say else: and we'll print you win.
So there is a few more things we are going to need to do to make this work just the way we'd like but let's go and run it and see how it's working.
Ahh what's a good guess?, 54 it must be 54, too high, oh, well the way game is supposed to work it's supposed to say too high, now you've got another chance.
So what we actually need to do is go around and around, and that means we need to find something called a loop, and the type of loop that work really well for this situation, in Python is something called a while loop.
So you say while, and then some sort of condition goes here that you want to test, and as long as that is true, you are going to go around and around and around, so we want to say long is the guess is not a winning guess, we want to keep going around so we'll say guess, you say equals like this, remember, and if you say not equals you say exclamation equals != So now we'll say colon, we need this whole if statement/else if part to run, while this loop is going, and the way that we do that in Python is we indent it so I can highlight in PyCharm and hit tab and it is going to indent everything four spaces for us.
And then when we are no longer indented, if I print something down here, done like so, you can see this is actually out of the loop.
Now, if I run this something really bad is going to happen, we are going to end up in what is called an infinite loop, because, we get one piece of input here from the person we convert it to a guess let's say it's 52, who knows, whatever, and when we go in here and unless you guess the winning number, it is going to go around and around and around either say too low, too low, too low, or too high too high, too high, and it's never going to give the user another chance to enter this text.
So what we need to do is make sure that we ask this question, and do the conversion within the loop.
Ok, so remember intend, so it's part of the loop, we'll talk more about that in a moment, so when it get the number, and then we are going to convert it and so on.
Now, you can see PyCharm has this sort of highlighted as an error and it says guess cannot be defined after it's used.
So basically we need to give some kind of value for the initial test so we get into this loop at least once so we can pick something like negative one or something that will never be a winning guess, that will make sure that we at least run to this scenario one time, now let's try it.
54- too high, 40- too low, 45- too high, 43- do I feel lucky- YES!!!, I was lucky, awesome.
So, that is how the game works, we can do a little bit nicer stuff but let's take a moment and pause and look at this concept of this indentation and defining code blocks that run by using indentation.
This is one of our core concepts that we are introducing as part of this app.
|
|
show
|
3:12 |
A core concept of this application is to understand the shape of a Python program.
So far, until very recently, we've been just writing straight top to bottom code, with no blocks, no functions, no conditionals or loops or anything that has to be disambiguated in that sense.
And now, what I want to focus on is this concept of a shape of the Python program.
Everything you see on the screen that is in the square, the gray square, the blue squares, the purple squares, those are all separate code blocks, sometimes called code suites in Python that are executed in some particular manner.
The blue blocks are functions that are executed when the functions are called, the purple blocks are if or else blocks that are executed depending on whether the argument is batch or something else.
The key thing that defines code blocks in Python which makes it quite unique in this regard, is white space.
Many C derived languages use curly braces, think of Javascript, C#, Swift, those types of languages, they all use curly braces and when you open the curly brace that begins a block of code, and when you close curly brace that block code is now done.
It doesn't work this way in Python, it's all about the indentation, so you can see the gray block which is just the overall program and we have two functions we are defining, we have def main and we say colon, and then we indent all the body of this function.
And that body is made up of those two blocks in there, there is two purple blocks sort of the if else statement.
Then we unindent and we define another run function the details don't matter, we say colon and indent again and that's the body of the function for a run.
So you've already seen previously that editors like PyCharm and other Python intelligent editors know about this shape, they know about this code blocks and if you type if something colon enter, it will automatically indent and stay intended until you unintend it manually.
While it might seem a little challenging to use spaces to define a structure, it turns out the editors know all about it and make it super easy.
One more comment I should add while we are talking about indentation or white space, tabs?
tabs are not a good idea, in Python, Python does not like tabs.
This white space should be actual the space character, not tabs, now that doesn't mean that you don't press tab in your editor like in PyCharm if you hit tab it will indent but it actually just means it's going to insert four spaces for you and many of the Python editors are like that.
So if you are coming to Python from another language that does not use significant white space like C++ or some of the C based languages this could feel a little strange to you and it does take a week or so to really become super comfortable with this idea but once you do it's very natural and it's a lovely way to program but I know that some of you out there you are thinking WOW!
this is really different and to you I say just give it a try, use a nice editor and you will start to love it right away.
if this feels totally natural and good to you, well then you are going to love Python right from the beginning.
|
|
show
|
3:29 |
So we have our app working but the user feedback could be a little better let's go down here and try to improve this message we sent to the user instead of just too low, let's say something like your guess of...
and we can put in this number this guess they had here we saw that we could do this by combining strings by saying guess so maybe you'll think well this is a way I can do this here and this will just combine it that does work in certain languages say your guess of let's say 70 was too low, something like that.
Now, PyCharm is kind of giving away the surprise here, but this is not really love that says the string int conversion with plus is not going to work out so well for you So let's just run it and find out.
So if I enter something that is going to be for sure too low like 0, let's see what happens, you get this exception, you cannot convert int object to string, implicitly.
Well we technically could explicitly convert it but it turns out that there is a much better way in Python.
What we want to do is we kind of want to put something into this space, now the way we can do this nice way is we can use these little curly braces inside the string and then call a function on the string called format and pass whatever we want to go in there.
And so, into this location we can say we'd like to take the guess and explicitly convert it to a string, whatever that means, this time it's a number, so that is pretty straightforward, and then we are going to put the result of it right there.
We can actually pass many parameters here, we could have something like name, like let's change this really quick name=input player what is your name and these little numbers that go in here talk about the order in which there is specified in the format method.
So guess is first, everything is zero based, so it gets a zero, name is second, so it's {1}, so we could say sorry {1} your guess of {0} was too low.
Now let's go and run this, see if everything is working, enter your name, my name is Michael, enter a guess, I am going to put 0, sorry Michael your guess of 0 was too low.
Now, we are just scratching the surface of the power of this format method.
We can if we wanted to switch the order here, if we specify the place holders in the string in the same order as they are specified as arguments there we can actually omit the number now this will say sorry name your guess of...
whatever the number was, was too low.
We can put in format specifiers here to say I'd like to separate this with commas or convert it to currency, or all sorts of sort of format specifiers we can take dictionaries and name these things and pull items out of the dictionary by name and then put their values in here, it's very rich, but let's just go with this for now.
So we'll do the too high case here, so your guess was too high, and finally, we'll just say something nice about winning excellent work so and so, you won it was something like this, right?
so excellent work, whatever the name is and you won it was whatever the guess was.
So, let's play the game again, all right, what's your name, now I've got to enter my name, it's Michael, I am going to guess 50- wow 50 was too high, 25- too low, 35, 40, 45 47, what do you think, 46, chances are likely, YES!
excellent work Michael, you won, it was 46.
See how we can use string format to really simplify and clarify how we convert values into strings.
|
|
|
22:06 |
|
show
|
1:35 |
It's time for app number 3, this time we are going to write an app that'll answer the question how long until your birthday.
So what exactly are we going to build?
We are going to build an app that looks like this it's going to have a little header saying birthday app like all of our apps do, and then it's going to first ask a couple of questions of the user when were they born, so ask the year, month and day, and then pull those back, those will come back as strings, we'll convert those to numbers, and use those numbers to actually come up with a date object, that represents a particular time on the calendar, and then we'll do a little math with that date, relative to today, and we'll use that to answer the question how long is it until your birthday and if you have already had your birthday we'll say hey your birthday was so many days ago.
So again, that looks pretty straightforward, but we are going to learn a lot.
We are going to come back to working with functions and organizing our code into small, reusable pieces of functionality and in our previous example, we saw that we worked with functions but they were sort of the most basic, they were just things you would call to have actions and here we are going to have functions that both take parameters and so we can supply different values to them like a particular date and the return values, the things like how many days from this particular date until now.
The other major thing we'll focus on is dates and times and time spans, so when you work with dates and times between dates, in Python these are the 3 basic concepts that you'll see and they will be central to this app.
|
|
show
|
4:25 |
Over here in PyCharm, let's start working on our birthday app.
So as usual, we are going to come over here and pick a new Python file, and call it program.
Now we are going to start out by having a header, then the next thing we are going to do is we are going to ask the user for their birthday information, and then we are going to compute the days between now and that birthday they entered, and then finally we'll tell them whether their birthday is coming up how many days, that sort of thing.
So we could just write this out in one giant series of events, but I find it much easier to think about this in small little steps, kind of like I just described, so we can do that with functions.
So we are going to start by actually defining a function and the purpose of this function is to get the input from the user and return it any time that we need it.
So let's say get_birthday_from_user().
So remember, to define a function it always starts with a keyword def, and then the function name, any parameters if it takes none, you just have open close parenthesis and a colon.
And then you indent to define the code block that is the function body.
Notice when I hit enter, PyCharm automatically took care of that for me.
Now, one thing we can do is we can just kind of sketch this out and if I want to define the function but not really implement it or write the details of it here, I can just say pass, right?
So the other thing that we are going to do is we are going to compute the days between two dates so we'll say compute_days_between_dates(), all right, and all right pass again just so we can go on and sketch out the rest of the app.
Now notice, PyCharm has put a little squiggly into here and there is a warning that says this is a PEP 8 violation, now PEP 8 is the standard for how code should be written in Python, you don't have to follow it but pretty much everybody does for the most part, and, one of the rules in PEP 8 is that when you have two functions that are stand alone functions, there is supposed to have two lines between them.
PyCharm actually knows how to fix many of these and I can hit command+alt+L and it will do whatever it needs to fix it.
Now the next thing we are going to do first we are going to get the information from the user, we are going to compute the days and then we are going to say def print_birthday_information(), something like that.
And again I am going to pass...
and there is one part I kind of omitted because I don't really think of it as an operation, but it is something we are going to do, is we are going to print the header, and let's just put pass really quick.
So that is kind of the steps of our app there we are going to print out the hello, welcome, get the information from the user about their birthday, compute whether that is in the past or the future, how many days and then we are going to use that to print out the information.
The last thing to do is actually just orchestrate this into a main method, so we'll define a main method, here some programming languages like C++, C# and so on, they require you to actually define a main method called main, there is nothing like that in Python, this is just a convention.
So, over here, we are going to first start by printing the header and then we are going to get the birthday information from the user, we are going to store this in a variable but just hold on for a minute there, we are going to compute the days between the dates, and then finally, we are going to print the birthday information.
And then we'll be done.
Final thing is we are going to be returning some information from this, we are actually going to return a date object that represents the real birthday, so we'll call this b-day here, and this birthday we are going to need to pass off to this function, so we have the birthday, and we are also going to need to know what time it is right now and I am just going to put none because I don't really want to talk about that what that is just this moment, but we are going to get to that right away, so we are going to call get birthday from user, get their birthday, we'll compute what the time is now, we are going to compare the times between these and this is going to actually give us number of days, like so, and then we are going to pass this on here and we will print this out.
So you can see that PyCharm is actually showing us that hey, you are trying to use the return value of this function, and over here you can see we just say pass they are not actually returning the value.
So our next job is to go and implement each one of these functions, so this flow works as we expect.
|
|
show
|
6:04 |
With the general's skeleton or shape of our app in place let's go and fill out some of these pieces simplest want has to be this print header, right it's just going to look something like this, the title right the middle and then one more there's so we have our little stamp now the next thing that first thing really we're going to do is get the birthday from the user so we could ask them for inputting like a date, you know, year slash months last day, but then you've got different cultures have different styles, right?
Like europe versus the u s we arrange things differently and then the partisan it's hard, so let's just keep it real simple like this, we'll just say, when were you born and we'll ask him, what year month and day separately we can use input for that so we can say year equals we'll give him a little hint here with this why?
Why, why?
Why now?
I could copy this line for the month or i could just retype it, but in PyCharm you can actually hit command d and will be duplicate whatever you have selected if there's nothing like that will duplicate the line so it's really nice we just say month or here and update this and then again for the day some like that ok, so now we'll have the information when the person was born this is not gonna work exactly want to come back and fix this?
But before we do let's go a little farther to see why next to one i dio is work with dates.
You know, just like when we worked with requests, we have to import this module so wouldn't go up here.
I want to say import daytime so this is a built in a library in Python you have toe install anything extra to get it it's just right there, but usually have to import it.
So now we come out here and say something like that a birthday i say b day some like that equals when come to date time here and now there's a few options what we can do, we can work with date and time so, you know, year, month, day, hour, minute second type of thing or we could just work with calendar dates and there's some other options here as well.
So we come in here if we want to work with daytime that includes the hours minutes, seconds we would work with the time dot date time it's a little confusing because, like, why do you say the same word twice?
Well, i don't know that's just how they designed it this is the module and this is a class contained within their which we can do things like love here and say what's, you know, set the year set the minute things like that, we don't care about the time we're trying to compare days, not actual moments in time, so what?
We're going to work with dates here?
We could also we just cared about time you could work with that and what we're also gonna work with implicitly, we won't create one of these, but you'll see it, it will be created.
But to sort of behind the scenes force is this thing called a time delta, which shows you the differences between two dates that's going to be important, so we're going to work with data now we're going to try to create the date that they've given us here, so we'll say, and our year, month and day, right?
So that looks like it's fine.
The next thing we want to do is give this back right that's the purpose of this function was to come down here, get some, do some or ask the user for this and then actually give back the birthday.
So what given stages for term b day and down here or going teo, you just change his name really quick just so you can see they don't know no matter from like that so we couldn't hear, and we'll return this from this function that we're calling out here and storing this value and use it later in this section here.
So this almost works now noticed pi charm is highlighting the stuff, and it has a reason for it.
But let's, go and run and see why he's going to actually be too interesting outcomes here, first of all, well, let's, we can't run it because there's no run configuration, so we just right click and say run program, and you might expect to see things like this printed out in that and so on .
But remember, there's no convention what this main method being called has just happens to be when i called it so in order for this to actually get triggered to do a thing, we have to come in here and call it now.
This is not really the best way to trigger this function to be called there's a better way, way we'll get to that another video a little bit later, but for now, let's, just do it like this so we can get it to work, and then we'll talk later about how to make it better ah, there we go, birthday app, when were we born nineteen, seventy for april fun who that doesn't look very good that's not it's not a date i was looking for right so what happened here it says we had a type air that imager is required but what we got was a string if i click here to actually take us to where the problem lies who this is the thing PyCharm had highlighted it said that you expected editor but you're giving me a string and the reason is everything that comes out of input is always a strength so we can convert it to a number by just passing that to initialize or for and so when its rapid like this if you wanted a float we would do like that but we don't want imagers on something down here okay now we should get it to run past this problem it's going to crash in another place but that's okay we'll get there well we're making our way down to nineteen seventy four april first and now we get this compute dates is not ready that's over here but noticed this part works so let's we're not pass it we haven't configured these functions to take those primaries yet so it's put that on hold for just a minute let's just print out what we got here we put out our birthday when we were born in one more time anything said before april first boom looking at nineteen seven for april first so we've gotten a little ways down the path.
In our application, we've gone to the user, and we've asked them a couple of questions.
And we've taken a input and converted it first in numerical values and then to date values.
So we're off to a good.
|
|
show
|
6:18 |
Now that we're getting the birthday from the user, the next thing to do is to figure out how close that is to today it's good to hear an uncommon this remember this was crashing and if we run it it's still going to crash saying a compute days between dates takes zero positional arguments but two or given that means it's unhappy that we're passing these things to it but it doesn't appear state that it expects any parameters some languages like java script we'll let you get away with this Python so let's go over here and say we're going to have these two things passed in date one and dates you now how do i given two dates get the difference between them?
Well, if these were numbers, you would just say something like this we just subtracted so the difference is gate one money states you and it turns out that this also works just fine and pipe on hand if we print out the type here we were on this put some random numbers didn't matter oh yes so it will work when we actually pass the value.
So first thing let's we're passing now is none and none is not a date you can work with that this is this now is supposed to represent today could even rename it to today potentially so what kind of hearsay daytime dot date and then how do you get that?
Well, you could enter these times are the date, but if you're just looking for the day you could say today right and this will return a new date which is just today so this will work but i kind of would like to think of this is today instead of now, so call it today dual re factor in Python all right now let's, go back and look at this what we get when lisa track these two so that's a lot of days but notice that we have a time delta so this attraction things works here and how do we figure out how many days we can go to this?
I say dt dot, what do we got?
We have day we had days here we also have a month a second, things like that, but what we care about is days and which want to return this here was not quite right.
We'll see there's something a little bit wrong with this but let's, get rid of this printing of the birthday and let's print out just the number of days so we can see how many days have passed today is september eighth, two thousand seventeen let's say i was born nineteen seventy four let's say we'll say august first so should be like forty days or something like that in the past fifteen thousand seven hundred forty four that seems a little different than what i was expecting what has happened this date was first day is the birthday right?
And the birthday is in nineteen seventy four this one his in twenty seventeen so when we subtract him it gives us this huge number for the day, so we need to put them basically in the same year what we're really trying to do is say given a particular calendar year, how many days it between those two places on that one one year so in some sense you might think what we could take this state which is nineteen, seventy four and moving up to two thousand seventeen or this one and move it back to nineteen, seventy four and do that's attraction but really got to consider that might be a leap year.
So what we want to do is upgrade or culture this date and make it move to twenty seventeen so it's renamed these parameters a little bit to just make life a little bit easier for so we'll call this original date and this will call target date.
So i'll say this year's date is going to be a daytime don date and then we can set the year equal to the target date here but then everything else tio original spell original writes better actually it's interesting that pie charm we'll find spelling there's just look it would have told me ok original date here we want month and we could shorten this up just so if it's on the screen dot day okay so now this year is the same month and day as the birthday but it's in the current year not in the past so then i could say this year minus target data now we returning this should be something closer to reasonable to nineteen seventy four let's go with august first thirty eight days so that is thirty eight days in the past meaning we already had our birthday thirty eight days ago okay great.
What are we going to do with that information last thing to do instead of printing out that is to come over here and print to eight days oh my god well come on hand print out something like your birthday's coming in thirty days or you just had your birthday or things like that so we're going to come over here we have a little test will say if days is less than zero the way we computed the difference that means it was thirty eight days in the past so we'll say print something like this all right so you have your birthday some number of days ago and we'll just do a format and pass the days on the other hand will say else if so l if days is greater than zero that means your birthday is coming up we'll put a days.
And if it's not bigger, it's, not less than zero.
It must be zero.
So we can say else today is your birthday perfect let's try this again.
And my charm has told me there's a misspelling of this word.
But you know what hard is going to go with?
Ok, so let's run this nineteen seventy four i was born august first.
Let's say.
And that is, i think, thirty eight days ago.
Let's.
See you had your birthday.
It was negative thirty eight days ago.
So that doesn't make a lot of sense, right?
Let's negate this here just for the display purposes nineteen, seventy four eight one.
You had your birthday thirty eight days ago.
And because it's september eighth.
That sounds right to me.
Let's.
Run it again.
So nineteen seventy four let's.
Say it's nine.
Ten.
So it should be in two days.
Yeah, your birthday's in two days.
All right.
You ready for starting the party?
Nineteen seventy four nine enter today and we get happy birthday.
All right, cool.
So we have all the three different cases in the future in the past and and even today.
|
|
show
|
3:44 |
Our app is finished, but let me show you a really cool feature a pie charm that's going to help you throughout this course and we can also use that to review what we've created so it's good and to remain method here now when you're writing code, it doesn't always go as planned.
Sometimes you need tio inspected state and carefully look at what's happening a lot of times you'll see people talking about the bugging Python code using command line to buggers and other things like that, but we don't do that reason pie time what we can do is actually was just like click or right here in this what they call the gutter and set a break point we could even set a couple of them one here and one there if we go up here and we pressed the bug not the play but the bug it will actually run in the two bugger and notice it's kind of here and it's hit this breakdown and just stopped right?
So is sitting there waiting it would show us the variables that air there and stuff like that when good are console and see the output like we saw before and we can say step over this or we could step into this functional watch it go or we could step into on lee our code so what's the difference here like if we were working with parameters and stuff like that.
And they were being computed as part of this.
But there be computed, say, inside of the pipe on framework and not our code.
That would just skip that step and go in there.
And if you happen to be inside a function, you couldn't get out of it from there.
So let's, just step over this line that will go to the consul here.
Step over.
We see this, you know, maybe we want to learn more about this get birthday apartment that was confusing so we could step into that.
And as we step down, you could see it, prints out a little bit and then asked us the question.
So let's answer nineteen, seventy four now, it's still in this line we've got a step let's.
Say eight and one more step.
All right, notice we've entered these three days here, through values and to the right of the variable here.
It's.
Not that, but it's just cause the variable here it has years.
Nineteen, seventy four months, eight and day is one that's really cool?
And if we go over to the to bugger, we can actually see, like here are all the variables over here we can right click and weaken, say, set value we could set this back to july feeling notice it changes color to say this is not the natural value this is like something you've done to it we take one more step we now have our birthday and it even shows the birthday that the date representation over here so that's pretty cool so let's step along here see your birthday shows it right there do another step it shows what today is like i said september eighth and let's go ahead and suppose we could step in here i guess and watch this go all right well come step along you see there's our time delta if we look at the variable it says dt is a time delta negative sixty nine days so we step along one more time and print out the information and that is our app pretty cool let me show you that these could even be a little more advanced if we'd right click on this i could say i want the condition where a number of days that completion for the stuff right here that's awesome okay this is equal to zero let me take away this break point notice that his little question mark now so if i run this and i say nineteen seventy four for this is not my birthday so this will not hit the break point now it didn't like that did it i guess i got to do that one later here we go no, just.
It just gives, tried over that break point and doesn't do anything because it's, not my birthday.
But if we do it again, i come down here and i enter the right day.
Nine.
Eight boom break point.
So you can do a lot of cool stuff with this to bugger with these break points, things like that.
So i definitely encourage.
You.
Use this of leverages whenever you having problems with your code, and it will help you understand it better by stepping through it.
|
|
|
48:18 |
|
show
|
1:32 |
It's time for app number 4 and this time we are going to build a daily journal, or a diary.
So let's look at what we are going to build, what it's going to look like.
Here is the output we are going to expect from our app you can see as always there's a header notice there is a few diagnostics messages there saying we are loading our data from a file and at the end you can see saving to a file.
So one of the primary focuses of this app is on File I/O and you can see the main part of the app gives you this menu and says what do you want to do list your journal entries, add a new one or exit.
And if we hit "L" you can see we are listing out a few items if we add one it gets appended and we persist this across running instances of the app, in our file.
While that may sound pretty straightforward we are actually going to cover a ton of topics.
We are going to break our application into multiple Python files and use them between each other.
We are going to talk about this thing called live templates in PyCharm, as I already said we are going to focus on File I/O.
We are going to work with the most common type of loop in Python called the for-in loop.
We are going to talk about documenting our code with these things called docstrings.
We are going along with File I/O and talk about how we manage file paths and combine file paths segments in a platform independent way.
We are also going to talk about how we define methods and how we can structure our code so that we can sort of define them in what order makes sense and then use them in any order we'd like.
And last but not least, we are going to talk about complex conditional expressions we've been working with very basic conditionals before, but we'll get a little more involved in this one as well.
|
|
show
|
7:36 |
So let's jump into PyCharm and build that journal app.
I want to start as always by adding our program.py, and we are going to add other Python files and start using them but let's first start here and focus on a few concepts before we move on.
Now, as you guys know, I am kind of fond to sketching out the overall flow of the app in one method and then breaking the pieces into smaller ones.
So I want to define again a main method here.
And the first thing we want to do is sort of print the header remember that is basically the little dashes across the top we are always doing, and then we are going to go around and around in kind of an event loop asking the user what operation do you want to perform I'll do the operation and we'll query for user input again.
So I'll just call that run_event_loop().
So we are going to need these two methods defined somewhere and let's define them first up here, we'll say def print_header(), and we'll just say pass and we'll say run_event_loop()...
pass.
Now, let's one final time sort of do this what I would call the wrong way but we are just going to invoke the main() method as you know if we don't do that nothing happens.
Let's do print() here just I'm going to our header piece.
So that we can see that main() is actually having some kind of effect, no run configurations, so we can generate one by saying right click run and here is our little journal app piece.
Now, to me the order of defining these methods is really wrong.
I like to see the high level bits, like what is happening overall and then the little individual pieces.
So let's take this and naively move this of the top here, there we go, and now our main() we can sort of, as soon as we open the file we see what the major operations are happening in our program here and then down we have the details.
So if I run this though, things are not going to be very happy.
'print_header' is not defined.
So let's just take a really quick moment to think about how Python defines methods and classes and these sort of structures.
In languages like C and C++ there is a compile step and it actually goes to the code and it compiles in it and writes out the machine instructions and the definitions for these methods.
But in Python there is not really this compile step.
There is kind of a tokenizing step but let's ignore that for a moment.
Basically there is not really a compile step so when you execute this block, when you execute line 2 to 4, what you are actually doing is you are defining a method main().
And when you execute line 9 to 12 you are defining the method print_header().
The problem is we are executing main() which depends on print_header() before we have effectively defined it by running this.
So we can solve this problem by just putting main() at the very bottom.
Again, there is going to be another better way to do this that we are going to come back to at the end of this example, alright.
Let's run it one more time, ok, everything is looking good, we got our high level code up here, we are working on the details down here.
So let's work on that event loop part.
Remember, we basically have a couple of operations the user can choose, they can list their journal entries, they can add a journal entry or they can hit X to exit.
So let's sketch out that sort of input conditional logic and then we'll write the details of it.
So first thing we are going to do is let's just do a little print() statement to let the user know hey your journal app is running.
I'll say something like...
What do you want to do with your journal?
And then I want to capture the user input and we'll call that command we are using the input() function like we have been, like so.
And now we can compare this against various options, so the 3 things they could type according to our menu option here is they could type L, A or X.
So we'll say if command == L then let's just print() the L for a moment, and then else if, or elif command == A we'll print A...
now of course, this is just going to run one time but let's just test this really quick.
So we are going to run it says...
What do you want to do with your journal?, let's suppose we want to list it, so if we say L it says great, it echos out the L here if we run it again and this time we say A, it's going to echo out the A, that we put in our print statement.
Of course, we want this to go around and around, so let's write some code here, let's say while the command != X, we want to do this, now of course if we try to run it's going to freak out, because command is not defined, you can see local variable command reference before assignment, so let's just initialize it to nothing to start which obviously is not going to be X after it goes into our loop the first time we should have real input, so let's try this, let's say L, L, A, X, perfect, maybe we could add a little goodbye a bit here at the end, just to let everybody know we are done with our app.
So we pretty much have the structure working, let's write one more bit here, so if I hit L I get the L again, but if I hit something like boo, it just sort of ignores it, it doesn't do anything, let's maybe give him a warning that we don't understand that, so we'll come down here and say elif command != to X or any of the other options, because we are already made this far, I'll say print("Sorry we don't understand...
now notice, we have our opening quote here and the code that is naturally in the string so we are going to do this I told you there were time when you would want to use double quotes for your strings because it makes your life easier, this is one of them, we'll say we don't understand, and whatever the command was, we can put that in little single quotes, like this and we'll say format, ok, great.
So let's run this one more time, we'll say L, A and if I say boo, great!, but there is a few things that are not quite yet right with our loop so if I say lower case l, they will say sorry, we don't understand 'l' because we were checking against upper case L, and things like that, so let's stop this and fix that.
So we can come down here and we can say lower on the string and we want to put that on all these put that here and make sure wherever we are comparing we are now comparing against lower case, now if I can say 'L', 'l', it seems to understand 'a', 'A', but if I put like a space and L all of the sudden it doesn't understand that anymore, so we can do one more thing to sort of make our input as nice as possible, and we can do a strip here, right, so now this should be really robust, we can say like L space space l, A, X, done goodbye.
So I think we have that working well of course, if you care about performance or even code repetition we are doing this 3 times, it might be nicer to do something like this do it once and then centralize that so we'll say lower().strip() and then we can just work with it assuming it's got no white space and it's always lower case.
All right, let's finish sketching out the methods we are going to write and then we'll move on to work with data.
So we basically need two more methods, one is going to be list entries, and I'll just print() Listing..., for now just so we can see that this is working, and we need to define add entry and I'll do a print() Adding..., darara...
like that and let's go over here and replace these pieces, we'll say list entries and down here this will be add entry, one more time, make sure it runs, list, add, exit, everything looks great.
|
|
show
|
6:06 |
Now that we have the structure of our app in place, let's start creating our journal.
So there are many different types of data structures from various simple ones all the way up to rich hierarchy of classes that we could use in Python to represent this, so let's start simple here and as we get into more complex applications we'll do something more new ones.
So for our particular journal here we are going to use just a data structure called a list.
A lists just hold on to a bunch of pieces of data in particular order you can access them in order it automatically grows, so in Python we create lists like this.
Usually you will see it written like that, open close brackets, that will create a new empty list, you may also see it by calling the class name initializer like so, that mean exactly the same thing.
So here we have our empty journal, and we are going to need to work with the data here and we are going to work with the data there so let's pass this along, I'll just go down here and make sure we can pass that data, like so.
Now for listing entries I'll just cheat for a moment and we'll make this nicer but we'll just print it out like so and for adding entries let's do a little bit of input so we'll say something like this text = input(”type your entry”) So we'll get this user input and we are going to put it into our data, and the way we add a new item is we say append text now we probably want to check this input to make sure that something is there but let's just do this for a moment, want to list or add entry, let's list and see what it looks like, there is our empty list, and let's add in new items say this is my first entry, and now let's list it and see if it's there, excellent, there is my first entry, let's add another, say this doesn't look hard at all, woho!!
Now we can list them again, so here is my first entry and this doesn't look hard at all and Python even uses the same trick in the case where we have quotes to put double quotes to make it easier for us to understand, excellent.
You can see we are gathering our data, obviously it won't persist across runs yet, that's coming up but the other thing that is not nice is this is not how our user wants to see it, maybe a programmer but definitely not a user.
So, let's do something nicer on this part.
And that brings us to working with loops.
Now we could write a while loop and use a counter or something like that that would be wrong, it would be what is called non Pythonic code.
In Python we have a really nice way to work with collections and loops and those are called for-in loops so we'll say for and then you name the particular instance of the piece of data you are taking from the collections so I'll call it entry in data and write it like this, so for item in collection like so, and now you just work with it, so let's say we'll just print it out for a moment, so we'll just say print(entry) and let's make it kind of obvious what is going on, so we'll say something like your journal entries, and let's run it again.
Start by listing them, your journal entries, there are none, great, let's add, say entry 1, add another entry 2, and now let's list them, ok, your journal entries, entry 1, entry 2, very cool ha.
Now, there is a couple of things we might want to do, we might want to change the order so we see the newest ones first, that's not the way paper journals work but that's the way a lot of digital ones would work, so let's come over here and we can change the order, there is a really easy way to do that, in Python.
So let's call or little reversed collection entries and you can just say reversed() and you give it a collection and you get the list back in a different order.
So now, we should if we run it, so we get items back in that order so I'll say thing 1, thing 2, when I list it we should get thing 2 and then thing 1.
Perfect.
Now maybe I'd like to number these like your first entry is this, your second entry is that, your third entry is such and such.
So let's go back to our list entries here.
Now in many languages they use a index space loop what is often called the for loop and it starts with numbers and you kind of increment those numbers and that makes it harder to access, harder and more error prone to access the actual entry but it does make it easy to number things, you might think but we have to fall back to the some kind of while loop, with the counter, not true, Python has a really nice way to work with the index and the item at the same time, so we can go to a collection, and we come over here and say enumerate, and what this will do is it will modify the collection that is returned, not just return the items, but to pair them with the actual indexes.
So let's just print this out and see what we get, it's not going to be the same as it was before.
Just to say that three things here, now when we list them out, you'll see we get this sort of parenthesis and the index and the item, now in Python these are called tuples, so let's go back and modify our loop to work with those tuples.
So we can actually define two local variables here, and the index and the entry and Python will take those two items from the tuple and assign them to these two variables so we can also go over here and change this print statement, so this time we shall have the slightly nicer print out, let's say thing 1, thing 2, thing 3, now if I list them, you'll see the 0 to item is thing 3, because we reversed it, the first item is thing 2 and the second item is thing 1.
Now typically people don't use zero based indexes but that's the way Python works, let's fix it up one more time here, let's add one to our index.
All right so it's more friendly for humans.
Your journal entries, number 1, thing 3, number 2, thing 2, number 3, thing 1.
All reversed, excellent.
|
|
show
|
1:14 |
We just saw the for-in loop in action.
And this is one of our core concepts from this application, let's take a moment and dig into it more deeply.
Now for-in loops are the easiest and the safest way to process any collection.
It doesn't have to be just lists, anything that is a collection can be used in one of these for-in loops.
So here we have an items list it has three strings in it, 'cat', 'hat' and 'mat', and we can go through those and print them out in order just saying for item in items: we can call item that variable anything we want as long as it doesn't conflict with the existing other variables and we just make that up on the spot as part of the loop and then we could use it within the loop so we say for item in items print() and then we just work with the item.
Now, that is what you will write most of the time, but sometimes you want a number, an index, a position associated with that item.
And when that is the case, you wrap your collection in a call to enumerate and you get back both an index, and an item, and you can work with those.
You can either grab it as a tuple or as I am doing in this particular example I am projecting or unpacking that tuple, into the two variables idx for index and item for the actual item.
|
|
show
|
5:55 |
Our goal is to add file I/O persistence to our journal, but I feel like our app is getting too complicated and this code that we are writing is actually doing several things it's both interacting with the user and dealing with the UI as well as kind of managing this journal data and it would be much better if we could separate those.
On one hand, we have this journal thing that takes care of itself, and we have our UI bits and they can just interact with that journal.
One really nice way to do that in Python is to create a separate module or a separate file.
So let's call this journal, here we are initializing, we are creating this journal and this is ok, it works but remember, we'd like to initialize this from a file where we have saved an existing journal load it up, create a new one if the file doesn't exist, all that kind of stuff.
So this is going to get way more complex instead of this one line here.
So let's move that over into our journal code and we'll call it something like load.
So over here we are going to define a load method, and let's suppose it's going to take the name of the journal.
And, it's counter part, we are going to write later, it's going to be saved and again this is going to take a name.
Now for the load, let's just temporarily write the code to basically do what we were doing before, just create an empty journal.
And we are storing the journal data as a list so let's do this and just to remind ourselves, let's add a little to do.
Ok, so we'd like to populate this from the file if it exists, similarly, we'd like to save something here, we are going to need a name, we are also going to need the journal data, I want to pass it in.
So, how do we access these methods which we are going to fill out with more interesting bits in a moment, how do we access those over here?
First of all, let's just try to write it, we could try to write load and there is obviously no load here, we could write journal.load there is no journal either, so obviously like before, when we were working with say the random class and various other pieces, we need to import it, so the way you do this is the same whether you are working with a built in module, an external module you've got from PyPi, or just one of your own scripts that you are going to load up and consume.
So here we are going to say import journal, you can see PyCharm is saying yep, that seems like a good thing to do and down here we can say something like journal.
and there is our load and our save.
So we can write it like this.
Now notice, we said import journal, format for a moment, we said import journal and down here we say journal.load.
So this style is referred to has name spaces, there are other ways we could import this, I could've said something like this instead, I could've said from journal, import and I could just name off the things like load and save and then down here I can just call load without the journal, right.
Now, it depends on the method you are working with, it depends on how frequently you are using it but my preference is definitely to use this style because it's extremely explicit where it comes from.
If I just call load it's not entirely obvious where it comes from.
Now, there is a way to write this code where it's even less obvious say, we just want to report everything from that journal module, and just put a star like this, from journal import star, that would be as if we named every single thing that was in there, and that's even less good, it's less explicit and you can run into the possibility if name clashes like imagine 2 different modules both have the method load, which one are you going to get?
It gets complicated, so I would say avoid this one, prefer this one sometimes this one is totally good too, so we are going to say import journal, and we are going to come down here and say load and you can see that PyCharm is saying we want to give it a name so let me just create a variable for our named journal, we'll can it journal name, and it can just be something like default.
Something along this line, Ok.
And down the end, let's make sure we do a save so we'll say journal.save and we'll give it a name, and the data.
So the save really does nothing, remember it was just pass, this load really is just returning the new instance to the list, but that should mean it really just works exactly like before, so let's add first item, second item, and list them out, hey it looks like it's working, and it exits.
Ok, so we were able to use the separate file to store some of our logic in here if you come down here you can see there is something I am not a really big fan of down here with this add entry, is we are directly working with this data structure, I'd much rather move this over into the journal pieces.
However, not this part because this has to do with the UI, so instead of writing this code I could write journal.add_entry, and just pass the text here instead of this, I could of course go over to the journal file, write add entry just to kind of as you see it here but let me just show you a cool trick with PyCharm.
So PyCharm knows that the journal.py file relates to this name space here and it knows this does not exist, so if we hit alt enter, it'll say hey, would you like me to write this method for you over here?
Yes.
please, so we can actually just use this alt enter trick to sort of avoid a lot of typing in and get everything exactly like we were planning on there.
So remember this was like this, and I forgot to pass the journal data so kind of unfortunate, now this probably would be much better model as a class, but hey, it is what it is.
Ok, so I think we moved the logic that manages the journal over into this journal.py module, now we haven't written the real complex code yet, but we are about to, before we do, let's just review this whole import story, this is one of our core concepts.
|
|
show
|
1:12 |
The concept of importing modules and packages is very central to Python and it's what really provides the ability to have Python be this so called "batteries included" language where if there is some sort of feature that you need, there is a few lines of code you write and boom, you have it.
Typically, it've to import some amazing package that's going to solve your problem.
Now we saw there is three ways that we can import, there is actually more than that but I think this is enough to get us started.
We can say import, name of the package or module, and that both allows and requires us to maintain the namespace down below you can see os.path.exists, or we can import individual entities, functions, variables, classes and so on out of the modules like from os import path, that's ok, it's not amazingly good, but it's not bad, and that lets us write style too, we could take the lazy way and import everything out of the os regardless whether we are going to use it, that's not such a good idea you kind of get into name conflicts and it's not very obvious what you are actually getting from that module and where are you using it below, that would still let you write style to code below.
|
|
show
|
10:30 |
So it's time to add persistence across running sessions by saving and loading our journal from files.
Let's start by first saving our journal to a file.
So I want to come down here and any time that you work with file I/O, you always start with this built in called open.
So here we can pass the file name by default you see the mode is R that's read only text but we are going to change that to W for write, and we are not really going to worry about the other options.
So we need a file name that goes here and we want to have the mode to be write, because we are going to create or replace a file, we are not going to just read one, and we'll catch this and I call it fout, from file output stream, you can call it whatever you like, it's just a variable.
The first thing we really have to do to make this work is actually get this file name here, so I could say, let's actually have a little folder, that we are going to save our files into...
a directory, so let's call this journals, let's suppose we want to put them in there, and I could say something like this, I could say filename = './journals/' and then there is this name remember we are passing in this journal name here, so let's say name, and let's suppose on the end when I have a 'jrl' file extension to indicate our custom journal format or whatever, it's hard it's going to be worthy of that but here we go.
Now technically, this would work on OS X and Linux, it may not work on Windows, right, remember, Windows uses backslashes to separate directories, not for slashes, but more important than that, if I am combining multiple directories, say if I am giving a like a base directory equals something like this '~/', and I am giving a relative directory = data/temp.
If I am given these two and I want to build up the full path, I would say full_file = base_dir + and I need to use a little separator here because you can see on this part there is no separator up there and then we'll say + relative directory, like so and if this is passed me well do I know whether there is this little slash on the end do I need to put it here, do I check, all those little nuance details in Python we don't actually need to deal with any of that and we get OS independent path operations, all I am using is this module called OS.
So I want to come up here, I want to import OS, and down here, I can say instead of doing this thing, I would like this file to be os.path.
and we can just say join, and put a whole bunch of little paths to segments together, so we can say ./journals, the name, like so.
Now, it won't really join the extension, right, it will think that's a separate file, so it's up to us to add the extension but other than that, it will take an arbitrary number of segments and put them together here and let's just print would load form this here, ok, so when I run this, and we exit, you would see what load I suppose that should be saved to, but here we have our ./journals, and we can even go one step farther make this very explicit, right, if I am going to write that to a log file I need to know what the working folder was, at the time, right, so I could say path.absolutepath, now if I run it, exit out of here, you can see user screen caster source Python jumpstart...
darara number 4, right this is the full path and this works perfectly well in all of the major platforms.
Ok?.
So this is going to let us work with that folder right there, and let's go and say just this, saving to here, and put a little indicator that that's not proper app output at the terminal there, all right, so now we have our file name, we can go and open it, and we can go through our journal, remember our journal is this list of drinks, say for entry in journal data, and say fout.write and I can write this out and say entry, now, if I say this way it's not quite going to work the way we hope, and let's go ahead and leave it for a moment, say fout last thing we want to close this you always want to close the file handles as soon as you are done with them, so let's run this and I'll show you better way to deal with this close scenario.
So if I run this and let's add an entry, say thing 1 and an entry thing 2, and we exit, you'll see that this was saved and if we go up here there is now this file and if we open it up, these are actually all in one line, probably not the best.
Now, we probably should talk about the format just a bit anyway, we want to keep this super simple, we are going to get to more interesting file formats later, for our journal, we are just going to write one entry per line.
But notice these are all in the same line so we need to actually add a new line character so backslash end and then when we rerun this say let's add a few entries here, and exit, now if we go back to our journal, you'll see perfectly misspelled thing 1 and thing 2, excellent.
Ok, so it looks like our file I/O is working, now we want to make sure we call close if there is some kind of exception or error or early return in this obviously a very small bit of code, but even it could have an error, however, the more complicated the code gets the more helpful this what I am about to show is going to be if we return without doing this, well that's kind of a problem, right, so what we want to do is in Python there is actually this thing called a context manager, that will say for this block or suite of code, I would like this object to exist, and regardless of how you leave it through an exception, early return, falling to the bottom, I want you to cleanup, and it's up to the item being used to determine what cleanup means, and you can imagine for files that's like close and flesh the buffer.
So the way we do that is a little different than this, we'll say with, it's kind of reversed, we'll say with open file name and same parameters, as fout, and then we do a block like so.
And we won't need this anymore, because as soon as we leave this block right here, it's defined by the indented section it's going to automatically call close.
Ok, so let me clean this up a little bit.
Here is our final save, we are going to use os.path.absolutepath to get a full path from our relative path we built up with join, and in OS we do have these for slashes in here, so maybe it would make more sense to write it like so, there we go, that way it doesn't depend on for slash being a particular separator, so here now we have a proper OS independent style.
We use our context manager, our open method to get the file stream, and then we just call write, and we write out the text, if we want the binary version, we would say WB for write binary, and that's that, we are going to do something very similar for loading it.
In fact, so much so that we are going to use exactly the same line of code, I kind of feel like that should be like a method or something, so let's go over here and I could write a method in this file or I could go to PyCharm and hit control T and say I'd like to create a method let's call it get full path name something like that, and you see down here it's returning this, and we're calling this file name here, let's just do that same thing up there.
So, this one is different there, usually it will be a file but not always, right, not the first time, so we can say something like this, if os.path.exists file name then we want to load it, otherwise we are going to return this thing.
Now, we are going to need to initialize that and maybe fill the data maybe return the empty version, but let's do this here, so now just like before, we are going to use our with, but we are going to not use the w, we are not trying to write it we are just going to read it, and I'm going to call it fin for file input, and then we can just say for entry and fin.readlines(), and that will just read in the lines and let me just print, would load and put a little print here entry.
Now this is not going to work out quite as good as we would like, but let me run it, so it would load thing 1 new line, thing 2, new line, it turns out that in Python, when you do the read lines the new line character is still stuck on the end, right that back slash end that we out here, or just naturally the file, is on here, it's not removed, so we can come over here and say strip(), maybe we want to be careful and just say rstrip() off the end, now if I run it, I would say would load thing 1, thing 2, those are exactly the entries we were hoping for.
Ok, so instead of doing this, over here we say data.apend(entry).
Now if I could just load it up and I say list, of yeah, I forgot the rstrip(), so let's say rstrip() to remove the white space off the right hand side, now have a look, perfect, still misspelled, thing 2, thing 1 exactly the right order because remember, we are reversing it.
So let's go and actually add a few more, we'll say add this is fun with files, and add one more, today I saved this.
If we exit out you'll see saving to this location and if we would rerun it, and just do a list, boom, there are things we wrote, back in our file, you can see right, there is the chronological order we created which is of course reversed of what we are showing.
Beautiful, let's go back and just review what we did for load, it's even easier than save, wasn't it, ok, over here in journal we can remove our TODO, so we create an empty journal bit of data and then we get the file name using our full path function right, ospath.
absolute path and join and then, we ask if it does exists let's load it, using our super simple format of one entry per line, and then we are going to return either the populated or empty data set and on the way out the door we create a writable file and we just write out the format that we were expecting.
Perfect.
|
|
show
|
0:50 |
So that brings us to our next core concept, file I/O.
And here you can see we have a list of three text items and imagine we want to just write these to a file.
So, if we’re going to use that file stream, briefly and then when we exit this little segment we are going to close it, we'll use this context manager so we'll start out with, create the item as variable name.
So, in this case with open and we pass file name and w for write text format, and we want to say as fout, for that's the variable I am calling it output file stream.
And then we just do whatever we want to do to write to the file, so in this case we want to write these three items on three lines and this text file so we just for-in loop over them and we say fout.write() the text and as you saw, that does not put a new line on there as it should not, so we have to add it with this \n.
|
|
show
|
1:53 |
Let's come back to our event loop for a moment.
So when I run this app, I can list the items, and I can an add an entry and hit exit, but if I hit like enter, you can see I get kind of this weird like hey we don't understand nothing, maybe I'd like an empty entry that just mean hey let's just exit, save and exit, so we can change our code really quickly here, first of all, let me change this to be something other than a non-existent empty pointer, Ok?
We want to come up here and we want to have a complex conditional that says when the command is not the x and it's not empty so in many languages, you use symbols like the && or double pipe to say and and or, Python we just say and or or, ok, so if we want to say the command is not equal to x, and the command has some value in it, I could say like it's not equal to none, or the length is greater than 0, or something like this, but in Python remember the truthiness of collections and strings are collections are if they are not empty then they are true, and if they are empty then they are false.
Maybe that's a bad choice of words of being empty like this.
Ok, so this will almost work if we run this, and when I say list then enter, it says we don't understand that and then boom, it exits.
So we need to add another bit down here, say if the command is not X, and there is some sort of command entered here, right, which is not empty, then save this error.
If we wanted to say it's not X and there's nothing in here we would say and not command.
Right, so there is ways to combine this with parenthesis, and all the boolean logic you can do in regular programming.
All right, so here let's list the entries, and hit enter boom, goodbye.
|
|
show
|
0:56 |
These complex conditionals are one of our core concepts from this app, so look at this code here.
We have a variable X, and Y, and Z.
And we would like to run something if X is somehow false, either in number or boolean or something like this, and the combination of Z is not 2 or Y is false, not Y, right, so here is how you write that in Python.
We say if not x and (z != 2 or not y) Now I think complex conditionals in Python make tons of sense, and it's quite straightforward, the one exception to this sort of English language version of boolean logic where you say the words and or not is the not equal to or greater than or less than parts, so they sort of use symbols but other than that think English when you are doing your conditionals.
|
|
show
|
3:22 |
Let's make our journal module just a little nicer to work with.
So if I come over here and I look at this journal and I hit F1, you'll see that PyCharm can kind of guess hey I think this is returning a list but it's all that we really get.
Now, if I look at some built in like print it will give me decent documentation like hey here is all the values, here is how you typically use it, here is a link to the documentation and so on.
We would like our journal code to look like that and it turns out it's super easy.
So if we come over here and we enter a string, on its own line right by itself here, that will actually be the documentation and there is a format that lets you describe the return values and the parameters and expectations and everything.
So, one way that we can create the string, let's just take a break for a minute, call it S is we saw we could put single quotes like this, or we could put double quotes like this, but the third way is to extend that to be sort of a string literal across multiple lines I could say S = ' ' ' and hit enter, right, this is multi line and close it off like that, and that's actually a string with one, two new lines and then this and then a new line, so you can have this sort of richer strings in that way and that is the style we are going to use, to document this here.
So we are going to come over here and say ' ' ' and when I hit enter, PyCharm is actually going to help me out.
Notice it says, ok it looks like you are trying to add a docstring, so we are going to have a parameter called name and we'll give you a chance to describe that so this will be the base name of the journal, to load, and it can talk about the return value a new journal data structure populated with the file data, something like this and then at the top we have the general method summary and so this method loads and say creates and loads a new journal.
So let's go over to our program and do that little trick again we had, so I can come over here and I can look at this, and hit F1 and now it pulls it up and says this method creates and loads journal and guess what the name parameter, this is the base name of the journal and the return value that's a new journal, Woho!!.
So now you have proper documentation.
Now if we come here to Python console and we say import journal, I can write things like help (journal.load) and when I hit enter you can see just like we got into UI there we also get the text version so if you are working in the console or the terminal the sort of standard help also uses the same docstrings.
Now I did notice that PyCharm was suggesting that we should use 3 double quotes, so let's go and make it happy, and do that here like so, perfect.
Now, of course in the real app we go right this for save and all the other pieces, we can even do this for the module itself say this is the journal module, and now if I were to come down here and do that import again or come over here to the top and look at the journal, I can ask for help and it actually gives me module level documentation as well.
|
|
show
|
0:26 |
Docstrings are a core concept of this application, let's look at it in detail.
So here we have some method, and we saw that if you just put a string at the beginning of a method on its own line without assigning it to a variable, that becomes the docstring.
The recommendation is to use three double quotes and separate this so it's nice and readable onto new lines we saw that there is actually a format to talk about the various parameters as well as the return value.
|
|
show
|
6:01 |
Finally let's briefly turn our attention to this line 51 here, main().
So I told you calling main like this is not ideal, but I've been pushing off talking about why that is and it really has to do with when your code is used as an external module to other code, right?
Either code you write or if you are writing a package, people who consume your package and let's see what the problem is.
Let me just create a program 2.
Now over here in this program 2 suppose we would like to do this print header thing up here at the top.
This print header method, it's unlikely you would import that whole thing just to get this but go with the idea.
Imagine I want to use this print header method up here so I would like to do something like let's just say import program great, and I could say program.print_header().
Now let's make this super obvious what is happening in terms of the program flow, so I'll do a print, and say about to import program, let's call it step 1 down here we'll do a step 2 program imported, and then maybe a step 3 printing header and we'll add a step 4 down at the very end here, done...
done with program.
Now, if you think how we want this to work it should print about to import program, it should perform that silently, then it should print program imported, then it should print printing the header and we have the little header part, remember it's going to say --- journal app--- and then it should say done.
And it should just run without the user input, conceptually the way we are trying to run it.
However, that is not what is going to happen, so let me run this really quick, now it says about to import program and we see the header, ok, that is a little early, but let's see what else is going on, now it says what do you want to do with your journal, and I can apparently interact with it- weird, ok, that was pretty unexpected, if I hit exit it exits and then it says ok now the program is imported so all of this from here to there, that is literally line 2 of our program, program2.py what the heck is going on?
Well, remember the way Python defines classes, functions, variables and so on is it literally executes this file, top to bottom.
It says ok we are going to execute importing the journal, all the stuff that that means we are going to execute defining main, we are going to execute defining print header and so on, so it's all looking fine until we get to here, this actually has side effects, this actually starts that event loop that we saw going around and around so in order to use this, I kind of need to not call it when we are importing so here it worked fine, now that I commented that out, it worked the way we expected.
So, in Python there is a convention that allows us to say if you are trying to run this program do some extra code but if you are trying to be a library, just define the features the variables functions, classes and so on during import.
It all has to do with this special implicit variable called _name_ there is several implicit values that we can get out of this module, one is the actual full path to the file that is executing, that can be really handy when you are trying to find something relative to the current module, and this name, this one actually is the convention we are going to use, so let me just run this, remember we are running program 2, we are going to import this, you'll see that this should be program and this should actually be the full path to program.
Here you can see that's the __file__, and here you can see that is __name__, let's make this super explicit.
So I'll put __file__, __name__, then I will run it and you will see the files that program is that could use a space, but you get the idea.
Ok, now let's go over here, let me add those spaces, and let's go over here and change the app that we are running, we are going to run program itself, so when I run program, the file did not change but check out what happened to the name, it became __main__ so the convention in Python is when you are running a script if it's the thing the target of execution, its __name__ is set to __main__.
If on the other hand it's being imported, it's just the name of the Python file typically without the extensions.
Ok, so how does that help us?
Well, down here in main, we can do this, we can say if, __name__ is this main then in that case, that means we are trying to run this program, right now so let's kick off the main().
On the other hand, if it's being imported, if this is program rather than main(), just don't execute our code, just let it define the functions and variables.
So if I run program, that's this file right now, it should be main, so it should act like our original app.
Right, it lets us list entries and exits.
On the other hand, if I am trying to import it, whereas I am running this program, it's not going to be called __main__, it's going to be called program, so let's run that, now notice file is of course what it is, name is program we did not call main() so it worked exactly like we wanted, 1 about to import program, boom- done importing, printing the header, so if we did not have this little print bit here, it would act exactly like you would have initially expected program 2 to run.
|
|
show
|
0:45 |
That brings us to our final core concept, this __name__ variable, or sometimes referred to as __name__.
So we saw this __name__ variable is available in all modules and Python scripts, and the convention is it's set to the name of the file, if the file is being imported, otherwise it's set to __main__.
__main__, right?
And we use this trick or this convention to determine how our should execute.
In our example we decided do we run the main method in the event loop or do we just define the functions and actually skip running the main because we are not the program we are just being used by another program.
|
|
|
59:22 |
|
show
|
1:16 |
Hello and welcome to app number five.
This time we're gonna build a weather client; an application that you run, and you can ask "what is the weather" in virtually any town in the world.
Now, this is not a simulated weather client.
It's not going to just give some random thing like "maybe it's gonna be cloudy and 72".
No, it's going to go out to the Internet and actually get the weather at that location.
So we're going to do some really, really cool stuff.
Let's see exactly what we're going to build.
It's gonna look like this.
It'll have a little header like all of our apps do, and it will say "enter your location" and you put in a city and a country, or just a city within the U.S., or a city and a state in the country U.S.
However you want.
Here, we're gonna say "I'd like to know the weather in Seattle" and right now, when I ran this, it said the weather in Seattle is 54.48 degrees Fahrenheit and the forecast is clouds, specifically scattered clouds.
And then I asked for Stuttgart, Germany, Deutschland, the weather in Stuttgart, Germany is 47°F and overcast clouds.
Cool, right?
So this is what we're gonna build and you'll see, While it may sound complicated to go out actually to the Internet and get all this information with Python, it's gonna be awesome.
|
|
show
|
1:15 |
If this is the first time you're taking this course, you could basically disregard the next two screens here.
But if you've already taken this course and gone through this chapter, you should know that we've completely redone it.
We've re-created it from scratch.
Why?
Well, we use this awesome place "wunderground.com" and we would go there and we'd get the weather from "wunderground.com".
However, it's been a couple of years, and Wunderground has dramatically changed their website.
So the presentation that we were giving was not really something you could re-create.
So, what have we done?
Well, I've built a new weather service, and I'm hosting it over at Talk Python.
So, "weather.talkpython.fm" we're going to use this weather API to get real live weather data for our application, Okay?
So if you've seen this course before and you've already gone through it and you're wondering "hey, why did all these videos change?" this is why.
It's just to refresh and keeping things modern.
If you want to get back to the old code, go to the GitHub repository for the course handouts, the course demos, and just switch branches to "Old Weather APP chapter" and you'll see that code.
Otherwise, you're also going to see the latest code on the GitHub repository.
With that out of the way, let's go build our awesome weather app against our brand new shiny weather API.
|
|
show
|
2:25 |
What are we gonna learn in this chapter?
Well, a ton of amazing things.
The primary focus, the thing that we're going to get the most out of with this chapter, is working with external packages or libraries.
There's a place called PyPi, or Python Package Index, and we're going to use this tool called "pip" to access all the libraries there, and we can just plug those right into our application, and there's literally hundreds of thousands of libraries out there that we can use, from computer vision to machine learning to working with stuff on the Internet and calling API's and so on.
So that's what we're really going to focus on, using these external libraries to make working with this weather data on remote servers super easy and straightforward.
Python is often referred to as having batteries included.
That means it has so much contained within it.
Rather than writing a bunch of functionality, you could just grab little pieces and put it together.
A lot of times that means working with something from the standard library.
But even more than that, this PyPi place with hundreds of thousands of libraries are also right at your fingertips.
So if you need to build almost anything, there's probably a library that either does that, or it will help you much more easily build that thing.
When we talk to our weather service, we're gonna create an http client, that's basically a major part of our application, to go out there and do a web request.
We're gonna use API endpoint.
That API is gonna return a certain type of data called JSON, which is very, very popular in API's.
So we're gonna work with JSON and Python.
In order to install our external packages correctly and isolate them on a per application or per project basis, we're gonna work with these things called "virtual environments".
The actual library that we want to use to call our API, to build the HTTP client is something called "requests".
It's one of the most popular packages in the world, in the Python space.
I believe it's downloaded about seven million times a month, so yeah, no joke.
It's very popular.
And finally, we're gonna be passing multiple pieces of data from one function to another and we'll see that there's this concept of tuples, these data structures that hold more than one thing.
We'll be able to pass multiple things around using these tuples.
However, they're a little bit clumsy, so we can use this more improved version that comes from the Python standard library called "named tuples".
You're going to get a ton out of this chapter, and it's gonna be a lot of fun to see how all these pieces fit together.
|
|
show
|
1:59 |
Well, let's create our application, our project, to build our weather client here.
We're gonna say "new project", and we're gonna create it in the "weather_client" folder on my desktop, then I'll move it into the GitHub Repository.
It's gonna be using the Python 3.9 interpreter, just the system one for now, we'll talk about what that means in a minute, and I'm not gonna use this main script, welcome script, that PyCharm just added to its functionality for new projects.
Over here we have our empty project and I'm gonna add, as we do for all of these, a new Python file and we'll call it "program".
Then just to get the little button lit up here, let's go ahead and, once it's done indexing, right click and say Run.
Run it, and of course it does nothing, but you can see it's now ready to run with the hotkeys or with that little button there.
So what are we going to do in our program?
Let's just write these out as steps.
Well, we're going to show the header and we're gonna get the location requests, or the city, where do you want to get the weather for.
We're gonna convert what they type in into more useful data.
What they're going to type is something like, they're gonna enter, you know, maybe Portland, Oregon as a whole single string here.
And what we want to know is both Portland is the city, and Oregon is the state, and the default is that the country is the United States or something like that.
So we're gonna convert plain text over to data we can use.
Once we have that data for the city and the country and state and all those sorts of things, we can get the report from the API, the actual weather API that we're going to use, and then the last thing, we're gonna report the weather.
What we're gonna get back from the API is structured data, and we want to take pieces of that data and turn it into something that's friendly to humans.
Like "the weather is gonna be 57 degrees and sunny", Not "here is some data structure that the API returned".
So that's what this last thing here is going to be about.
So these are the steps that we're gonna have to take to build this simple application.
|
|
show
|
2:57 |
Before we get into writing the code that goes behind each one of these steps, let's do just a little bit of organization.
Remember, I always like to structure my program with a main method that has the high level steps of what it's going to do right the top.
So let's write that, and we'll put these steps here.
Now, notice there's a little error right there, if I make it go up it'll probably move to the bottom.
No, it still stays there.
The error is that the main method doesn't actually do anything yet, so let's just make it do something.
"Hello from weather main".
Okay, so now there's a meaningful main implementation here so that the structure of the Python code actually works.
However, remember, if we run it, there's no output.
What happened to our "hello weather main"?
So that's because we're not actually calling it, right.
That's just defining a function that if it were to be called, it would say "hello from weather main".
So if we call it like this, that's great.
However, this will not allow us to reuse the functionality of this script or this application.
So, like get a report from the API?
Nope.
We will not be able to use that function because if we import this, it's going to actually, as part of the import statement, processing, is going to run this.
So, recall there's this convention, "if __name__ == '__main__'" that means it's being run explicitly, not imported, so we should do the same thing.
But if somewhere else we import it, it'll just define the functions and not try to run the program.
This actually is so common, this convention right there, in most real Python programs, that PyCharm actually has what's called a "live template" for it.
So if I type the word "main" you can see there's this thing that's a function, that's just what I wrote above, but there's another one down there with a little hint that's like "if name equals main" hmm, maybe that's interesting.
So if we hit Tab here it's going to expand that, that live template will expand that, and then we can call the actual function we're looking for, Okay?
And it doesn't matter what this is called.
This could be called something like "entry point" or whatever, right?
It just means if we're being run as a program, run the thing that we're thinking of as the top level.
Here we go.
Okay, So let's look over here.
And if we look actually in the editor, you can see there's a whole section of these live templates for all the different languages.
If you're doing SQL or you're working on Flask or you're doing Javascript but most relevant to us right now is Python.
And down here you can actually see "main".
So this is the template that we were expanding, and I actually went and created my own that will create the main method and write that.
So I can just start from an empty file and type "F-main" so you can create new ones as you see fit.
So, for example, the one I created would do this part right here.
Saves me a little bit less thinking about it, just get started and go.
So these live templates are super helpful, and now our program is ready to be run as an application.
|
|
show
|
2:02 |
Well, let's get started writing the pieces we got to write to build our applications.
So the first one is, we'll just have a print or show header, I guess we'll call it "show Header" and we'll come down here and write it like "def show_header()", super easy.
And remember, all the headers they look like, we'll add a couple of sections, and we'll say, whether client or weather app or something like that, we'll roughly try to center it and then maybe just a new line at the end.
Let's get rid of that part.
Now if we run it, you can see our little headers coming out the bottom.
Great.
The next thing we need to do is get the actual location.
We'll call it "location_text".
Remember, this is not the location data yet.
This is like human friendly plain text, right?
Stuttgart, Germany or Portland, Oregon, U.S.A., something like that.
So we're gonna just get that from the user.
We can just do a simple input here.
So we can say "where do you want the weather report" and put a little message, and then we'll put this space here so they're not typing directly on that text, and let's just print out "you selected", Now we could go like this, "location text", but if that came back as none, for some reason that would crash.
And we could also do ".format (location_text)" and put that there.
But now that we're using Python 3.9 we can actually use what's called f-strings.
We put an f here, notice the color of the curly braces change and we can just put the thing we wanna format like that.
So here's a little cleaner, simpler thing what we might do.
Okay, let's just see that this works.
Okay.
Where do you want?
I'm thinking Boston, U.S., You selected Boston, or I'm thinking maybe Boston, Massachusetts, USA, you selected Boston, Massachusetts USA.
Alright, well our getting this information from the user works.
Next thing to do is convert it over to plain text, hand that over to the API and then convert the API response over to some kind of human readable thing like "the weather is whatever it happens to be".
|
|
show
|
2:14 |
Let's have a quick look at this real time weather API that we're going to be using.
So, there's a lot of weather API endpoints out on the Internet.
In fact, one that I think is really cool is "openweathermap.org".
So if you're going to really try to create a live weather application, go use theirs.
This one is just for educational purposes, has limited data and so on, but it will give us the answers generally that we're looking for here.
So this is a HTTP RESTful API.
And what that means is we just make HTTP requests, like standard web browsers do, and what we get back typically, not always, but typically is some kind of formatted data called JSON.
So, what we can do is we can go over here and say "weather.talk Python.fm/api/weather" and it requires that we pass some things.
So if we don't pass a city, it says "missing field: City" so we could put "Boston", Sorry, we could put "city = Boston" like this, there we go.
And apparently, the weather is mist.
Some kind of mist rain, I don't know.
It should be category: rain, description: mist.
Who knows?
Here you can see, we're getting the weather.
It's right now 13 degrees Celsius because down here somewhere we have our units are metric.
If we wanted this to be in more US style we could say "units = imperial".
Now all of a sudden it's 55°F instead of whatever it was Celsius.
Okay, So what we do is we make a regular request and we get data that looks like this back from it.
So this is called JSON, and we'll see how to play with it in Python really soon.
But it's a nice, simple format That's very, very natural for us to work with over API.
And if we come back here, you can see that we have a city, right?
This should have a question mark.
actually.
It says that we have a city equals whatever we want, but we can optionally pass a state, if you want to disambiguate say Portland, Maine, from Portland, Oregon.
We can pass a country we wanna disambiguate Portland, Australia, from Portland, USA.
If you don't specify a country, it defaults to the US, Just that way.
I could type in short city names and it's still gonna work.
Then also you saw we can pass in units as metric, imperial or standard, and it defaults to metric.
So this is the weather API that we're going to be working with.
|
|
show
|
3:51 |
We saw that our API returns JSON, and this is not by coincidence, this is the most popular data exchange format on the Internet, period, when one application talks to another, not just in Python, but across all the languages.
So let's just go play with this idea really quickly so that we know how to work with it.
So let's create a new thing, we'll call it "example_dict" for dictionary.
We'll see where that goes in a minute.
I'm gonna use my fmain magic to find the program.
It's gonna run here.
So what we want to do is we want to talk about this data structure called a dictionary, and a dictionary looks like this: it has curly braces, it has a key, so maybe this is gonna be city, and then it has the value, the current value of the city.
So this is like a variable type of thing, and then the value might be Portland, and then the state, maybe the state is going to be Maine.
And then, we could also have other, more interesting things in here.
Like we could have a details and the details could actually be a list of cold, snowy, winter, I don't know.
Maybe it's wintertime right there when we're doing this.
So these are the things that we could have in there.
In order to work with this, first of all, if I just print this out, and run this, p, d, there we go, if we print it out, look how this looks.
See that right there?
If we go back over to our API here and we click on our example, we look the raw data, those are basically identical.
The only difference that you'll find is Python's representation have single quotes if you print it, JSON has double quotes.
Other than that, they're basically the same.
Yeah, there's other technical small details like, for example, datetimes can't be represented in JSON documents where they can in Python dictionaries.
But, think of this JSON stuff as like a subset of these Python dictionary data structures.
If I want to get something from it, I can come over here and say like the city, we could print out just the city, notice Portland comes out.
If I asked for something that's not there, like country is not here, and I use the square brackets to access it, I get a KeyError.
There's nothing called country in here.
So, oftentimes a better way to do this is come here and say "get", so just do the brackets, if you say "get", you'll get either, you'll get the thing that you are asking for or none.
You can even pass a default.
So if they don't ask, they don't provide a country, let's default it to the USA, and that way we'll get the default value here if this does not exist, but if a country were to exist, like country is Germany, say now, Germany would come back, in Deutschland, okay?
And then also we can modify these things.
We come over here and set the country to, set it to Australia, and then we could ask for it again.
Notice, now it's been set, and actually if we just print the whole thing out again, we get, over here our country is Australia.
Cool, right?
What we can do is we can actually just take this, and it's better to take the pretty printed version just for your own well being, and we can go over here and say, "the weather is, paste", you just indent it.
But Python already understands that.
So, if we want to get, say, the weather, let's say the forecast and the temp.
So first we've got to get the forecast and then we get this little baby internal sub-dictionary back, which we can then ask for the temperature which will give us that.
So we could do a print, go to our weather and say "get the forecast", then we're gonna say "get the temp" and that should just print out 64.08 degrees Fahrenheit, and it does.
Okay, so this data structure is what we'll be exchanging with the API, we're gonna be passing these JSON strings back and forth, but you'll see that Python has it, because they're basically subsets, sub-types, the functionality of Python dictionaries, It's incredibly easy to convert that response over to a Python dictionary, as we've explored right here, and then just directly worked with that.
|
|
show
|
6:04 |
Recall now that you've seen the API, what we have to do is we have to pass over to the URL: city equals city value, country equals country value.
But that's not what we're getting here.
We're just getting plain text that looks like "Portland, USA" we can't say the city or the country is "Portland, USA", It's got to be broken into pieces and understood, right?
So that's what we're gonna do now.
We're going to convert this plain text over to data.
So, let's just put for now, location, I'll write that here in a second, and for a second and then we'll improve upon it.
We're gonna go over here and say "convert_plaintext_location()" and this will be the location text.
So we gotta go write this function and it's gonna be some cool string manipulation.
And let's just print out as an F-string "the location equals the location", what we got back.
Alright now, First of all, if for some reason we get no location at all, we don't want to try to process it.
We're just going to return nothing.
So if there's no location specified or it's empty or something like that, then we just want to say "you know what?
There's no location contained in empty text".
This is false if its both either none or just the string quote like this.
But it could just be like a space or a tab or a return character sometimes it also doesn't mean much, so this strip will take that away.
And we'll say "If there's not anything here, we're gonna, either it's truly nothing or it's effectively nothing, we can't process that, so there's no location".
So now we're in a good place to say things like location text equals first; maybe you wanna get the lower case version of this and we also don't want any extra spaces there.
So we could be very restrictive about what we allow people to type up here.
We could say "you must type city comma state comma country".
That gets a little tricky because not every country has a state concept That's easy to type that goes over to our API.
So you could say, Well, you just type city and country, but then lots of countries have duplicates.
Like there's at least three Portland's; there's a Portland, Oregon, Portland, Tennessee, and Portland, Maine.
So we're gonna need a little bit of flexibility here.
We have basically three cases: is it just the city?
is it the city and the country?
or the city, state, and country?
Now, regardless of which of those we have, let's just print out "location_text" and run that real quick here, and we'll just return a "location_text" back for the moment.
If we run this and I put Portland, USA, notice we're getting "Portland lower case usa" because we did the lower and strip and if there were spaces, it would take those way away as well.
But what we need to do is we need to somehow get Portland and USA, and Python has really cool mechanisms to take a string and break it apart.
So let's say parts is going to be "location_text.split()" split means split into parts, and then you give it a character on which you want to split.
So we'll say comma, and now let's just print out parts here, see what we get back.
So if I say Portland, Oregon, USA like that, look now we have a list of three strings: Portland, Oregon, and USA.
And there's comments in here, but these commas are not in the string.
These are just the list saying I have things that are in it, right?
The list represents itself with those separators.
There is a space right there and a space right there that we need to deal with.
But other than that, we have the three parts.
So here's the deal is, do we have just the city, the city and the country, or the city, state and country?
And the way that we're going to figure that out is how many parts do we have?
So if the length, how many parts are there, equals one, well, then it's just the city.
So we're gonna say "city equals nothing, state equals nothing, country equals nothing".
And then, in this case, we're gonna say "city equals parts of zero", right?
That's how we get the element out of the list, we say the first one, zero based.
And then we say "strip()", because we want to actually remove those spaces.
Remember, Oregon had a space on the front if they typed it and things like that.
And then we want to say "country equals US", two letter abbreviation, and actually, we can just put that as a default up there, Alright?
On the other hand, if the length of the parts two, then they've said the city and the country, what we have assumed here, so this is gonna be the same, but the country is going to be the next one in the list.
Parts one, if they specified it all the way out and it's three, then it's city, state, country is the format that we're expecting.
And if it's not one, two or three, it must be some kind of error, so we're going to return "None" again.
Alright, Now, instead of returning this location text, we're gonna deal with that in a second.
Let me just print this out here and now we can show things like "city equals city, country equals country".
And this is exactly, I guess I'll put state in here, this is exactly the style of format of data that we're going to need to pass it over to the API.
Alright, now let's run it.
Let's just say Portland, city is Portland, State is empty, country is US, perfect.
If we say Portland, USA, We have, city is not what we're looking for but country is good.
What did we do wrong here?
Oh, it's because I didn't I didn't put a comma, I just wrote it wrong.
Here we go.
City is Portland, country is USA, and then let's do the full thing: Portland, Tennessee, USA let's say.
Now we've got city is Portland, State is Tennessee, and country is USA, perfect.
So we're breaking this apart correctly, but now we have this challenge.
Like you could see we can return one thing, we have to return more than one thing.
How do we get all this stuff back?
So we're gonna address that in just a minute.
But just breaking the data apart is already done, we just gotta figure out how to pass it around in our program.
|
|
show
|
2:18 |
Well, here we are.
We've got our city, state, and country, but our function over here, it just returns one thing: a string.
Or something else, a number or whatever.
Functions just return one thing, actually, sorry, this one, but same idea: returns one thing.
How can we take all that data that we've gathered, the city, state, and country and return them back?
Well, what we can do is we can create something called a tuple.
So a tuple, we'll call this "loc" for a minute, the way we create this in Python, we would just say "separate things by commas", and then we could actually return that one thing like this.
Let's see what we get if we do this.
We say Portland, Oregon, USA.
Look what the location is.
It looks a little bit like a list, but it has parentheses instead of square brackets, which means you can..
it means it's a tuple.
but the limitations are you can read the values of it, but you can't add to it and stuff.
But other than that, you can kind of think of it as like a list a little bit.
So this is cool.
And then up here, we have to say we get our one location, but what we really want is the city, state and country.
So on the other hand, on the reverse side, once I have a tuple, I have this really cool thing that I can do in Python, It's called tuple unpacking.
So I can say "city, state, country equals location".
And then let's just print out these things here, see what we got.
If We come over here in Portland, Oregon, USA, look what we get when we print this out right there.
That's those three things just with a space between them, that's how print works if you just put multiple things into it.
But they all came back into city, state and country.
We could even put a break point right here, at the debug, and notice city is Portland, state is Oregon, Country is USA, perfect.
Now, We don't need these intermediate steps, Actually.
I just wrote them so it's really clear what was going on.
So what I want to do is I just want to say for the moment, city, state, country equals that, and the tuple unpacking will just happen automatically, and then down here we created the location and we returned it.
We can also just do you like that.
We don't need this anymore, okay?
So one more time.
Here we go.
We got them back exactly as we had hoped.
So how do we return multiple values from here?
We just return a tuple.
|
|
show
|
2:43 |
Now, there's a problem with these tuples that we're returning here and let's go back and actually put this into a variable for a minute as we saw say this t tuple is this.
Now when we go to it, notice it knows it's a tuple because you can count how many things are in it and so on.
And the way you access it is kind of like a list or something.
So you could say, "Give me the country would be that, the state would be this, and the city would be that".
Well, that's not at all obvious that it's city, state, country.
What if it was city, country, state?
Because state is optional.
There's no communication here about helping us with this, Okay?
So this is a real big drawback of these tuples and passing them back like so.
They don't know what goes where.
So Python has a couple of mechanisms, or a couple of data structures, that make this way, way better.
We could use classes which require a lot of stuff for us to talk about.
But if you're looking for just a short, sweet little thing like this, what we can do over here, is we go over here and say "import collections" and then we can define a special tuple that gives it extra properties.
So I can say a "location = collections.namedtuple()" and notice the first thing we put us the type name, which should be the same as a variable name, right?
So this is what we program against, and this is what it thinks its own name is, so they should be the same.
And then here we just put what we think it has: city, state, country, and the order here is the order you specify them.
So watch this.
We can go over here and use this type down here.
Instead of passing back a regular tuple, we would say do the same thing, but notice the location is gonna take a city, state, country, so we could do like this.
There's the location, and I wanna leave this here for you so you have it.
But let's say, we're going to return a location like that, and advantage just to tie it back to this example up here, now we say "t2.", look, country, state, city.
It knows the things that it has, doesn't matter what order they're specified in the tuple, you address them by name, not by order, which is way, way better.
So I'll just put "t2.city" and comment those out so you have it left in code there.
Alright, so when this comes back, we could still do this trick of unpacking it, but let's just go down here and say location again, and now we can print out location, which is gonna be looking nicer as well.
See, it's location, the city is Portland, state is Oregon, country is USA.
So these named tuples make it much easier for us to come back here and program against.
We know it has a city, country, and state.
It doesn't matter the order and so on.
So, really, if you need to pass multiple things back from a function, tuples and tuple unpacking is cool, but named tuples are probably what you actually want to work with.
|
|
show
|
3:24 |
Now that we have our location in structured data, not just plain text, we're gonna be able to call our weather API.
So let's say "data = call_weather_api" and we're gonna pass this location over.
And as we've been doing, let's go over here and write this function.
Well, we start out by having our URL, remember we go back to our example, it's gonna be "weather.talkpython.fm/api/weather" and then stuff up here.
So let's go and work with it like this.
So it's gonna be this base URL up to here and then city, what goes in here is going to be location dot..
Check out the named tuple, so cool!
Now the state is tricky, so I'm gonna put the state to the side for a second here, and the country is going to be "location dot country" like that, and then the units, I'm gonna just hard code it to Imperial.
You can hard code it to metric or if you want to pass metric or imperial and let the user decide how they like it, go for it.
But to me I'm just gonna leave it like this because you gotta pick one, and I'll leave it like that.
Alright, so now it gets a little bit interesting, because if they pass a state, we want to use it, but if they don't pass a state, same state equals blank, might throw off the API and freak it out.
So what we want to do is we'll just do a little test, if location dot state, then we're going to say "url plus equals ampersand, make that an f-string as well, state equals state, like that.
Location, not state.
There we go.
Okay, So if you pass a state through our structured data, we're going to tack it on with an ampersand.
The order of the query string, these key value pieces, this whole thing is a query string, the order, the key value pairs in the query string, doesn't matter.
So if it goes at the end with units in the middle, doesn't matter at all.
Alright, so now let's just print out the URL, would call, and with PyCharm, here's a cool thing with f-strings: I can type curly brace, and notice the curly brace is still stringy, it's still green, it's not actually interpreted as anything, and there's no f at the front.
But if I start to type of variable like URL, it's still not an f-string.
But if I hit enter, it puts the f at the front and completes it.
So that's pretty cool.
Let's just, and I'll just say for now it's supposed to return something, so I'll say it returns "None".
Functions always return None anyway, but whatever.
Say "Portland, US", like that.
The country and the state need to be two digits, or two characters.
So check it out, we would call Portland, US with imperial units and let's click it, see what we get.
Look at that.
Broken clouds, 65 degrees.
A wonderful fall day here in Oregon.
Now let's call this again, but this time let's get Portland, Tennessee, actually, yeah sure, Portland, Tennessee.
Like that and says, notice the state says TN at the end, and here we have Portland, US, Tennessee and the weather is 60 degrees.
Looks like it's also nice.
Little more clear skies, but not the same forecast to be clear.
Cool, huh?
This is great.
We're actually structuring what we need to send over to the API perfectly.
But now what?
This "would call" needs to turn into "I actually went to the Internet and got data and understood what came back", but we're off to a great start.
|
|
show
|
1:46 |
Let's talk about the two group together paired concepts of accessing these external libraries.
The first is, Where are they?
The second is How do we get them installed?
So PyPi, pypi.org, or the Python Package index, is where these packages live; these extra libraries live.
And at the time of recording, there's 267,000 projects.
That's 267,000 other libraries that you could choose to install into your program and start working with them.
Whether you've got to create a little GUI app, gotta build a web application based on some web framework like Django, all of those things live here, and the one that we want is we want to be able to go and make a web request to an API.
So the other side of the coin is how do we access PyPi and get the stuff installed and manage them for our application?
Well, that's what this command line tool called "pip".
So we're gonna work with a library called "requests" that lets us do the, well, HTTP request over to the server to get that JSON document then convert it to a dictionary to give the weather report, and in order to use it, it doesn't exist in Python.
It's a separate thing.
It works with Python, but it's not part of Python.
So in order to work with it, we have to install it into our current environment, alright?
And the way we do that, we would just say "pip install requests", sometimes you might want to throw in a "--upgrade" in case it's already there, go ahead and give me the latest version.
But you'll see it when it downloads it.
Actually it already downloaded that version, so it didn't need to get it, but you would see a progress bar go across as it downloads if it needs to then it installs that package and all of its dependencies that it needs, maybe the dependencies of the dependencies.
So everything is ready to run for you, and it gives you a message like "whatever package you asked for, the current version is installed".
|
|
show
|
5:55 |
Now we're going to use one of these libraries from PyPi, one of these external libraries, and we're going to use pip to install it, but that means we need to address one more concept.
If we install this library into our global operating system environment, we could be setting ourselves up for big trouble.
What would that be?
Well, imagine application one depends on an older version of a library.
Over time, Somehow that's changed in a way that's incompatible to the program, but you want to use a new application, build a new application, that uses the new version of that library.
So one app needs the old version of the API, the newer app must have the new version of the API.
How could those both exist at the same time?
Well, if you just pip install your library, it's not going to be possible.
You could install one and then uninstall it, and so the other, and that's not how we should do things.
So let's open the terminal here, and we're gonna go over and go to my desktop, make a directory just called "test".
Now, remember if I "pip install requests", whatever version it gets that's gonna be controlling what application libraries are available for every app.
We don't want that.
So what we can instead do is we can create what's called a virtual environment.
So the way we do that is we say "Python -m venv".
So have Python run the module library within itself, called "venv" for virtual environment, and I'm gonna put it into a directory called "venv".
Those air a little bit weird, confusing, but it's a convention that's used all over the place, so I don't wanna stray from it.
Now, we want to run Python 3.
There we go.
So now if we look in here, you can see that there's a bin folder and in the bin folder there's an activate script.
So right now if I ask which Python, on Windows you would ask "where Python", it's the System one, okay?
But if I say "dot", this dot means apply to the current shell rather than run in a new shell, I must say ".
venv/bin/activate".
Now notice my prompt changes and I say "which Python", now it's this one, okay.
It's this current one.
If I say "pip list" and ask what things are installed for this Python, nothing.
If I run Python and I try to import requests, it'll say "Nope, we only have pip and setup tools as our external libraries, nothing else".
So if we get out of here, now we can "pip install requests", installs locally along with its dependencies, and here we have those and we can now run Python, import requests, and we could even do things like "requests dot get" to Google or wherever.
And if we could print out, you could say the response is equal to that last thing that came out.
Here's the text that apparently is Google.
You can see at the bottom has got some Googley JavaScript, looks like quite a mess, but nonetheless, we went and got that off the Internet by installing it.
So here's the deal, this application now has this isolated local virtual environment, and when we upgrade or downgrade or install things, it's just this list.
So, for example, I can type "deactivate" to get out of this virtual environment, and if I type "pip list" now, it just shows me all the random stuff in my global Python.
We can use these virtual environments which we create by saying Python, or Python3, depending how you run your Python, -m venv venv and it creates it and off we go.
So we want to do the same thing for this application.
So right now you can actually see what version of Python we're on, 3.9 down here, and if you click "interpreter settings", it will show you that it's the global one.
This is, you know, user local, this is global one.
We don't want that.
So what we can do is either through over here we can click, "add" or that's what you were actually after you would just go over here, say "add interpreter" and then what we can do is we can pick a location.
So here we've got the base thing, the thing we're going to run with the Python -m venv, and then we want to go to our project directory.
Convention is to go to the folder at the top level of your project and there create a folder, the virtual environment, called "venv".
Now, PyCharm doesn't help us much this time, but we just type the "venv" on the end, we hit ok, it's gonna create the virtual environment for us.
Then we come down to the terminal and notice once we open a fresh copy of it, it has this venv and if we ask "which Python" on Windows you have to ask "where", notice it's the right one.
It's out of our virtual environment.
And if we ask, "what's installed", shows us that nothing is installed.
Again, we could come over here, we click this "interpreter settings" that would show us again nothing is installed.
We could actually install requests over here like this, and we could just click "install package", but I'll show you an even simpler and shorter way.
If we go over here and just try to use "import requests", PyCharm is going to say there's a problem, this is not going to run.
And if we try to run it, you can see "no module named requests".
But check this out.
If we put the cursor on the squiggly bits and hit "alt + Enter" it says "Do you want to install this package?" Why, sure!
Please do!
notice at the bottom?
It just installed requests.
And now, if we run it again, hey, look at that.
It works!
And we go back to our interpreter settings, now requests is installed right there.
Cool.
So that's how we can manage virtual environments from within PyCharm, but you already saw that you could just use the terminal or the command prompt to do them directly.
What I found was in the early days, I would do most of my stuff in PyCharm because it helped a lot, but as I got better and better, I didn't.
There was just little things like I wish it worked slightly different, so I'm gonna use my own way of doing that.
So now I don't use PyCharm very often to create the virtual environment, but I did find it super helpful when I was getting started.
|
|
show
|
3:33 |
Let's talk about virtual environments one more time, and I specifically want to cover how the Windows version is different, specifically different than doing things on Mac and Linux.
They're both great.
They both have the same concept in the same functionality, but because of the way the shells work, mostly, and the way that Python gets installed, partly, you gotta issue slightly different commands.
They're very easy once you get used to them, but at first it can be a stumbling block.
Okay, so let's review the Linux and macOS version.
So what we're gonna do is we want to create a virtual environment in our top level project, the top directory, the root level directory of our projects.
Sp we say "Python3 -m venv", so the "-m venv" means run the command within Python 3, and then the orange "venv" is the directory name.
So those happen to be the same by convention most of the time, they don't have to be.
Once that happens, it's created, but you don't actually have access to it yet.
You have to activate it.
So you have to say "dot", meaning apply to this shell, or source, and then "venv/bin/activate".
That's going to change things about Python, it's going to change the path, it's going to change the prompt, and when you run things like "pip List", you'll see you no longer have the entire system, whatever is installed for the Python there.
You just have a fresh, empty Python, and the only packages it has are the tools that you need in order to install more packages.
pip and setuptools.
And then we can ask "which Python" and it says "It's the one here in this project".
So it's header's a project directory called "weather_app", You'd see it's the one in "venv/bin/Python" and then off you go to install various things.
One thing you might consider, didn't happen this time because Python 3.9 was just released, but often what happens is pip itself is out of date and any time you try to use pip, it'll complain to you that it's out of date.
This is like 80 - 90 percent of the time you're pip is out of date.
It's not worth going into why that is, but when you create a virtual environment, just get in the habit of always upgrading your base level tools, pip and setuptools, so you're working with the latest one as you install other things.
So here's what you do on macOS and Linux.
This two parts right there are mostly, it's basically where it varies for Linux versus Windows.
Sometimes the Python 3 versus Python varies, but that depends on how you set up your Windows machine.
Speaking of windows, over here, same thing.
Probably you just run "Python -m", depends.
Then, once we create our virtual environment, we don't activate it with the dot, We omit the dot and instead of the bin folder, there's a scripts folder.
So you say "venv\scripts\activate" why there's a difference there, they haven't unified those things, I have no idea because it seems trivial to do so, but they haven't done it for years and years, So this is how you do it in Windows.
Just remember, not bin, scripts, pip list, exactly the same.
Once you've activated it, your prompt changes, it has the venv in there again.
Now you can't ask which on Windows, That's not a command, but a command that does work on command prompt is "where".
So here you get the same basic answer.
It's the version of Python in the virtual environment Windows style.
So scripts folder, Python.exe, you'll also see other Pythons.
But when you type Python, it's going to show, the where command shows you all of them in the path, you're going to run the first one that's found in the path, which is the blue one from the virtual environment.
Again, you want to upgrade pip and setuptools for exactly the same reason.
So the big difference, these two.
Remember, it's no dot, scripts not bin, and then "where" instead of "which" Python.
That's it, they might seem like a bit of a stumbling block it first, but trust me, once you get used to them, you just do it the way that you do it on any OS and off you go.
|
|
show
|
3:08 |
It might seem like we're a long way from being done with this application.
We've talked a lot about some honestly slightly complicated topics with virtual environments and dependencies and manage them and all that kind of stuff.
But we're actually extremely close to being done, as you will see.
So let's go over here to the "call_weather_api".
I've have already written "import requests" at the top, so we can jump down here.
Remember, we've already got the location as a named tuple, and we're using it to put together the URL.
So here we just said, we would call the URL.
Well, let's not say we would, let's do call it.
So we're gonna get a response from the server.
The way it works is you go to request you say, "get the URL", done.
We've now downloaded the data from the Internet.
How insane is that?
So let's just print out what we got.
If I say Portland like that, response 200.
200 in the Web is okay, success.
So that's good.
There's all kinds of things here.
One of them is the text.
So if we print out the text from Portland, what do we see?
Oh oh oh..
broken clouds.
How about that?
Super cool.
Now we could use the libraries that understand strings and convert them to Python dictionaries by parsing the JSON document, but requests already knows how to do that.
It has a function called JSON.
So if we write that and we type "Portland", it looks the same, Right?
But remember, Pythons representation of dictionaries has single quotes.
Our Web server's returning double quotes.
So what actually came back is just data and it just when you print it out it looks basically the same.
So check this out.
We'll say "data equals response dot JSON".
Now, Before we go through that step, we got to make sure that we actually got the right values.
So what we need to do is say if for some reason we got something other than a 200 ok request and JSON response, we just want to say "sorry we couldn't get you any weather".
So the easiest way for us to do that is to ask if the "response dot status code", that's that number, is in a set of things.
We could do a bunch of if statements or we could just say, "Is it in 400?" which would mean bad request, 404 like if you asked for a city that doesn't exist, 500, the server crashed.
We could return "None".
In fact, we could say, basically, it's not 200, return an error, right?
So here we'll print out "error", and then let's just put the "response dot text".
A lot of times when there's an error, you actually get an additional message in the text, so this might help us debug what's going on.
Like, for example, if we go and ask for the city such and such, it says "error, what we got back is 404, city not found".
Okay, so once we make it this far, we think we're in good shape, and this might, I'll leave it like this, but you might just say "not equal to 200".
So the last thing I want to do is I actually want to convert this response into something that's easier for us to work with.
But we're already off to a pretty good start.
What about Boston?
Whoo hoo!
Boston!
Alright, we returned it.
We haven't printed out anything yet, but our final step is going to be to convert that into something meaningful we can show to the users.
|
|
show
|
4:52 |
Recall what we got back from here with our data.
Let's just print out Portland.
We got all of this stuff, like our weather with our details and our categories and whatnot, what I want to do is get more structured, simple data that we could work with.
We did that up here with locations, and let's just actually do the same thing for weather.
So we'll say whether or weather report or something like that is gonna be another named tuple and it's just gonna have city, let's actually, you know, we could have city, state, country, or we could pass over just a location object.
So let's say it just has a location and then it has units.
Is it metric or is it imperial or whatever?
And then the temperature and the condition.
So if it has those things, we'll be able to create it down here.
So all we gotta do is come down and, it's not, we can print it for a minute.
I'm gonna go over here and what we need to pass is the location which is already passed in, so that's easy.
The units, well, the units we're passing over are right there, so you could pass it however you want.
And then we need the temp and the condition.
I'm gonna do a short little version here, and then we'll do an improvement in a minute, but let's return the weather.
So we'll say the temp is data, now remember, how do we get this out?
It actually is kind of all over the place.
We want to run.
Remember, there's weather, which has, like, the description here, so that's gonna be interesting, I'll put that up here for us.
The other part that we care about is what's called the forecast right here, it's wrapping around oddly but like this.
So what we actually need to do is we need to get, go to the weather and we're going to get some stuff here and then go to the forecast and then get the temperature, so the temperature is gonna be go here.
I'm going to get the forecast and from the forecast, we're going to get the temp, and then the condition, same type of thing.
Get the weather, then we want to get like we could get the description right.
But I want to say, "category : description", So what we could actually do is come over here, like thi, and say we're gonna put this into a little variable, and I could just say, create an f-string, say it's gonna be "w dot get category" and then outside the curly braces "colon w dot get description".
now, the description I wanted to be a capital first letter.
So a cool way to do that is to say, "capitalize", makes it like a sort of like a proper sentence.
And then here put a period on the end.
So let's just print out the weather before we go any farther here, see what happens.
If I say "Boston" got a bunch of stuff printed there.
But this part, this is the interesting part.
Look how cool that is!
So we got the location is Boston, bo state specified, US.
Units are imperial, temperature is 53 degrees, and the condition is clouds, overcast clouds.
Super, super cool.
So the final, final thing we've got to do is just show that to the user in some meaningful way.
But this is starting to look complicated, and it shouldn't be complicated.
There's not much going on here.
All we need to do really is we wanna actually, maybe even do a little bit more like so when it said that the temperature was 59, we're not using the units either, are we?
So what I want to do for the unit is we're gonna need to get the, whether or not it's imperial or metric.
But you don't say the degrees or imperial, you say they're Celsius or Fahrenheit, so we're gonna have to do a little conversion there.
And what I want to do is I wanna make this something that we can put away and make it simpler.
So let's say, we're going to convert this data over to a weather report so we could go and write a function, I could come down here and type "def convert API to weather object" right?
Or PyCharm actually has our back here, which is pretty cool.
We can go and highlight that, right click, refactor, extract method, and you give it a name, hit enter, and check that out.
It wrote that right there for us.
So that's cool.
It's, if I deselect it here, you can see that it's helping us out here a lot by cleaning things up.
We don't need to print the weather.
We could, in fact, just return this directly.
Now, if we go back to our calling the weather API, it looks simple again, and that is because it's simple.
It was just getting a little bit complicated there.
So now, call this "weather" maybe, up at the top, and, print it out one more time, It's not quite the end, but you can see nice print out of everything.
All we gotta do is convert that into something we'd like to show the users, maybe get rid of that, and we'll be set with our weather example.
|
|
show
|
7:40 |
The final step here is to report the weather, but in a nice way that users went to see that they expect.
So instead of just printing out this random data structure, we're gonna go and do a print statement.
So I'm gonna say print, little f-string, the weather and just put little curly braces there for some for a bit because we don't have it done yet, in some location, like Portland, is so many degrees, whatever the scale, Celsius, Fahrenheit and the condition is cloudy or something along those lines.
Take that away just for a minute so it doesn't crash.
So what we need is to get stuff like the location.
So it would be easy to think we could just put like the location there, but remember, the location is this data structure, and it'll look weird.
And it will say that the state is None some of the time.
So what we need to do is get some location text.
Let's say location_name, so get location name and we'll pass the location.
Let's write this little bit here.
And really, it all comes down to whether or not we have the name of a state or no state.
So we'll say, "if not location.state" So there is no state specified then we just want to return a string that says "location dot city comma location dot country".
Now there's a small problem here, It's gonna be lower case city and lower case country because we converted it that way, remember?
We did all that lower stuff to canonicalize it to make sure we always sent the lower case thing to the server?
Well, we wanna undo that.
So now we can say "capitalize" here on this one.
And then the country, remembers it was a two digit code which probably just wanna make that upper case.
Now, if it's not the case that there is no state, so there is a state, we just want to add it in there, and it's gonna look like this because the state is an abbreviation as well, so we'll just put state there, and that's it.
So let's just print out, comment that out for just a minute, on the way, location name.
If I say, let's say Seattle, Washington, US, The weather in, look at that, Seattle, Washington, US.
fantastic.
Alright, So what else?
What else do we got to do to make this work?
Over here We have It's the temperature.
So we're going to say the temp is easy.
That's just "weather.temp" and the scale, let's just put it for a minute "f", we'll fix it in a second, let's say temp, scale, and then the condition as well, gonna be "whether.condition".
Now, some of these we could actually just in line them like We don't need a separate thing for this, I guess, probably for the temp, either.
But for the scale, we're gonna need some help.
Let's go ahead and run this.
See what we get.
Make sure it's working.
Boston, the weather in Boston, United States, is 53°F, clouds and overcast clouds.
And I think in this condition where we created that, we'd already put a period.
I didn't want the period there I don't think like there.
Try one more time.
Boston.
Look at that.
Perfect.
The weather is 53.29°F.
The last thing that actually make this legit is to determine whether that's an F or a C.
And that's actually one of the easiest things we've done.
So let's go over here and we'll just say "if whether.units equal imperial else the scale is C".
So let's change where we're calling that just to make sure if we put metric over here, we should get the right answer.
Boston and you spell it right, Boston, here we go.
But are we, we are not getting right here, are we?
Let's see.
And I think that's because when we called this, I hard coded that into two places, which is never a good idea.
But down here, we just wrote this right.
So I think we wanna put units in here and pass it over.
We don't have to pass it.
It's with the data.
So here we just do "data dot get units".
Alright, try again.
Boston.
Look at that!
See, now if we go and just change this to Imperial and Boston again, perfect.
There it is.
Our little scale is being shown.
We can do one more thing on the scale to make it nicer.
It would be better, I think, if this happened on its own place.
So we can come over here and make a method called "get scale", something like that.
This writes this little function so we don't have to worry about in-lining that, makes our code up here much simpler.
Okay, so we're pretty much there.
We can trim up some of those comments.
Like, do we need to say show the header?
show the header.
No, those basically say the same thing.
Convert Plain text, says the same thing.
Call the weather API.
Report the weather.
This could even be a function that we create if we want called "report weather" that might be cool.
Create a method called "report weather".
Here we go.
And now how much, how many comments do we need?
We've got show header, what do you want to do, convert to plain text, call the API, report it.
Super, super clean way to have our program working here.
There's one final piece that we want to take care of to really put a nice cherry on the top.
Now check this out.
If I type Boston and miss the T, Boston, something crashed.
This came back as "None" and we tried to get the units which made it crash right there in the get scale.
What we need to do is there's a couple places like if they don't pass the, we were not able to create the location, right?
They passed in nothing or something, we want to print "Could not find anything about the location" location, text let's say.
Like that, and then return.
Right.
So now if we come in here and we just hit enter, couldn't find anything about that.
There's also the same things happening right here.
So if not whether, if it comes back as none, we'll say "could not get the weather for this place from the API".
Now, if I put "bos", somewhere we're still printing that out, right?
But it says could not get the weather for bos from the API, but if I do "Boston", perfect, that works.
We got one stray print in here before we call it, totally good, maybe we want to keep it there, maybe we don't, right probably belong in some kind of logging or, I don't know, we could get the status code and try to make sense of that and so on.
But I'm just gonna put it like this and call it done.
So even though it sounds like quite a big deal, what we've done is pretty straightforward, right?
We just do a little bit of string work to get the data in the right format, we call the weather API using requests which we've installed into our virtual environment using pip, and then we just take that dictionary data that came back and put it over and here, we just call request.get, make sure it was successful, JSON, and we're done.
Beautiful.
This is a really, really cool app, and I would consider it a real app.
It has really error handling, it has real behaviors, super cool.
Working with live data off of a live API, gotta love it.
|
|
|
27:25 |
|
show
|
1:28 |
Hello and welcome to app number 6.
We are calling this one The Cat Factory, as in LOL Cats, it's going to be fun.
What exactly is a LOL Cat Factory?
Well, it may be a bit of a strange title but it's going to look like this.
So we are going to have our standard header and then I happen to know of a web service that will give you a random LOL Cat it's very important service on the internet we are going to use it to return data, to our app.
Previously in the weather app we had used Requests to make HTTP Requests against the weather site and actually get the HTML's text.
But now we are going to work with binary data, and the cat pictures and then we are going to write that to a binary file which is quite different than using text files.
Finally, you can see at the bottom it says launching output folder and finder so we are going to start a subprocess from our app and if this returning on Windows, it would say launching output in explorer.
So what core concepts are we going to cover in this app?
Well, it's going to be a little bit shorter than the previous two marathon apps, but we are going to come back and talk about HTTP clients again, this time with binary data and we said we are going to save those to binary files and we are going to use sub-processes launching other applications from within ours to actually interact and display those pictures to the user.
We are also going to come back to this OS independent file and folder management and do a little bit more than we had been previously doing with our other apps.
All right, so let's get to it.
|
|
show
|
6:41 |
All right, back in PyCharm, it's time to build that Cat Factory.
So, like always, we will add a new Python file to get started and in our convention, we'll call that program.py.
Let's go ahead and define our main method as you guys should be familiar with by now so we'll create our main(), and down here let's do a few things I'll just sketch this out in comments so let's start by saying print the header, as usual, next we are going to get or create output folder and then we'll download cats and then finally, let's go and actually display cats.
Now just so that we can sort of see things running I'll just say hello from main, just for a moment, so we can get everything executing just fine and remember, we have the live template in PyCharm that gives us the correct pattern for launching our main method only if it's being called as the program itself so here we'll just say main(), nothing too fancy there, and let's go and run it once to setup the run configuration boom, all right, so everything is working, the first thing we've got to do is print the header, that is pretty straightforward, so we'll just say def print_header(), and you guys are really familiar with this by now so let's say print() and it's this sort of thing, do a little duplication, and we'll call it Cat Factory.
Something like that, how about with capital O, Ha?.
So we can go over here, let's get rid of this useless print, and let's go up here, we'll call print_header().
So the next thing we are going to do is we are going to work with our output folder, so let's try the method down here, called get_or_create_output_folder().
All right, so now we are going to use our OS module and the path class so we are going to come up here and we can have PyCharm do this or we can say import OS and down here we have to pick what our folder is called.
So we'll say folder equals, and let's just call it cat pictures, something like that to make it really obvious, and then we'd like to create this folder first we'd like to figure out what the full path is and then we are going to either create it or just return it if it already exists.
So let's say full_path = os.path.join() and we could start by joining just this in the folder, and let's just for a moment print that out, you guys have already seen let's make sure we call it so that something actually happens and if we run this you can see we have ./cat_factories and before we were actually able to come up with the full path and that was actually working out pretty well, we were saying os.path.absolute path, and we got our full path to where this folder that doesn't yet exists was.
However, the fact that we say .
really is a comment on the working folder, not relative to this particular Python script, so I would like to have this folder called cat pictures, right next to my program.py file here, we could do something different, let me just do a print statement here, you saw that there is __name__ that is a special string that is defined right, we are using that right here, and it's __main__, it's literally what it is the way we are running it now or it's going to programmed.
But there is another one that we could work with called file and you can see that here is the file that we are currently in, the module that we are currently running in regardless of what the working folder is, so we can actually get the base folder, we can get it like this, we can say os.path and just call dirname() and give it this file here like so and instead of using this absolute path and all this stuff we can just go back and say we are going to combine base folder with our little local folder, and you should see we have the same output but this time it's sort of independent of the working folder.
So we now have our folder, the next thing to do is create this folder but only if it doesn't exist yet, so we'll say if not os.path.exists give it the full path this is the folder that we are going to work with but maybe this is a file, maybe somebody has created a file called cat pictures here, we'd like a folder as well so we could say or and come down here and say isdir() and let's put a not here, Ok so now we want to create this directory, add full path and if this had many levels in it that didn't already exist you would have to kind of do this recursively, but we'll just say os.mkdir(), remember it's not on the path it's actually on the OS model directly and we'll give it the full path, and we would kind of like to know did the thing find the path before or did it actually create it, so let's do something like this, we'll say print creating new directory at and we'll put the little location here, so only when we are creating the directory shall we see this print statement, but either way we should return the full path, now let me leave this out just for a second to show you some PyCharm goodness so if I come over here and I create this variable I want to say this could be the folder, like this, right away PyCharm says no no no this is a bad idea, it looks like you are trying to store the return value a function into this variable folder, yes we are, but if you look carefully it will say this get or create output folder method does not return anything, in some languages this would crash or not compile or something, but in Python there is an implicit return value for all functions, every function returns a thing, but if you just omit their return statement, it's only going to return none, so it's not super useful.
That's kind of a warning that oh, woops we forgot to return full path, regardless of whether we created it.
Now let's just do a print really quick, we'll print so found or created folder and we'll just put a little folder, ok, now let's run it, if you look over here there is no cat pictures directory, but if I run it it says creating new directory here and then it says found or create a folder there yes, and now you can see PyCharm's automatically refreshed and found hey, there is cat pictures here, if I run it again, we should only see the second found or created but not the actually creating line.
Perfect, so now we don't recreate it and actually that would have crushed had we tried.
That's the first half of our os part we've created this folder, we are ready to put our binary data into it, the next thing we have to do is go download some cat data.
Before we do that, let's just hit a little command alt+L to do some cleanup and have our code look nice and fresh.
|
|
show
|
6:45 |
The next thing to do is to download the cats.
So let's write a method called download_cats().
And we are going to need to pass this folder that we created.
So down here I'll make some room for the method, so just for a moment let me write pass so we can talk about it now one possibility is I could write the logic here entirely in this method and I could maybe write a few other methods within this program.py file, and that would solve the problem.
Downloading the cats from the internet and saving them to our folder but that would make it not very easy for other people other programs that maybe also wanted to download cats so as we have seen in other apps let's go and actually put this into a separate module so we'll come over here and add another Python file, I'll call it cat_service and in the cat service we are going to have a method, we can call it whatever we want, let's just say something like get_cat(), and it's going to take a folder to save the cat too, and it's going to take a name for the cat, for now let's just say hold on, we are not going to write the code here but use it back in this area, so back in the program file, we'd like to call that get cat method form our cat_service so we would say something like cat_service, and of course, just like any module we have to import it, now we could go to the top and write import cat_service, or as you've seen PyCharm will do that for us, watch the line numbers change when I hit enter, there you go, at the top it's imported, cat service just like any other module and so now we can say get cat and pass the folder and we are going to need to pass a name and I am just going to leave that empty for a second.
Now before we go and implement this cat_service, let's talk about how we are going to get these cats, what we're going to do with them, so we are going to get eight cats and let's make this configurable so I'll create a variable called cat count and let's set that to eight, we could make this a parameter we passed to this method but we'll just sort of put it here locally, now I'd like to loop over something and for each one of those I'd like to go and get a cat, well, what do I look over, I say for i in...
what?
so Python has a couple of cool methods that will build what are called iterable sets that can be used in for in loops and more Pythonic structures such as list comprehensions and so on, out of just parameters, so we don't have a collection but we can go over here and say for i in range from 1 to cat count.
Now before we get into this part, let's just do a little print and see what we get back so this is going to return an integer, I want to call it i, and one of the things we might ask is this going to go from 1 to 8, maybe the end point is inclusive, maybe it's sort of upper bound but not included, let's just do a little print i to find out and while we are at it, let me show you another cool little trick here, so we'll run this and you can see it's 1 to 7 so 1 to 7, actually that's not what we wanted, we wanted 1 to 8 so let's put a +1 for a cat count here, excellent now it goes 1 to 8 but notice, when I run this it's kind of like all up and down like every print is a new line so there is something you can do on the print statements where you can figure the endings, so I can come down here I can say end = normally the end is like a \n anew line character, but I can say the end is just going to be a comma with a space, and now when I run it we actually just print this out on one line, separated by a comma and a space, ok, so it looks like we got the right number of cats working, now the next thing to do is to come up with the name so that should be pretty easy we won't be too creative here, we'll just call this lolcat_ and here we'll just put the number like, lolcat_1, lolcat_2 and so on so we'll do a format and we'll do i so let's pass this name along here, right so that should be everything we need to do to download the lol cat from the program file, now we just have to implement the details of cat_service.get_cat().
Now, before we move on to writing cat_service.get_cat() notice there is some squiggles here and PyCharm is actually telling us lolcat is a misspelled word so PyCharm will actually find spelling errors in variables, in methods, in strings and so on, so let me just put a string here it says I went to the house and misspell house here, so I can go over here in PyCharm and hit alt+enter and it will say you know what, that word is a misspelled maybe you should use house and it will do similar stuff for parameters for methods and so on, and that's really cool, sometimes, there is a word that is actually the correct word but you know, PyCharm doesn't believe in lolcats so we need to tell it hey this is a properly spelled word, so I can come over here and say save lolcat to my dictionary which is just a local file associated with your PyCharm project and now it won't give me a little squiggly and say there is something wrong.
So this spelling correction stuff is really nice I actually find it super helpful for when I have misspelled the method especially if it's like in public API or something.
All right, let's write the first part of this get_cat() method, so the first thing we are going to have to do is get the cat from the internet and we are going to return it as some kind of data a binary stream or a byte array or something like this so we'll write a method called get_data_from_url() and this method could be used like a binary data for many url it doesn't have to be just cats come over the url parameter here and so we'll say the url is and I happen to know a url that we can use that will return a random lolcat.
So here we have this web service and we are going to call it and it will return a small set of random lolcats, let's go and pass this here, now obviously, this method doesn't exist so let's go write it, so let's do what we've done before and use the requests package from PyPi to download the data from this url, so we'll say response = requests, now again, request is an external package we have already installed it with pip but we still need to import it at the top of this file that's easy enough to do, then we can do a get() and we'll pass the url.
On the response object we have a raw, and the raw item is just the binary stream of data that we want to work with, so we can return this to the caller and then they can go save it or work with it memory, whatever they want to do with that data.
Now it turns out this is almost right, there is actually a problem with this even though it might be the most straight forward way of thinking about doing it and we are going to come back and fix that but first let's move on and talk about saving this binary data to a file.
|
|
show
|
4:21 |
Now that we have this data downloaded form the internet, let's go and write it to our file.
We want to save this data to a particular name related to the cat name in the folder that we pass so this should be pretty easy to write, we'll say save image and we'll give it the folder we'll give it the name and the data.
So down here we are going to have a method like so now you worked with files before and you know when you are just going to use the file stream just for a moment and then you are going to close it right away you want to use what is called a context manager so we'll say with and open() is how we get all the file stream, and we want to give it a name so let's come up here and say file name = and again, we are going to use the os model so we'll say os and import that at the top, we'll say path.join() and our folder is in absolute path so that should work and then we have our name and then this is like the name of the cat like lol cat 1 lol cat 7, these are basically jpg's so let's just add on the file extension.jpg.
So now we can say open file name and say as fout for output file stream, and then we are going to somehow work with this, if I were to just write this code and try to write this data to it, this data is binary, this by default is a text file it's not going to turn out so well for us so what we need to do is go over here and change the mode, and we can say I'd like to write, by default it's read only and I'd like to write not just text but binary so we'll set the mode to wb for write binary and then I could work with the output stream and the data stream and sort of copy those around and juggle the bytes and that would be fine but it turn out there is a module called shell util that will help us out here.
So we'll say shutil, like so and we want to import that at the top of course, and it has a method called copy file object.
So we can say I would like to copy this data stream to that data stream, done, one line, beautiful.
Now, PyCharm says you should really move your method down one line, according to PEP 8 so there we go.
All right, we are ready to run this app and get some cats but remember I told you on line 14, 15 here we are doing it wrong so let me run it, get some sort of not working images, and then I'll show you how to fix it, it's really easy but it's also easy to overlook.
Let's make sure we get some output while we are running our download so we'll do a little print statement here, we'll say contacting server to download cats, and we'll say something like print downloading cat and then let's actually involve the name here, I'm going to say plus name, and then at the very end I guess we could print done, something like so so let's run it.
Ok, how exciting, let's go look at our cats, so now you can see we have all of our cats and if I open them up, that's not a super cool cat, I love all cats and I was hoping for a good little laugh when I saw this picture, and this made me sad, well you might think there is a problem with PyCharm but in fact, if we actually open this up and find her you would see there is not really a proper picture there, so let's go back here and talk about the mistake we made when downloading binary data using requests so in order to work with this raw stream here in the manner that we were doing it, we need to go tell request to operate in what is called streaming mode, so we can say stream = true, and now if we just rerun it you should see some nice cats.
Ok, it's all done, let's go look at the cats.
Nice, trash cat is not amused, but I am, because this is awesome, let's look at one more, uuu I'm in your quantum box, maybe.
Ok, so perfect, we've downloaded our data using requests and we are working with the raw stream and we set stream mode to be true we then pass that stream over to our saved image which we create a binary file we can write to and we use the shell utility copy file object to copy from this stream to that stream.
But as a user, how would I know my cats have been downloaded other than seeing done, ok, great, done, but I actually want to see the cats so the next thing we are going to do is show the cats to the user.
|
|
show
|
3:00 |
So our cat app is basically working, the last thing we need to do is display the cats to the users.
Now we were looking to them over here in PyCharm but obviously the users won't be running this in pyCharm and we do kind of say hey we downloaded your cats but let's show them to them.
Now we have to keep in mind we are working in a terminal app so we kind of can't really show it like on a cool web page or in some kind of window, the best I think we can hope for is maybe showing the finder window or the explorer window and letting them browse with the operating system file browser.
So the last thing we are going to do is write this display cats method, let's do that.
And the only thing we are going to need to know is what folder do we have so let's go write this at the bottom, and we'll define this method we'll say pass just for a second, now let's think how do we open the finder window so it turns out that you can just type open and give it a folder name so you could like type open.
in the terminal, in OS X that would launch the finder window in whatever window you are at.
Now on Windows, that doesn't work, you have to say start.
and that would then launch the Windows Explorer to that location as well and in Ubuntu neither of these work you actually do xdg-open and then the folder so something like .
to open the current one.
We can run these three programs and let's go ahead and write it just for OS X first since that's what I am on, and then I'll show you how to actually determine what platform we are on and choose the right commander run based on that.
So the command we are going to use is open folder, something like this, we want to call this open process, we want to launch another process from our application and we do that with the sub process module, now like all of these modules we have to import them, PyCharm will do that for us thank you very much, and then it has a call() method, and call takes you can see there is a few options here, call takes an array strings, the first string is going to be the name of the program, the second one is going to be the argument that we are going to pass to open so we just say open, folder and that should do it, let's test it out and maybe put a little print statement here just to say displaying cats in OS window so let me come out of full screen so this doesn't look weird when it launches, boom, there are some cats check that out, let me make them a little bigger for you, oh yeah, those are good cats there has been a failure on the internet, of course there has right, so we can sort of flip through our cats like this, I kind of like the dune cat, he is a good one.
So we've successfully displayed our cats to the user, granted we have pretty simple mechanism for that because we have a simple terminal app, but nonetheless, pretty cool, it shows you how to work with sub-processes, in an incredibly easy way.
Now the last thing to do is to say well, if I run this on Windows, or I run this on Linux, this is not going to work so well, let's actually take it and run it on Linux and watch it crash and then we'll fix it over there.
|
|
show
|
2:14 |
Here we are in Ubuntu, let's try to call open and pass the folder and see what happens.
Couldn't get a file descriptor referring to the console, that's not a super user experience, so what are we going to do.
Well, like I said, we need to run a different open command on Linux, different one on OS X and a different one on Windows, now there are variety ways to determine what basic platform you are working on we could go and import the OS module and say OS.name and it will say pause x for Linux, and nt for Windows, but it doesn't distinguish between OS X and Linux.
So we need another way which we can get with this platform module so we can say if platform.system now platform is another module we have to import so again we'll let PyCharm do that.
You have to kind of experiment with it to see what it returns but on OS X it returns Darwin, on Linux it returns Linux and on Windows it returns Windows, so that should be pretty easy, let's say Darwin if it's Darwin we are going to do what we have already been doing, that was great, otherwise, we want to come here and say elif it returns Windows we'll say start and elif if it returns Linux we'll say xdg-open and finally we'll do an else and say print we don't support your OS.
And we could even show it to you.
Like so, now I suppose spelling Darwin correctly would help, wouldn't it?
Ok, so OS X, Darwin, Windows Windows, Linux Linux, let's try it.
Excellent, here are our cats let's have a look, I are dune cat, I control the spice, kkkkkkkkk cat and so on now notice one of these is actually off, I know that one of these images actually is a gif not a jpg and apparently this little viewer doesn't understand that from the metadata in the header it just looks at that extension, whatever that's not really a big problem we'd have to check what the file extension is when we save it.
So pretty cool, we were able to use platform.sys to determine what sub-process to call.
|
|
show
|
2:56 |
Finally, let's make sure the cat factory runs on Windows, and this is going to give me a chance to show you one more cool little feature of PyCharm, because this is not going to work.
So I tried to run my app, is that huh, no module named requests.
Of course, that should be a clue to you guys that hey maybe this system doesn't have requests installed, so we need to go out to the shell, or out to the console and say pip install requests that sort of thing, now you've seen when we were using request but we didn't import at the top PyCharm would say hey we will write the import statement for you at the top, but this is cool too, it says hey you are trying to use requests we know that this is a module form PyPi form the Python package index and you don't have it installed in your currently selected interpreter, but guess what, we'd be happy to do that for you so you can see down here installing packages request, hold on, done, so if we try to run this on Windows, you'll see that there is a small problem, it's pretty easily fixed but it's not entirely obvious why.
So it says it cannot find the specified file, that's weird, and it's talking about start actually, we have two ways to fix this, we could say shell=true because apparently the start is just a shell command or we could be just more explicit about what we want to start and we could say we'd like to start explorer, let's just go with that to be consistent.
Beautiful, here are lol cats the ones that you guys are now familiar with, and love so we've been able to use the platform.system to figure out the right command for each operating system and we have used xdg open for Linux, Explorer for Windows and open for OS X it's not the cleanest way to work in the world but when you are doing this cross platform stuff you know, sometimes you just got to put an if statement somewhere but it does list right app that run everywhere and that's pretty cool.
Let's do a quick review before we move on.
So we started out printing the header, this you guys have it downcold, and then we worked with OS path to create check for and create our output folder and then we downloaded the cats.
If we go down here remember we did a forin loop over the range of 128 so that let us create our 8 cats and then we just called into our cat service.get_cat() module which we wrote over here, we can check that out, and we just went and downloaded the data using requests made sure to set string=true return the raw response and then use the shell util copy file object right from one stream, to the other stream.
Then finally, we displayed the cats using the sub-process module and various operating system dependent apps that know how to somewhat display pictures to the user.
Now we have our very own working lol cat factory.
|
|
|
58:01 |
|
show
|
2:48 |
It's time for app number 7, and this time we are going to build a really fun wizard game it's kind of a role playing dungeons and dragons sort of thing.
So what is that going to look like?
Well, it's going to be a text based game and here you can see a standard ground of game play.
Of course, like all of our apps we start with a little header that says what the app is, and then right off bat we have our hero in the game, the wizard Gandolf.
And he sees an Evil Wizard.
It turn out that one is super strong and so he is like...
I am not going to battle this, he could attack it, he could look around, but he is just going to run away as quick as he can.
So he does, and then next he finds a Bat, and decides hey, I can probably attack and kill this Bat, so he does, but just barely he roles a 22, the bat roles at 22 and I guess because the element of surprise the wizard was triumphant over the Bat.
After that, the wizard Gandolf decides to have a look around and he sees that there is a wimpy toad, a tiger, a dragon and that very strong evil wizard hanging around.
The game goes around and around like this, until either all the creatures are defeated or the wizard is gone.
While building this game we are going to learn one of the most important concepts in all of computer programming and that is the concept of object oriented programming.
This starts with defining classes and classes defined how data and behavior are bundled into one concept in programming maybe this is a wizard or a dragon or the general concept of a creature or even the concept of a game itself.
When we create these classes, these are called objects, we want to initialize them, have them come into existence immediately with the data they need, we'll do that through initializers and when we are talking about inheritance we have to chain those initializers through the inheritance tree, so we are going to talk about initializer chaining.
Speaking of inheritance, that lets us model our concepts in our program with different levels of specialization.
So we can model all of the actors, the dragon, the toad, the wizard as something maybe we'll call the creature in the game and then we have a specialized version of the creature that has other data and other features called the wizard and it knows how to battle creatures and then we can also have different types of creatures that may themselves have special features like a dragon that has a special attack or something like that.
So this is called inheritance and it's a very powerful feature, when use judiciously.
When we talk about inheritance, we are also going to talk about polymorphism and duck typing.
Some languages have very strict rules about how you can use the more general what you call a base class or the specialized derived class, like how you can use a creature versus how to use a wizard, the compiler checks all these things and so on, Python doesn't have compiling or this concept of strong typing instead we are going to use something called duck typing we'll talk about that near the end of our app.
Let's get onto build a super fun dungeons and dragon style wizard game.
|
|
show
|
2:59 |
Over here in PyCharm, let's knock out the first pass on our initial game loop.
Now, it's probably not going to end this way, or probably refine it and add some enhancements, but let's just get started in kind of the same way we have with other apps.
So, again, we are going to add our program.py file and we are going to have our main() method, we'll have our print_header(), as always and in this one we are going to have what we are going to call a game_loop().
And then in our main let's just go and say print_header() and then we'll just run the game_loop().
And finally, let's use our PyCharm live template to call the main() only if it's actually being executed rather than imported.
So header is pretty standard as always so let's just do this, ok, standard header now let's focus in on this game_loop() we have here.
So the concept of our game_loop() is we just want to go around and around getting input from the user until the game ends so let's just start really simple with the concept of going around and around, and so we'll just say while true and we are going to do some work here.
First thing we are going to do is get some input from the user, so we'll call this cmd for command and this is pretty standard we'll say you attack, run away or look around.
Like so, and let's give the users hints on which they can do so we'll say attack, run away or look.
And then we have four basic outcomes here, one could be they actually said they want to attack and let's just print out some things we can kind of test a little loop here, then we'll rewrite the behaviors.
So if they say a we'll say attack, elif if it's run away we'll just print('run away'), if they are going to look around we are going to print('look around'), and finally, if they hit something else and we don't know what it is we'll just say all right, you must want to exit the game, like if we just hit enter with no command that means you are gone, so we'll just say something like this, ok, exiting game.
Bye.
So, let's go ahead and run our app just to make sure everything is hanging together.
Now, no run configuration, so we kow how to get that going.
All right, so down here we can attack, we can look around, we can run away, or we can say enter and we should exit.
Now, oops, we didn't really exit we just printed exit, right, that doesn't mean anything to Python just that we printed exit.
So we can exit out of this infinite loop by just saying break and then we'll come down here and basically run the next line which is empty and we're done.
So look around, enter, exit, perfect.
So our little loop is running, it's time to build up the data structures with classes and objects it's going to be great.
|
|
show
|
4:02 |
So we have the basic structure of our game built and now it's time to start working with the actors and the payers in the game, the wizards and the creatures and defining them and their relationships, and that's where this is going to get really interesting.
We are going to move this stuff around in the end, but let's put it here for now, we are going to have a set of creatures let's just take this a list in the beginning and we'll figure out what goes into this list it'll be empty for the moment but we'll come up with some creatures to put in there and we are also going to have a wizard and let's call it our hero, and we somehow want the thing to represent the wizard.
This hero is going to have to have actions and it's to battle the creatures, and it's going to have to have data like the remaining hit points or the level or something like that.
So let me just write none for a second so say this points add nothing until we get the chance to define how we are going to represent the wizards.
So I could come over here and I could write the code, just in this space, right here.
I can create a class that represents the behaviors and the data associated with our hero wizard.
But I don't want to do that, let's try to organize this a little bit better, when I have a lot of pieces involving the creatures and the wizards and so on so let's come over here and make a new Python file and we'll call it actors, we'll call it creatures or players or whatever.
One of the real powerful ways we can model both behavior and data in Python and in many programming languages is with this object oriented programming concept, and we start by creating a blueprint from which we will build out instances, of these classes, we'll build out a particular type of wizard, maybe we will have a hero wizard and an evil wizard or something to this effect.
So the blueprint is called a class, when we define it it's just class, remember, we used def for methods, for classes, we just say class and then we just give it a name, notice, in Python most things have lower case names, game loop, creatures variable, hero, keywords and so on, but for classes, they are typically cap words style, so like Wizard or earlier we worked with Beautiful Soup, where the B and the S were capitalized, right?
So we are going to define a Wizard, and we'll name it like that and we can just say pass to say we don't want to define any data or any behaviors in the beginning.
Let's do the same thing for creatures, we'll create a class called creature and we'll just say pass for the moment so let's go back here and we'll use those, instead of saying none, that wasn't so amazing, I'd like to say Wizard, well, that's not working so well, PyCharm says I have no idea what this means wizard, just like any function or variable defined it in another module we have to import it, so let's go at the top and import our wizard.
Typically, I prefer the type of import that retains the name space so it's very clear where something came from, so I can say actors and then down here I could say this I could go over here and say actors.
and then you could see my creature and wizard and the way we create an instance of a class is we call the initializer just like this.
Now wizards and creatures are so fundamental to the behavior of this game, that I am going to use a different import, I am going to come over here and say from actors import and then we get a list, what do you want to import, wizards and let's go and import the creature while we are at it, so if we write it like this then we don't have to use the name space, we can just come down here and say wizard, in fact we are not even allowed to use the name space.
In here let's put some creatures, so we'll create a new creature, what we want to do is initialize this with the various specific details of this creatures, so remember we had a toad, he was pretty weak, we had a tiger, we had a dragon, we had an evil wizard, we haven't set that part yet so let's just put a bunch of creatures into our list here.
So now we have a list with five creatures here and we have one particular hero.
Now if you wonder why these are gray and have squiggles that's just because we are not doing anything with them, yet.
Let's go and run this and make sure things are hanging together.
All right, if we get to this far, things must be going great.
|
|
show
|
7:51 |
We have this concept of defining a creature as a thing in memory and wizard as a thing in memory but they don't really do anything or have any behaviors or data, so let's go over here and look at this, let's look at the creature first.
So what might the creature have, well, maybe the creature is going to have to have a level, and let's have it have a name, so we can say something like toad of level 7, something like this.
So there is actually several ways to add those features as data to this class.
And the way that I am going to show you will generate what are called instance attributes or instance variables, and these are only going to be tied specifically to a particular version or particular create instance of the creature, so the data related to this creature will not at all be shared with this creature, there is way to have sort of what you think of a static shared data but we are going to use what is called a magic method.
Now, to add methods, magic or otherwise to classes just like normal methods you say def but you have them indented into the class body.
Now, I could have a method that is called run away, like this and then that would be a standard method there, but we are going to use what I said a magic method, so magic methods start with double underscores often called dunder, and you can see there are a lot of them, so here you can see length, if we had some kind of collection and we wanted to talk about how many items were in it, we could implement __len__() and when somebody says len (our object) it would implicitly call this method.
If we wanted to change how our creature appeared when you just said print creature, it was no special formatting we could implement __str__() and then we would get some kind of special string representation.
But the one we are looking for is __init__().
So here there is something a little bit unusual in this method, notice when I hit enter there, PyCharm put this self and this self has a color, while other variables in here, parameters, they wouldn't have this purple color, remember, the class is the blueprint and then there are the object instances and each one of them has their own special copy of data and that's what this self represents, this is the pointer to the one that has been used to call this method, so in this case it's the pointer to the object that is actually being created in init.
so we want to have a couple of things, we want to have a name and a level, remember this is kind of like dungeon and dragons type thing, so we have a name and a level that you have to pass in, these are not optional parameters on the initializer for creatures, you will see in just a minute, and then the way we define instance variables is we use this self parameter and we just say something like self.name=name, and self.level=level.
Now, these don't have to match, let's just change this to like the level to make it really obvious that there is no real name relationship between them, but self.
this is actually the creation, the definition of name attribute or field in the creature class.
It's highly discouraged to do this outside of the init method.
Now, while we are at it, let me show you a cool PyCharm trick so if I had another thing here like let's say experience or whatever, notice that it's not assigned to any member variable or used in any way, so I can come over here in PyCharm, I can hit enter and say add this field to the class.
And bam, it'll go here and automatically do what you just saw me do, so we are not actually going to have this experience but you can use PyCharm to kind of automate this, it's very nice.
Now, it turns out that the wizard is going to be very similar so let me copy this over here and I'll just change this form the level to level, like so, so now we should be able to create our wizard and create our creature and they should have data associated with them, the next we are going to give it behaviors like maybe it has an attack method here and it can attack other creature, or just a creature, and down here we'll put something that has to do with attacking a creature.
So I told you that these are required, if I try to run this app again, it's going to crush, and it crashes right here and says, wow, we are missing two required positional arguments, name and the level, I can do things like give it default value like so I could say by default the level is 10 unless they specify it then it will be something else.
So then you'll see, only one required parameter is missing, because if you don't specify the level, hey it's 10 so all good.
That didn't actually make a lot of sense in our particular game here, so I'll take that back away.
All right, so let's go figure out what's wrong and fix this, so I can just click right here and navigate right to where the error was and you see PyCharm has this kind of lit up and color here saying not such a good idea, all right so what do we have here, we have the name, remember, let's see we have a toad and the level of toad was quite low, and we had a tiger and the level of tiger was modernly high, I think we had the bat, the level of the bat was not particularly high it was 3, we had a dragon and the level of the dragon was pretty high and we had the evil wizard, so now our list of creatures will actually contain particular copies of the creature object, five of them, and each one of them will have a separate name and a separate level tied together.
one thing that is a little bit off here is using this creature class for a wizard when we also have a wizard class, so let's leave it like this for a minute and we'll get back to it, so now here we have the same problem, we have exactly the same requirements to create this, so let's say this is Gandolf, and his level is 75.
Ok, let's run this and see if we can get down pass line 27.
It looks great, let's actually run it in the debugger and we'll have a look at these in memory.
So here we are at the break point, we've got our local variables and we have a hero and you can see we can open this up, we have this hero has a level 75 and his name is Gandolf, and we actually have a list of creatures, and you see there is various creature objects, these are manifestations or things created from the blueprint of creature, there are different memory addresses, everything in Python points to somewhere in memory and so the elements of this list all point to different places and the debugger happens to show you that if you really care about it.
We can open this up and see there is our bat, there is our tiger, there is our toad and so on.
So we spoke a little bit about magic methods, and you can see that this is not the most amazing experience for looking at what is in this list of creatures and that's not the only place it showed up, if I were to come here and just say print the creatures, and I run this, you can see basically I have the same output here, there is some kind of actor.creature at these various addresses.
And we can use the magic methods to change how this works.
so my creature, I can down here and I can say define __repr__(), for representation, and then I could just say return some kind of string so let's say creature of the name of level- something there we'll do a little format like so and so when you say self when you refer to the variables of the particular instance of the type of class like so the particular creature here, I'd say self.name, and self.level.
Of course maybe format instead of forma, have a little better chance there.
So we are going to return a string that could be used in various places, so now if I run this you can see we have a creature toad of level 1, maybe like so or something.
Creature, toed of level 1, creature tiger of level 12 and so on, so we are only getting started with our classes and modelling the actors in our game, but you can see we are already after a good start we have a list of creature objects, they have their particular data associated with them, and we have our hero which is a wizard named Gandolf of level 75.
|
|
show
|
13:21 |
It's time to add some behavior to our objects, now let's work with attack first.
We'll just implement these 3 behaviors, attack run away and look.
So attack I think is the most interesting, we'll start there.
Now, the first thing we are going to need, remember, is the game said a creature of such and such type appears, or has come out of the forest and what do you want to do to it, so that part is not here yet, we do have our creatures they are up here but I need to get one and randomly selecting one seems like the ideal thing to do, so we can come down here and say something like active creature is something.
Now, in many languages you can get random numbers, random integers and so on, and you know that if I had a random integer, I could come over here and say if I had randomly selected an index I can come over here and say creatures some index and pull that out, but in true Python fashion, Python has a better way to do that.
It still involves a random module, so we'll start there.
Add that at top and then we'll say choice, so we can go over here and say I have a set of items, I would like to randomly get one so we'll say I would like to just randomly select a creature so let's do a little print statement to just say what that creature is.
We'll say a { } so you know, and then the name of the creature or something, has appeared from a dark and foggy forest, something like that.
Now, I want to add the levels so we'll say of level that's very important to know whether we should attack it, so then we can say .format active creature.name and active creature.level, let's just add a little extra space here, so let's go and run it to make sure this randomly choosing a creature is working for us, yes, so this part up here we could probably get rid of that all right, here is our game, oh a bat of level 3 has appeared from a dark and foggy forest do you want to attack, run away or look around.
And it didn't really matter what we press, it's just going to go around and around.
You can see each time through it's randomly selecting an item so the sort of activating the random creature for a particular round is working, now next thing to do is work on what we do when we actually attack it, how we do this, there is a couple of ways depending on how you want the features at this game to evolve, there is maybe one way of doing it that's better than another.
But I think the simplest and straightforward one is to assume that only wizards can attack creatures and maybe other wizards but creatures don't initiate the attack they don't have this concept of being an attacking creature they can just have, they can be in a battle but they are not the one who initiates it.
And if that's the case, which I am going to say for this little game since I am writing it, that's the case.
Putting some kind of attack behavior on the wizard so the wizard knows how to attack creatures is the way to go, so we'll come over here and we want to say something like hero.
and then you can see we have our level and our name that we created in the init method and we want to add not just data but behavior.
so let's go over here to our actors where we have our wizard and we'll define just like any other method we are going to define a method called attack, these instance methods they always take the self parameter, when you invoke them from the outside you don't specify the self or anything like that not explicitly anyway, but when you define them they do, that's just how Python classes work, and then you can pass the actual arguments that appear to the color so the thing we want to pass is some kind of creature and we'll just print something like and we want to refer to the name of the wizard that is the particular instance the object that was generated from this class that would be the self parameter, right, so we'll say self.name and then the creature also has a name and a level so we'll go like that.
So let's go over here now and say .attack, now we have our data and our behavior.
And we'll come down here attack the creature, so that will be active creature, now we still have a lot of work to do to determine whether they win, to figure out even what the algorithm is for winning or losing or fighting, but we should be able to at least test this out.
Ok a bat of level 3 has appeared form the dark and foggy forest, let's attack it, why not, so the wizard Gandolf attacks bat, so let's go finish our attack method.
Now, PyCharm has some really cool ways to navigate around, I can come over here and if I hit command B it will actually take me over to that method wherever it happens to be defined, right, there is only two files it's pretty straightforward to find it but in real projects you might have 50 files and where did this beast come form, that can be really tricky so that is a super helpful feature, you can also hold down command and then all of these things basically become hyper links, so I'll click over here and it will take us straight to attack.
So, this print part, this is going well, now what we want to do is decide on the algorithm and my simple algorithm I am going to use for this game is the level of the creature and the level of the wizard are going to be factored into this and we are going to have what would be more geeky in dungeon and dragon then a 12 sided dice, so you are going to roll 12 sided dice, multiply it by your level and then whoever rolls the higher number is going to win that battle, so that will go something like this, I'm just calling my roll = now I want a random number between 0 and 12 so we'll say random and just like before we'll import this, and we'll say rand int and we are going to give it 1 to 12 and then I am going to multiply that by the wizard because this is the wizard's throw by the level.
Similarly, we are going to have creature roll, and it's going to be the creature's level.
And then we are just going to compare but first before we do so you get some visibility into what's happening let's do a print statement, you roll some number, my roll and then we'll say the creature like bat or toad or whatever rolls, then finally we are going to do a test to see who rolled the higher number, and I am going to assume if the creature and the wizard tie because the wizard has the element of surprise, he is going to win.
So that would look something like this, if my roll is greater than or equal to creature roll, then let's return true to say that we won, on the other hand we could return false here, now we also want to give a little visibility like print the wizard had handily defeated whatever it was, right, so we'll say .format creature.name so down here we can say the wizard has been defeated, let's change it so we don't need to use the same words, they triumphed over, ok.
There is a couple of things going on here that are not fantastic.
One of them is that this attack method really should be performing the attack, it should be doing this algorithm here and it should be doing sort of this decision here determining whether or not you won or the creature has won.
But it's also doing this kind of UI management stuff, now this is simple game we are just going to leave it here but in a real application you want to somehow separate if possible the UI piece from the core logic of our game so we are kind of violating that rule here but just because we are just getting started with classes let's just keep it simple and roll with this.
All right, let's test our attack method, ok, a tiger of level 12 has appeared out of the forest, let's go ahead and attack since well, that's the only thing we've done, and it looks like I forgot something in my print string, so notice here there is one, two place holders but only one value provided, let's go fix that really quick.
Ok, now let's try it again.
All right, this time, that same tiger has appeared, let's attack it.
Ok the wizard Gandolf attacks tiger and you roll 375, the tiger 24.
So the wizard has handily defeated the tiger.
Now, something more serious has happened, a dragon of level 50 let's give that thing a shot, OH NO!!!, now we only got 75 the dragon did 100 and we have been defeated.
Now there is really no consequence for us to be defeated and if we look around we haven't implemented that part yet, but if we did, you would see that all the creatures are still there even though they have been defeated.
Let's do a little work on the consequences of being defeated.
So over here we'll say if when the hero attack the creature they win, what we should probably do is remove that creature from the game.
This creatures list holds all the living creatures, so we can do something really simple like this creatures.remove and just give it the creature, active creature.
On the other hand if the hero loses, there is two possibilities here, one could be that's it, the game is over you are done, the other one is maybe the hero wasn't killed all the way, they are just defeated so they have to go rest and recover.
It's pretty straightforward to say print game over break without the semicolon, break so let's look at what we might do if we want to make it pause.
So to make it sort of appear to pause, we'll come over here and we'll say time and I imported just the import time above, and I say time.sleep and that's a number of seconds it takes a floating point number so I could do like 0.001 for milliseconds, something like that and let's say you are going to sleep for 5 seconds, and we'll say something like this print, like this and then we can print, the wizard returns revitalized and let's just add a new line down here at the end of all this so there is some space so here is the consequence of either winning or losing this attack round, so when the hero attacks the creature is removed from the game when the hero loses, we have decided that actually the game is not over, but that the hero has to go rest for a while, so let's try that, ok again a tiger of level 12 we have seen we have a good chance against that let's go, all right, so we have beaten the tiger, now but you can't really tell, like us we haven't verified it yet, is we haven't implemented look around, so let's do that really quick, just so we can see that our creatures are going away.
So what we are going to do is just say something like wizard so and so takes in the surroundings and sees then I want to loop over each of the creatures so I'll just say remember our for in loop, so we'll say for c in creatures we'll print out maybe a little sort of place holder thing like this and we'll say a name of level such and such and again a format would be nice here so we'll say c.name c.level and maybe that fits on one line, ok.
Ok, let's try this again, now we can see if our remove is working ok so the tiger again, every single time it's the tiger, all right tiger attack oh we definitely defeated him, oh oh, a dragon of level 50, it's not so good, so we'll look around and apparently we are going to need to put something there a toad of level 1, a bat of level 3, a dragon and an evil wizard.
Well, the dragon is away, the toad is out, we can attack it and beat it of course, we look around again, now the toad and the tiger gone, let's attack the bat- again, we defeated it, now it's just the dragon and the evil wizard, it's kind of getting a little bit harder for us here, I still got to fix this but let's go and do this attack, what are we going to attack- the dragon, maybe we can beat that.
Oh, we have won, ok now it's just down to the evil wizard, there is almost no chance we are going to win here but let's give it a shot.
Oh no the wizard has been defeated, he runs and hides, taking time recovering.
Now notice, it took actually stopped for that five seconds and then it went on, let's do it again I am sure we'll lose, 1, 2, 3, 4, 5, counting up and there we go.
So that's our time.sleep.
Excellent, let's just get out of here for now.
You can see we forgot our format here, hero.name well the very last thing to implement is just run away and that is super easy to do let's just say print the wizard has become unsure of his power and flees.
All right so let's look around, oops, focus focus focus- look around, so the wizard is looking around he sees all this things, oh my gosh this toad is frightening, run away, all right so the wizard has become unsure of his power and flees, and now a tiger appears, oh let's attack it, excellent, we defeated it, now if we look the tiger is gone, I think our game is very nearly done, there are a couple of things we are going to come back and look at because the way we have written it here while it works in this simple scenario is not maybe the best way to model things so we are going to do one more pass on this and make sure we have everything just right.
|
|
show
|
2:12 |
Now that we've had a chance to work with classes in our wizard game, let's take a moment and look at the most common features that appear and are used with classes.
As you saw, we define all classes with the class keyword, so here we are defining a creature class so we can say class creature, and like all code suits or blocks in Python we define the rest of the class by saying colon and then indenting four spaces, everything that is indented, is part of the class, so here you can see that we have two methods, one method called walk is a standard instance method if you will, and when you call it on an object and instance of a creature you say creature.walk and it takes whatever the name of that creature is and it says, you know, let's say it's toad, so toad walks around.
See this yellow keyword self, this is a convention, it could be called anything, in other languages it's called this and like C# or C++ but here we are calling self and its pass to all instance methods and that's how we access the field, here we are saying self.name this is just a standard instance method, the one above it is the initialzier, and you can tell that is a magic method because it has double underscores on both sides, so typically this is referred to as "dunder" so here we would say __init__(), and this is actually not often directly called but it is actually part of the initialization creation process of the creature, so when we create a new creature we are going to actually pass this name and level and then we somewhat dynamically create the fields the name and the level on the self pointer.
Classes bundle together behavior and data, here the data is the name and the level of the creature and the behavior is its ability to walk around.
And these features of a class are the most common, but in fact classes are very rich in Python as we'll see you can have inheritance, you can have what are called properties, you can have static methods, you can have class methods, you can have static variables or fields on the class so we are just using sort of the most common stuff here in our game, but you can go really deep with classes and actually do a ton.
|
|
show
|
2:04 |
Let's take a moment and focus on this concept of classes versus objects.
If you are new to object oriented programming and this idea of classes, this can be a little bit tricky.
Recall, this is how we define a class, we say class creature and it defines basically the blueprint.
Here is how we initialize it, and within that initialization here is the fields, here is the other behaviors it has for example that it can walk around and for creatures, there is only one creature class, this is it, this is what a creature looks like in our program.
However, we can create many of these creatures, here we are going to create a squirrel and its power, you can see the parameter to the initialization method is power so here we want to say creature (7), we'recreating a squirrel with power 7 whatever that means and then we are also creating a dragon which is more powerful, it has power 50, so when we create the squirrel and the dragon, these are objects, and objects are created via classes but they each have their own pointer in memory and that points to their own data.
So, this squirrel object it has its own power variable and its power is 7.
Now it actually gets the implementation from walk, from the blueprint, but we can call walk on this squirrel and we'll work with its data, its power of 7 and so on.
The dragon has another pointer, somewhere else in the memory and it has its own copy of power, so if we change the dragon's power obviously it's unrelated to the creature so these are objects, they are created from the class, from the blueprint of what defines a creature, and then we can call squirrel.walk(), dragon.walk() and they may have different behavior based on the power, maybe if there is a lot of power it jumps around if it doesn't have much it sort of scurries or something like this.
Once you get used to this idea of classes and objects it's really straightforward and you'll have it down no problem, but if this is new to you, make sure that you get this idea, it's the core concept in object oriented programming classes, inheritance in object oriented programming play a really central role in Python.
|
|
show
|
5:35 |
So we basically have a working game here, I guess one final thing to consider is what happens if there are no more creatures, so we can come down here and add one more part after this section here and say if not creatures and we'll leverage the implicit truthiness of collections, this is a list of creatures, if it's empty, it'll be false.
So then we'll say print, something like you defeated all the creatures, and then we'll just exit, like so, let's just test that super quick, I'm not sure we'll be able to defeat the evil wizard of 1000 so let's just there is a theoretical chance but- it's not so high, so let's go over here and just comment out the wizard for just a minute, so here we can attack all the creatures, how many are left- oh, it looks like we defeated all the creatures, well done, game over, perfect.
So our game is working, but it's kind of blunt and basically the only factor we have to consider here in the entire game play is that number, what is the level of our creature and what is the level of our wizard and then its randomness.
Do you feel like a fighting a toad should be equivalent of fighting the dragon or fighting evil wizard should be the same as fighting a standard like bat, probably not, not only do they have different ways to fight but the properties that you would take into consideration around how they would fight would be different, maybe a toad's worthiness would have some sort of factor there and if a dragon had scales maybe you would consider like how thick his scales were or if he was a fire breathing dragon or something like this.
But if we just use our creature class let's go over to that if we just use our creature class here, it doesn't model scales or worthiness or whether it breaths fire, it simply is the most common denominator thing that models all creatures, right, they have names and levels.
And that's exactly what we want we want to have this sort of commonality so regardless the type of creature we are fighting, we can just use that, ok.
So let's add 1 more feature that will give us a little flexibility to this creature class and then we are going to look at how we create other classes based off of creature and use inheritance and polymorphism in that kind of thing.
Let's add a method say def and we'll say get_defensive_roll() or something like that.
Now up here we just had the creature do the roll directly here but rather than do this let's comment this out, and let's say we'll go to the creature and we'll say get_defensive_roll(), and it's up to the creature to take all of its attributes into account, if it's a dragon, let's think about its scaliness, if it's a toad, it's worthiness or whatever, right, we can make our game behave exactly the same if we just use sort of the same algorithm, we randomly compute our 1 to 12 number, that's throwing a 12 sided dice and then we are going to multiply that by the level, and that's what the base creature does, but maybe we'll do something different for a dragon, if we can invent this idea of a dragon and so on.
So let's just make sure we can still play the game, so we are going to look around and we see a level 1 toad so let's attack it, excellent, toad rolls 2 let's look around again, now we see the evil wizard let's attack it just to see that it's throwing something high, oh we've been defeated, it rolled 5000, of course we've been defeated.
So now let's go back and think about our creatures here.
We want to categorize these different things, these different fighters into more specialized versions of creatures, so maybe a toad and a bat maybe we would actually want to call these like small animals here, so let me just sort of sketch this out, so maybe these would be something like small animals, and small animals let's see, small animals would have the same properties as the creatures but maybe they would have some diminished sort of throw to save or as the tiger, maybe this is a predator, dragon maybe this would be a dragon and maybe to the dragon we could say scaliness = 20 or whatever a scaliness factor is right and that might be considered when the dragon is fighting, if it has more scales it's even harder to beat and then similarly maybe the wizard maybe it has some type of magic and it somehow takes that into account as well, right, so maybe we put a wizard here, this isn't going to turn out really well for us, so far, first of all we don't have small animals, right, these are not a thing we are modelling, and we don't have a dragon, let me just comment those out for a minute, we do have a wizard, now if I try to run this it's probably not going to work so well, a wizard has appeared, let's attack it; oh bam, the wizard doesn't have this concept of a defensive roll, Hum?
all the creatures in our game are supposed to sort of have this commonality of everything that is a creature and then some specialization in them.
So let's take a moment and look at this core concept called inheritance that allows us to sort of layer on this specialization and share attributes and behavior across these different types of creatures in our game and then we'll come back and add it to our classes.
|
|
show
|
1:36 |
Inheritance is a core concept of all object oriented programming and it's exactly the tool that we need, to help us build these special types of creatures in our game, dragons, small animals and so on.
The way it works is you declare what is called a base type, in our case this is the creature and it has all the common functionality.
But we can add specialization to it, we can add some other type that is like a creature but has additional pieces of information, or behaviors, so here we have a dragon and the way we declare its base type, the thing that it gets its common behaviors from is we say "(" and the class name, ")".
So dragon is a creature but dragons also have special behaviors and data so this dragon can bread fire, you can see there is breed fire method, creatures can do that, it's adding that to its specialization, and it also has a different set of data, it has a scale thickness in addition to the name and level.
Now, notice this call to super, we say super.init, that's the initializer for the creature class, and we have to pass the name and the level onto this base class, we technically could ignore this step and then just store the name and level as well, but you often find you would be duplicating code and introducing various bugs by omission and so on, if you do that.
Here we are actually letting the creature class deal with storing and processing the name and level and whatever that means and however that evolves over time, and we are only doing the extra stuff in the dragon, like storing the scale thickness and so on, so let's go back and apply this concept of inheritance to our wizard game.
|
|
show
|
13:11 |
So let's go over here and first address this problem we kind of would like to use the wizard specialization, the wizard class as any other fighter in our game.
And the commonality that we need for any kind of fighter is of course that it is a creature, right?.
And we just saw that we can use inheritance up here like so and say this wizard is a creature.
Now, this is little annoying, it says we don't really know what creature is and the problem is creature is defined after the wizard class, so there is two possible fixes here, one fix could do this and just change the order, put the base class first, the other fix could be to possibly put the creature in its own module, a wizard in certain module and then they could just import them int he order they need.
Now notice down here, there is some extreme duplication in the init for the creature we have a name and a level, and we are storing those, in the wizard we have a name and a level and we are storing those as well.
This is not good first of all but there is two ways we can fix it, one way we could just say we are going to have this init method here and we could say super and this will give us access to the direct methods on creature, we could say __init__() and we can say name and level, and now the warning will go away, right, and we are actually passing this off, so when you call this method, by creating a wizard you really just setting up the wizards properties by delegating or pushing this onward to this method.
That's really good.
If we were doing additional stuff like more wizard stuff here, with the proper spelling, that would be cool, we would totally do this.
But, if all we are going to do is delegate up the inheritance hierarchy there.
There is no reason to even have this method.
So we don't even have to write an init method and we can just inherit this one if you will.
so now let's try to run our game and see if it will work.
Ok, a tiger of level 12 appears, let's just look around until we see the wizard again.
Ok, wow there is a lot of tigers in a row.
So the evil wizard appears and we are going to try to attack it, remember, when we tried to get its defensive roll before it didn't have one, boom, now it rolled 4000 in its defensive roll, we were defeated, but by making the wizard class derived from the creature class we now can treat the wizard as any other player in the game, not just the hero.
In fact, we can go over here and fix this up again so here we say creature get defensive roll, and now because the wizard itself is a creature we can say self.get defensive roll as well, and the game should play exactly the same.
Perfect, so our wizards can now be creatures, let's work on small animal.
So let's define another class, over here, in the actors, and again, make some space for a minute, so we'll say class in memory just to define a class by saying class, class name, cap words and hen again we want this small animal to be a special type of creature and we could just say pass, and if that is all we do this stuff will start working.
Ok, we have a toad and a bat, of course, one minor problem, remember the top the way we imported, the actors, we now also need to import the small animal, from here.
Ok, now we should be able to treat these let's run away from that, a toad of level 1, this is a small animal, and perfect, it behaves just like a creature but there is not a lot going on, with this small animal yet, right, there is nothing special about it, it literally just does everything a creature does other than it does have its own type and so when we say what type of object is this in memory, we know that is a small animal, you could actually ask is this a type that is derived from a small animal or is it just a plain creature.
We could use that for something, but we don't really in this game.
I guess the thing we can do to make this small animals special is maybe it's a little extra weak, so down here we can change this get defensive roll, in the creature class, it has a get_defensive_roll() and it just does this, let me copy the whole thing, and we can actually change how this works ok, so we can come over here and say the small animal has all the behaviors and everything from the creature except it's going to have its own version of get _defensive_roll().
Now, some programming languages when you are doing inheritance you have to have special words like override or virtual or these sorts of things, Python doesn't have that, we just have one method it's called get_defensive_roll(), if we just say it in the other classes something replaces it.
If we leave it this way, things should just still work again other than the wizard is not what we are looking for, but the bat is, perfect.
But let's say we want to change this, let's say we are going to come over here and say I'll call this base roll, and then we want to return base roll maybe divided by 2, ok so it's a really small creature it's really weak, so we could do this, let's find something, ok, there is a bat and the bat rolls 3.0.
Ok, so it probably really rolled 2 but it's level 3 so I got 6 so then we cut that down to 3.0.
So now we specialize our small creature to be a little easier to defeat than the others, well quite a bit really.
However, this part of code, is literally this base get_defensive_roll().
Ok, so we can actually change this, we can say you know what, it could be the super, go to the creature class and we can call it it ge_ defensive_roll(), and that way as this evolves over time maybe we decide to switch to a 15 sided dice or something like that we don't have to maintain the small animal, it just does whatever the base one does, and it divides it by 2.
So let's run this one more time, make sure it's still working, ok wizard, wizard, tiger, wizard, can I get a bat please, ok, there is a bat, perfect, look bat rolls 3.0, let's do it one more time, there is a toad we just missed it, ok, toad of level 1, the toad rolled like an 8 and we cut it in half.
So notice how we are using the super to grab the base behavior but then we are replacing it with our own method so that small creatures have a different style, different algorithm, for their defensive rolls.
Last thing is let's create a dragon, because I know you've been waiting to create a dragon they are so awesome.
I want a black one, right so we'll create a dragon and again, this is going to be a creature now in the small animal and the wizard case, we have said there is no reason to have a specialized init method, basically the setup in the data that the wizard and the small animal use is the same as the creature, but the dragon, I am going to change this, I want the dragon to have a scaliness and an indicator whether it breaths fire or not.
So what we are going to do is we are going to define a __init__() and it's going to have to take all the data or at least somehow supply the data that the creature is going to need, so name and level, and then I am going to have a scaliness and a breaths_fire, this will be a boolean to say it does or does not breath fire.
and then the first thing that we should do is we should set up the creature feature so we'll say super.__init__(), name and level, and then we need to assign to the dragon whether or not his scaliness and whether or not it breaths fire.
So we can just say add this parameter here and let PyCharm rock it out for us, thank you PyCharm.
So now our dragon has different data, different features, and it's probably going to have a different defensive roll, that's going to take those into account, so here we can say we have the base roll but our dragon it's going to have a multiplier, so let me take this chance to show you a slightly different way of doing if statements or conditional test here, so we'll have a fire modifier, now if they do not breath fire we are just going to have 1.0, we are going to multiply this by the base roll, so if the dragon does not breath fire we are going to say 1 but if it does, maybe we'll have a factor of 5 or something, I mean that's a pretty serious defensive measure that it literally can breath fire.
I could write this, let me say none and I'll say if self.breaths_fire, the fire_modifier = 5 maybe this could just be 1 but let me put it this way just an else statement, so it's a little more clear, so else fire_modifier = 1, now this bit right here, we are using all these lines of code to do this, we could actually write this in a much nicer way, we'll say fire_modifier = and I can put this all in one line in this condensed if statement the Python supports, let me just sketch out the placeholder so value if true say if some test else value if false, right, so if we use this the value if- let's do the test first, so the test is going to be if breaths_fire, self breaths_fire, so if it does breath fire, I would like the modifier to be 5, but if it does not breath_fire, I would like the modifier to be 1, ok, so let me put this like so, so now this will actually let us in one nice little condensed line, specify the fire modifier, the other thing is the scaliness factor, let's say this i s a number between 1 and 100, and it has like a 10% effect on here, so we'll have something like this, scale_modifier = scaliness self.scaliness, divided by 10, right, so if it's a 100 it multiply it by 10, if it 1 it actually takes it down a little bit, so then we are going to return base_roll times fire_modifier times scale_modifier.
Ok, now let's go back to our program here and let me just put this back for a minute, that's how it was before we started to talk about these dragons, so if I try to run it, it's not going to turn out so well, first of all it says dragon is not defined right there, that's because I again forgot to import it, it's kind of why I like the name spaces you don't have to keep doing this, but it's all good.
So now we have our dragon, but notice PyCharm is already telling us hummm?
not so good, but it won't run it will say you are missing this parameter of scaliness and breaths_fire right there, so let's say that this dragon has a scaliness of 50, no let's say 75, that's a pretty serious scaliness and it does breath_fire, so this dragon is now a much harder thing to fight, all right, so let's look around, so we see- let's run away until we see something that is a dragon.
Toads, dragon, ok dragon at level 50 and what is our level, 75, so we should have a pretty good chance to beat it on the previous algorithm, but this is a very scaly fire breathing level 50 dragon chances are not good, let's see what happens, let's fight it.
Ok, we rolled a 150 it rolled 11250, I would say we've been defeated, the thing is even worse than evil wizard now.
So let's go back and see how inheritance has helped here, so we have a small animal which is a toad and the fact that it's a small animal it behaves like a creature but it actually is easier to defeat because it has a different wimpier get saving or defensive roll method.
Creature this is just a standard creature, the dragon, actually is much harder to defeat it has additional data that it uses to define how it defends when it's attacked and now it has a scaliness protective layer and it breaths fire it takes all that into account during this battle but the wizard doesn't have to know it's a dragon, it can still treat it like a creature.
And yet it behaves differently, finally the wizard can also behave like just another creature in the game because it derives from creature.
We do this derivation by just defining the creature class and then we say the wizard is a creature, here we have an additional method that's not in the creature class called attack, PyCharm we can collapse those, and then the small animal it has this simpler wimpier defensive roll and notice that PyCharm is showing you this overwrites a method in creature so just be aware of that, that's actually pretty awesome.
The dragon has its own initialization method because it has to take additional data and do additional setup but it delegates to the base class for the sort of base class setup and it has a different defensive roll that not just has a different algorithm but takes into account the fact that it has different data like the scale modifier which apparently I misspelled.
So this just gives you a little taste of object oriented programming but the types of applications that you can build are seriously powerful and it's a lot of fun to think about solving problems this way.
|
|
show
|
2:22 |
Let's look at the final core concept in this object oriented wizard game called polymorphism.
Now you can see that we have a list of diverse creatures, we have small animals, creatures, dragons, wizards and so on, and as you saw on a previous examples and you can see in the note on the left here, all of these objects all of these classes, small animal, dragon and wizard, all derive from creature.
Below we have a wizard named Gandolf.
and if he is going to go on some kind of a rampage and just start attacking all the creatures he would say for c in creatures wizard.attack c, now when we write that attack method, we don't care or usually want to know, it's usually a negative thing to actually know whether it's a dragon a small animal or just a creature because we can leverage the fact that all of these derived from creature we can just work with the common functionality, the fields and the methods on the creature class and it doesn't matter what type of derived object we pass, it's a small animal, a dragon, we can still fight it just the same, we don't have to rewrite our code, and that's fantastic, that lets us as we evolve our application create more specialization other types of objects, later in the game we want to add some kind of like water monster to attack the wizard when he is out in the ocean or something as long as that thing derives from creature, we potentially don't have to rewrite the code that handles wizard's battling water monsters it's just using this base class.
So that's a really powerful feature.
One other concept closely related to this is something called duck typing, in statically typed compiled languages like C#, Java, C++ and so on, you absolutely must have these objects derived from creature or trying to pass them to the attack method which assumes it takes a creature or literally not compile, your code won't run ever.
In Python it's not like that, Python uses something called duck typing and as long as the shape of the pieces of the creature class that the attack method assumes are there, get defensive roll, name, as long as the types you pass match that, you technically can use it here, and that is called duck typing, it's like if it acts like a creature, if it looks like a creature, it is a creature, but I still encourage you to create these object hierarchies as it helps reduce code duplication, it helps with maintenance and it's generally just the right thing to do.
|
|
|
44:24 |
|
show
|
2:00 |
Hello and welcome to app number 8.
This is a file searching app, and it's going to be able to search for text within files across a massive quantity and number of files.
So, let's see exactly how this is going to work.
What are we going to build- well, it's going to look like this, header, as always, and then it's going to ask the user what directory do you want to start searching in and we'll search recursively through that entire tree that subdirectory sort of thing, and what text, what string you want to look for.
Here we are looking in our previous app for the word dragon, and here you can see we've found two matches, in the players.py file on line 7 you can see we are defining a class called dragon that is derived from creature, so there the word dragon appeared.
And then, in the program.py on line 24, we found where we were using the name space from the other module to actually allocate or initialize a dragon and this is what a dragon with level 50- Now we just say that's it, total number matches was two, we are done.
You might think that this app is largely about files.
But it's not, we've spent a lot of time on working with directories and files and so on, we will have to do that, yes, but that's not the primary focus, what we are going to look at is actually how do we use this concept of a generator method which is implemented with the yield and yield from keywords, along with recursion to put together a pipeline of processing, a chain of what are called generator methods that are extremely efficient at processing large quantities of data so you'll see that we are using this app to actually search through like 2.5 gigs of text files, the memory usage is almost 0 above just standard, hey what's your name sort of input type of app and the key to making this work is the yield keyword and the generator methods.
We are going to talk about path operations and very basic string searching but we are going to focus on this concept of a processing pipeline and we just happen to use files directories and text as our input.
|
|
show
|
4:53 |
So let's just take a moment and sketch out the flow of this application just to build a skeleton as we always do.
So, we have a main() method, and we'll do something here and of course we'll use the live template from main() to call it down here, like so, from PyCharm, and then, the next thing we want to do of course we are going to say define print header, what we need to do is ask the user hey what directories subtree you want to search and what do you want to search for, so those are the next two things we are going to need, we'll say get_folder_from_user(), and similarly we'll say get_search_text_from_user().
then let's just define a search_file() method here, and we'll figure out the parameters in a minute, ok, we'll reformat so we are all good, via PEP 8 and let's start writing, so print_header(), you guys know this, this is old hat by now, so we'll just fly through it, next we are going to get a folder from the user, we'll say get_folder_form_user() and let's just do a quick little test like if they enter nothing we would rather not have our app crash we'll just say hey search that, moreover, if they enter folder that doesn't exist, we'd also want to build the deal with this so just do a test, and we'l leverage the truthiness of strings so we'll say if not folder, print something like sorry we can't search that location, now notice here is a little single quote, this is one of those times where double quote strings are nice and handy and then we'll just exit out of here.
Next thing is we'll get text we'll say get the text from the user and basically exactly the same deal here boom, I can't search for nothing, notice PyCharm has these grayed out and that's because it knows there is no return value from that method and yet we are trying to store in a variable, it will not crush, it will give us the implicit return none but that is going to be highly unuseful for us, that is kind of what the warning is, now finally we want something like search file and over here we'll say we want to search the folder and the text.
Now, I don't really like this name so much, I'd rather say search files or search folders, something like that so we can hit control t and rename this and go I want to search folders, like that, and do the refactoring, and down here and if there was 100 files all potentially leveraging this and docstrings leveraging and so on, all of that would have been fixed, now we need our folders and text of course, excellent, so these should be pretty easy to write, let's just come here and pull this out, now we are going to do a little more than just get the text that is the folder, we are actually going to verify it.
So ask the user what folder do you want to search, then we'll say if not folder: so for some reason it came back empty I don't think it can ever come back as none but let's just verify it, let's little safe and we'll say or if possibly the folder is just white space, right, so in either case we'll just return none so we have nothing, there is no folder and that will trigger remember up here that will trigger this and say no, it doesn't work.
Next, let's verify that this directory exists and that we have access to it.
Again, back to our OS.path.exists, now we can be little better, we can say isdir in case they put a file and we are trying to search through subdirectories, or something like this, maybe we'd write our app to behave differently if you give it a file versus a folder, but we are not doing that right now, so we are just going to say if it's not a directory we'll just return none and then finally, let's clean this up a little bit and say we'll return OS.path.absolutepath() so we have a nice absolute path for a folder instead of something relative, as you will see having an absolute path is helpful for later on.
Next, let's just get some search text here, we'll say text = some sort of input again, we'll ask what are you searching for and then we'll give a little warning that says single phrases only, because we are not going to do like words that mean and breaking into parts we are just going to do basic substring searches, that's an interesting app to write but it's not the focus that we are really trying to get here.
So we'll just return text.
All right, it looks like things are going pretty well here, now let's just finally do this and just do a little print statement just to show everything is hanging together, print would search this place for that.
So let's run ti really quick, let's just search the current directory, it looks like I made a mistake, let's see here, not folder, that should be or not strip, ok, perfect, ok let's try again, I want to search the current location, and I am searching for the word cats, so we would search/users/screencaster/desktop_08_file seracher for cats.
Perfect, so we have kind of all this parts of like user input and everything completely done, and now it's just a matter of implementing the search method.
|
|
show
|
6:37 |
Ok, let's implement the actual search.
The first thing that we need to do is actually go to this folder and find all the files.
So again, our friend OS will help.
We can say OS.listdir() and give it a folder, and here this is going to return all of the items in here so let's say items, notice I am not calling them files because sometimes they are folders, sometimes they are files.
So, we are going to do our loop, we are going to say for item in items: and we want to check, well if this is a folder we don't want to do this so we'll say if os.path.isdir(item ): we are going to not process this item but we want to keep going to the loop and the perfect keyword for that is continue, so basically go back to the top of the loop, pull another item and keep going.
If it's not a directory, it has to be a file, so then let's write a method that will just search the file, so what we need to do is store the results, the matches somewhere so we'll just say matches = search single file and we'll give it the file and the text to search.
Now, one thing that we are going to want to be careful with is when we say listdir this only gives us the file name, not the full path name so either way whether it's a directory or a file, we need to do something like this, we'll go full item = os.path.join() and remember, we made sure that this is an absolute path and then we want to join up the subitem, perfect, so here we got the check full item and down here we are going to pass full item, somehow we are going to- we'll get the search method in a minute, somehow we got to do something with these and that's going to be like a collection of some sorts, so let's say all matches, initially it's an empty list and I'm going to put some stuff into it in the end we will return all matches, so here we are going to say something like this, if this was one item we would say append, but if it's a collection we want to add the items from that collection individually here which is what we want, we'll say extend matches.
Cool, so last bit to make this work for round one is to implement this file, so now we know this is a full path to a file name so we'll just go with that and here let's be a little more clear we'll call this search text.
The first thing to do is open the file so we'll just use our little context manager to make sure we close it under all circumstances we'll say file name read only and let's treat this as text, you'll see there are potential problems when we get into things like binary file, so if I give it a folder and it just starts go through all of the subfolders, what if there is like images in there or something, then we want to just go through each line in the file and check it to see if the search text appears in it, so it turns out that these file streams are iterable so I could say for line in fin: and in a really nice way just sort of smoothly stream over them without loading the whole file of them in the memory as like an array of strings or something like this, this is also going to be really key way to use these generator methods later on but we are not there yet.
So the first question is we want to search to see if this substring is in the line and we probably don't care about case sensitivity, or things like that, so let's say if line.find- there is two ways to look for substirng, I could say index or I could say find, if I ask for the index and it doesn't exist it will actually throw an exception, but find, we'll just turn negative one if it's not found, so I'll search for search text and before we do we want to create a lower case version of that string, we'll say if the return value is greater or equal to zero so they actually found find found the substring.
The other thing we got to do is make sure that this is lower case, we could do that once above here instead of every time we had a file, so let's do something like return text.lower().
Cool, so if this is the case let's for now just somehow collect up this line of text, we are going to see that this is not the ideal way to do this and we'll fix it just in a moment, but let's come over here and create a little submatches, matches just for this file, and that's the case we'll say append and we'll just, for now this is not the final answer, we'll just append the line of text that we are going to match.
And in the end we'll return matches.
So a very limited version of our search is I think working, let's give it a test so we want to come to create a list to store all of our matches, we are going to get all of the directories and files in the current folder, we'll go to them, we'll build up the full path which is to join the full path with just the names of the subitems, then we'll see if it's a directory for now we are going to skip it, if it's a file we'll search it and if there is a match, we'll add those in there if it's empty then it will just have no effect.
Awesome, let's go and try to run this.
So on my desktop I actually have some files here that we are going to go search, and let's see, if we go to some classics like you can see we have Dracula, The Adventures of Sherlock Holmes, things like that, now this is the full text of the Sherlock Holmes book and I got this from Project Gutenberg and there is a bunch of other ones, we'll go through in a little bit, but these are actually the full files so you'll see this is 160Kb that's 777 Kb of text that is a beast of a book, Ulysses is 1.5 Mb, so we are going to go through all of these files here, notice we are not going to get into the classics yet, because we are just looking at the files.
We are skipping a case where it's a directory, so let's go and search here, right, so we are going to search that books folder which is full of those text books and the phrase we want to search for is let's say "friends", we'll that's pretty fantastic, except for we forgot to print out the results, let's do that really quick.
Ok, so we'll capture the matches and for each one int here we'll just print this out.
Try again.
Search for friends and you can see we found some results, look at that.
So we went through all the various text files and we have printed them out, now this is not a very helpful answer, but at least you can see that it's working.
Let's just do a little bit of a sanity check, "And Harry of the six wives' daughters and the lady friends from-" "Your friends are inside," remember, we are just doing substrings, so friends, if we had searched for friend it would also find friends, we are not doing anything fancy it's just substring, but it does look like it's working, now what line in what book did that appear in?
I have no idea, so our next job is to fix that so we actually return more information about our search.
|
|
show
|
3:50 |
So it's time to improve the results that come out of here, remember, we are just passing the actual lines of a bunch of files, we don't really even know which ones those are, we just start with the folder that have some kind of match, so we want to bundle some information together about these matches, now we could use classes and those are very powerful, but really we just need things like what's the line number, what's the text, what's the file name and so on.
So it turns out named tuples are perfect, so again, we'll just use our collections of a SearchResult = we want to use our collections.namedtuple() and then the first thing is the type name the second argument are basically the fields so it will be file, file line and text, let's say those are the three things that we are going to return there.
Now let's change this, so not just append a line but actually make results, so we'll say m for match it's going to be a SearchResults() and in here what are we going to add, we want to say line = line I think I called it and file = filename and the text = ...
actually, text = line, that's the line of text and I need the line number here, so we have to compute this ourselves so way we are doing it but that's ok, ok, so now we are going to append that and let's just run it again, remember, these named tuples have decent output, ok, we are going to search our books again, and we are going to search for the word friends, and here are a bunch of matches that have way more information, so you can see we have the file is the Ulysses book and these are the lines and we also have Tales of Two Cities and The Doll House, they all talk about friends.
Ok, that works, but let's do a little bit nicer output so we can read it here, let's go at the top where we are doing this print and for a little bit let's go ok, stop this, what we are going to do is we are going to say print() and we'll do something like this, there is a match ok so we'll say match then we'll print out the file, the line and the actual match text, let's run it again.
Same folder, this time let's search for happiness.
All right, so we have some matches, it turns out in Ulysses on line 20899 rule of happiness of the better land- yes, this is a much nicer output.
Now, something is funny like there is two lines here, and yet we are only doing like one print on this which is kind of unusual what is going on, remember, when we read an individual line from the files they actually have the new line on there so that new line is appearing here and I'd rather take more control over the text rather than assume that that's always going to be there so let's just say strip(), now it could do an rstrip() just to get the stuff off the end or maybe if there is white space you want to pull it to the font, anyway so let's do it this way.
Great, it looks like our searching is working perfectly.
Now remember, we are giving it this folder and it turns out we are searching these files but we are not searching Dracula or Adventure of Sherlock Holmes, or anything like that, let's just prove that, we come over here and say I want to search for Holmes, well, Ulysses apparently has some place that talks about Sherlock Holmes but we are not getting into that subdirectory.
And this is to be expected because we said if it's a directory ignore it, there is a variety of ways in which we could solve this problem but it turns out the most natural way to solve these hierarchical problems are to use something called recursion, so let's take a moment and go look at this concept and then we'll come back and apply ti to our application.
|
|
show
|
3:10 |
So let's take a step back from application for a moment and just open this empty file called play for play around, where we can just play around with some ideas.
We'll do this for recursion but we also do this for our generator methods later.
Let's take a really simple case something like creating the factorial of a number.
Now remember, factorials are sort of iterative processes.
The factorial written with like a number and an exclamation point right so like let's say 5!
= 120 and the way that you get that is you take 5*4*3*2*1 and each step if I start here the thing I need to multiply is the number minus 1 times the rest of the factorial and then form here I need to multiply it by that smaller number minus 1 times the rest of the factorial and I keep doing that until I get down to 1 and then I stop.
So this turns out to be really easily modeled with this concept called recursion.
So let's look at this in code and then we'll look at this conceptually and we'll go back and apply this to our searching app.
So here we have this factorial function, empty, but it's going to return the number which is the factorial so here I have pre loaded the few, we'll compute 5, 3 and 11 factorial and these numbers grow tremendously large in a hurry.
So remember the way you take the factorial, is you want to take the number in this case we are going to have n and we want to multiply it by the factorial of the smaller piece of the stuff to the right, and we kind of keep building that up and it turns out that we can say we want those factorial to be n times and we can call the same method within itself with different parameters, so for this step from here is just going to be whatever that is times 5, so n*factorial (n-1) now if we do it like this we are going to end up in a stack overflow situation, it's going to keep calling this function over and over and over until it just runs out of space.
So once we get down to one we are going to stop and we just say 1!
is defined to be 1, so we'll say something like if n is 1 return 1 otherwise we are going to come down here and do this we'll just say return n*factorial (n-1) so let's run this, now this one this should be 120, this one I know should be 6, this one should be huge, I don't know what factorial of 11 is but I know it is much larger than you expect, so let's run this and see how it works.
Awesome, 5!
is 120, 3!
is 6 and 11!
is some big number, now we don't like to look at numbers like this, right, we like separators, so we can actually do that by putting colon comma on all of these, will make a difference on the small ones but on the larger one you can see we now have separated those with digit grouping.
So this is a recursive algorithm, let's take a moment and look at this as it's one of our core concepts.
|
|
show
|
1:35 |
Here is a mathematical computation that is defined iteratively and that makes it very natural to define it as a recursive function, the factorial of number n is just n times the factorial of the smaller number until you get down to a pre defined number that has a factorial known and where the factorial of 1 is defined to be 1.
We are taking this function factorial and we are calling it again within itself with slightly different parameters and we need to make sure that there is somewhere along the way some kind of test where we no longer call the function recursively so that is kind of like our if we are if we are remodeling this as a loop this is how we would like break out out of the loop, but in recursion, we just return from the function without doing more of recursion, let's look at this visually.
Here we are going to take factorial of 3, that's going to return 3 times whatever the factorial of 2 is, and when we call factorial of 2 we actually are back in the same function but now entirely different data so now we are going to say well factorial of 2 is going to be defined to be 2 times factorial of 1 when I pass one down but remember, the factorial of 1 is just 1 and as we work our way back we pass 1 up we do 2 * 1 and that's 2 and we pass the 2 back and we say 3 * 2 and that is 6 which gives us our answer.
This is one of the core concepts of computer science and computer programming, it's not used super often but when it is used it so perfectly aligns with the types of problems you are solving, if you are dealing with hierarchical data structures or you are dealing with these iteratively defined algorithms, recursion may be what you need.
|
|
show
|
2:52 |
Ok, let's see how recursion will us search not just the current directory that we give it but the entire directory tree starting at the directory we provided as the root.
Here we have search folders, this is going to be our recursive function, and we are going to come here, we are going to list all the items here, we want to for each item build up the full path to that item.
Remember these little bits are just relative, they are sort of the name but not including the path.
Before we said we don't know what to do with directories let's just get out of here and then we said if it is a file we will go get all the matching lines and information about that in extend to all matches, well now we can use recursion to solve this problem so let's try this like else really quick, so we sort of put these in these two cases so if it's a directory we do one thing otherwise, if it's a file we go search it.
so what are we going to do, we are just going to say if it's a directory, let's also search that directory and what method do we have that can search folders, well, the one that we are in.
But we are going to search a different folder, a subfolder, and the name of the subfolder is full item and we still want to search for the same text, now this will come back with matches as well, different sets of matches probably more, you never know, right, but it's all the stuff within that subfolder not just an individual file, and then we'll say all matches.extent those matches as well.
Look how perfectly recursion solves this problem like it was really one or two lines of code, we removed the continue we just said search but a smaller subset of this tree we want to go search and we are just going to recursively do that until there is no more directories we are just down to the end, the end of that hierarchy if you will.
So let's run this and see if it's working.
So again, we are going to search our books, now remember there is that classics folder that has Sherlock Holmes in it previously we did not have results for Sherlock Holmes so hopefully we are going to search the books folder and within there we are also going to search the classics subfolder, let's give it a shot.
Again, we are going to search for Holmes, and here we go, wow, now we have a lot more results and we've got the Ulysses book but we also have classics, The Adventures of Sherlock Holmes you can see on line 11587 the table had not been cleared yet, Sherlock Holmes had been...
beautiful, and you saw that recursion was the key to making this work in the most dramatically simple way possible.
So in some sense our app is done, if what we want to give our app is 10 books that are just all text and maybe that's a couple of megabytes you can see that we can actually search that quite quickly and there is no problem, but if the number of files and the quantity of files gets to be gigabytes of text you'll see there is a severe performance problem especially around memory and we can do much better using some very cool features called generator methods that's what we are going to do next.
|
|
show
|
4:24 |
So let's jump over here to Windows 10 for a minute, and continue working on our app, and the reason I want to come over to Windows is I want to explore this performance problem and the tooling on Windows is super good for understanding the performance characteristics of individual applications rather than system wide.
So, we are going to change the problem space a little bit, we have been previously searching this books folder, and if you go look at the properties you will see that's about 5MB of text, that's a serious quantity of text and it's blasting through ti so that's already impressive, but let's look and differ them out.
Here we have some more books but now we have 2.27 GB of txt files, that is a ridiculous amount of text files.
You'll see that if we try to search that content, well the app actually does surprisingly well, it really does go through and it finally has results and so on, but if we leverage this concept of generator methods and related things that will build on other applications further down the line, we can actually do amazingly better, ok, so just to make sure everything is working on Windows, let me just search the same stuff here, ok so we want to search c/users/mkennedy/desktop/books and let's search for "incredible".
Excellent, so it looks like we've found some inverness to from incredible age...
right, Ulysses, A Dolls' House, not too many results there, but you can see it's working.
Fantastic, now I happen to know from trying this earlier that we need to change this output here and in fact if we print out all of this it's going to be so much output when we go through the 2 GB of files that it actually causes the problems, the significant part of the performance is literally that print right there, so instead of doing this we are just going to go and do a count, so we'll say match count, now right now this is a list and I could just do len of list and just print that out but it's going to turn out when that this becomes a generator, len of generator doesn't mean the same thing, so let me just independently keep track of the count, and we'll just say something like this, and let's put a little comma separator and we'll do .format and match count.
So let's run this one more time, ok, same place let's search for "funny" and apparently we've found 33 matches of the word "funny" and the 5 MB of text that was really quick, that's awesome, right.
But they just sort of push a little bit harder in the performance perspective, let's search the 2.27 GB of text and we are going to search for something, maybe question mark.
So let me introduce you to process explorer, so process explorer is kind of like task manager, activity monitor, from OS X but it gets a ton of information both visually as well as so things like performance counters on windows to tell you what is going on with the apps, and it lets us, here is our Python app that is waiting for us to hit go, all right, here we go, our app goes, here is our sort of operating memory down here and it's dropping into the distance so it turns out that searching for question marks in this file there are ton of them so we are building them up into our list as we are recursively going through these files on disk.
You can see it's pretty computational heavy, pretty IO intense but really the memory is just growing and growing, you'll see that when we get to the talking about generators that maybe this is not the way this app has to behave, right, we can actually incorporate very minor changes into our app and get dramatically better performance at least from a memory perspective.
All right, our process has finished and we found 2.7 million question marks in those files, and look at the memory, this is not the most amazing outcome that we could have had.
It turns out it took almost 400 MB the way we implemented our algorithm, and depending on the how we hold the data or the size of the data, it could be even worse.
|
|
show
|
7:16 |
Let's explore generator methods and we'll use the Fibonacci numbers, an infinite series of numbers to do this.
Here we have this Fibonacci numbers that are defined to be 1 and 1 and apparently I left one out, and then the next one is the sum of the previous two, so 1+1 is 2, 1+2 is 3, 2+3 is 5, 3+5 is 8 and so on.
Now this is an infinite series of numbers, so let's write a function, a traditional function like you've seen us write with lists and building up the data and then passing them back to generate this.
So we are going to define a Fibonacci function, initially I am going to give it a limit, if there is no limit, you'll see we'll just flat out run out of memory and crush our application, so we are going to have something like a list that holds the numbers, and then we'll have the way you compute this is you have a current number and you have a next number, then I'll say while current is less than a limit, and the implementation in Python is actually really fantastic so what you do is you can use tuple projection and avoid having a temporary variable, a lot of times you will sort of see 3 steps to do this we can do it in like one, it's cool, so we'll say current,next and we'll do- that's the variable so we want to do a tuple unpacking into them so current is going to be signed next and next is going to be next + current.
And then, we want to add to our numbers current, and then we'll just going to return the numbers, so let's print Fibonacci and let's say we want all the Fibonacci numbers that are less than 100.
Now we still have the factorial stuff above but don't worry about it, just pay attention to the last line.
So here are our Fibonacci numbers computed up to a sort of including the last number there, so let's actually print this out in a slightly different way where we have a little more control to go through, so let's say for n in Fibonacci like so, and we'll just print(n) and we'll do that end = just a little comma thing so we can have on the same line, let's run that, perfect, it looks basically the same but here is the key thing, let's put a break point here and actually step through, we'll actually step through this function call, now there is nothing fancy here, there is nothing that should be surprising to you here, we are just going to step into this code and step through it.
So I want to come down here and say while this is not the case we are just going to step along, you can see on the right the numbers are changing, 3 next should be 5, then next should be 8 and so on, and we are building up this list of numbers here you can see the list is highlighted but here you can see this list is building up, and all of the computation is happening in this method, and then, in the very end, we decide we've had enough, few more and we'll be there, then we return it and then we loop over like so.
Ok, that is how traditional methods work, but what I am about to show you is not a traditional method, it's something called the co-routine and it's extremely powerful.
So let's take the same method and let's define a second variation of it.
So let's call this Fibonacci co and down here we'll call Fibonacci co.
Now we are going to take two steps to go through this and understand it, first we are going to just change this to work exactly the same and then we can see we could even do better.
So instead of letting all the work happen in the Fibonacci method and then processing the results what would be better when we have these large sort of pipelines of data processing would be to pull one back and then inspect it, do a little work with it then throw it away.
And then pull the next one, inspect it, do some work with it, throw it away.
That way, even if we are processing a billion items, we only pull one into memory to work with at a time.
And you can see that we'll do the same thing here, so instead of putting this into a list we can use this keyword called yield so I come over and I can just simply say yield current, and the way this works is when I start using the yield keyword this becomes a co routine, and when I say yield some item I am basically declaring to the Python interpreter what I want you to do is create a sequence that can be one item at a time computed.
And, here is one of the items, every time I say yield something, I say here is one of the items, I never have to return all the items at the end I just say here is an item, here is an item, and when I stop saying here is more items than that's the end of the sequence.
Let me just use some quick formatting here so that things appear correctly, so we'll say via list and via yield, now it's running.
We can see via list we get those numbers and via yield look at that, we get exactly the same thing, but have we really gained anything, ok we have this cool yield keyword, we don't have to have the list, that got a little shorter, but what you'll see is we've actually gained something tremendous, so let me put a break point here and debug it again.
So we'll step in, now this should look kind of similar, we'll step along here, and we are computing the first round, and before we are adding it to our list, watch what happens now if I step into this, well, unpack here I only computed a single item right, notice that n is 1 and now if I go around again and I step in again watch, we should go straight to line 49 or maybe 47.
Straight back to 47, we are not rerunning this method, we are resuming this method, right here and if I step, step let me step down here now n is 1 again because that happens twice, then if I step in again here we are n is 3 and so on, so as we get them back we get the first item and we've only had to do enough work to compute the first item, moreover, we don't have to put this into a list to gather up all the results so then return them, you know we are never holding on to all the results at once we are just giving an item at a time.
So to make that point kind of extreme, let's suppose we want it all the Fibonacci numbers, the infinite sequence of numbers.
We could remove this limit and we could just say I want to do this forever, now obviously doing this forever is going to sort of be a problem, it won't crash, it will just keep going and getting slower and slower, and so on, but it does let us down here as a consumer decide when we've had enough so we could say if n > 1000 then break, but we have access to the entire infinite series.
Now, if I did this with the list, this would just run for a long time, run out of memory and then crash, but that's not what happens here, we just get one compute the next, compute the next and any step along that path is super cheap, basically a couple of additions and a return value, so let's run this again.
Boom, look at that, with yield we got the infinite series of numbers and it only took microseconds to compute it.
All right, and you can take these generator methods and combine them with other generator methods and create a pipeline of processing and that's exactly what we are going to go do in our file searcher app.
|
|
show
|
1:47 |
Let's look at this core concept of generator methods.
Here we have a Fibonacci method and we are passing the limit to it, to say how many numbers do we want, in traditional type of methods you could have a list and we could go through and compute all the items adding them to the list and then when all the answers are done, the entire lists are generated, we could then return that list.
Generator methods provide us a different mechanism, a different way to do that, it dramatically different performance characteristics.
Here what we do is we come to this method and we work our way down just like any regular method until we get to the yield keyword, and the yield keyword says here is an item in our collection, and immediately this method is sort of suspended, the item is returned to whoever called this function as the next item in the sequence, they can process it and either decide to continue pulling on the sequence from like a foreign loop or something or they can stop and that's it, you've only computed one item.
But if they come back and they ask for the next one then execution will resume right here, and it just goes around, gives the item back, the caller looks at it and it gives you this kind of lazy evaluation of items that ultimately turn into sequences where the sequences don't have to be stored in memory all at once before you get to work with them, so these generator methods and this yield keyword are really powerful when you are processing large sets of data in series or you are even building pipelines where one step pulls on some large data source and then another step takes that process transformed, filtered a bit and does more work on it and then you keep doing that and passing along until finally some sort of pipeline spits out a smaller transformed set of items, the composition of generator methods is amazing there.
And that's what we are going to build in our file searcher app, when we get back to it.
|
|
show
|
6:00 |
We've seen this concept of generator methods let's go apply it to everything we have going on here and I'll even show you another keyword we haven't had a chance to talk about yet.
So let's start at the bottom.
Here is a method that is a traditional method it puts all the stuff into the list and then once that computation is done everything is computed it returns that whole list and then we just have another list above that we had even more to and keep extending as we have more and more files.
We can do better.
So, instead of doing matches here, let's get rid of this, and instead of doing append m we will say yield m and we won't have to return the matches maybe I'll even comment it out for you, like so, so no more lists, we are just doing yield, now this would already work, what we are doing is calling up here search file and here we get our generator, but we can take any collection generators or iterable collections and extend this list, but we can actually do better still so this is a generator method, this is a regular one, but we can also apply the exactly same idea here and the same idea here, now this gets a little tricky because I have to say for m in matches: yield m, now, that's not the most fun thing to write, it would work but I'll show you something better, same thing down here for all the matches there we want to do that, and then we no longer have our return so here is the generator method and this is going to come through and each time that we sort go pull something out of this collection, it's going to go until it hits one of these, which the generator and it's going to hand one back so if we only wanted the first 4 matches we could compute that extremely quickly.
However, this line 65, 66 this is not the coolest thing, it turns out that Python 3.3 added basically a keyword that will do the same thing like take a whole set and sort of hand them back one at a time, and so we can simplify this and just say yield from matches, and if we really wanted to simplify this we could actually come down here and just write it as one line, we could just say yield from that, never even store matches here, similarly yield from that; so down below, we have search files, that individual searching of a single file is a generator and we only ever have a single line in memory at a time.
Now up here, as we work through all the files in our directory or even recurse into a tree of directories and their files, we are only pulling back one item from either here or here at a given time, and that means we only have 1 line in memory at a time, really one search result and then we can go up here and we are printing out now, let me just show you that this is still working, let's bring this back and then let me search the simple files again, just to show that we are actually still searching just like before.
So let's search the small set of books for Holmes, there you go, you can see 468 matches, and we are searching the Ulysses or searching The Adventures of Sherlock Holmes, perfect, it works exactly the same but from a performance perspective it's not the same, let's run it again, and this time we are going to search the large set of files, and again, we are going to search for how many question marks are there, there were something like 2.78 million question marks and remember, we had to use like almost 400 MB of memory to answer that question, remember, 400 MB, what's it going to do this time, can we do better?
Oh, here, hold on, let me stop this really quick, remember, we didn't do the output, it was too much going really, we just did a little count, so let's rerun it this way.
Ok, here we go again, it's going, 3.8 MB remember, it should have jumped up to 300 MB, a gigabyte, what is going on, this is so absolutely amazing, look at this you guys, we are processing gigabytes and gigabytes of code with almost identical algorithms and yet the memory usage is the same as if we are processing like a single line in memory, because that's all we are ever holding, is a single line in memory, ok, great we do have the file stream open to some huge file at some point, but we are seeking over, we are streaming across it.
Let's just let it run and see where it goes.
It's done, look at that, look at the memory usage, look at the CPU, look at the performance, it is so much better than it was before, in fact, I kept the previous one around, let's have a look at it, it's not really fair to put them side by side, because the scale of the graph is not the same, but I think we'll get the sense anyway.
So on the left is the old bad sort of standard procedural code style and now look at the memory, it goes from 3 MB when I was starting out to 394 MB; ours went from 3 MB to 4 MB.
And that was it.
If you look at the size of the CPU graph or sort of the length of any of these graphs, you'll see they are basically identical in computational time, it looks actually lower on CPU usage, presumably it's doing less garbage collection less allocation, doubling of lists and copying them and things like this, and all we have done to change that algorithm is use the yield and yield from keyword instead of making lists appending and extending them.
The code we wrote actually got a couple of lines shorter, so this is the power of generator methods and any time you are processing like a pipeline of lots of data you saw that you can chain them together to create these pipelines basically effortlessly, we'll see that there is even a simpler way to create this type of structure something called a generator expression, right, but we'll save that for the next app.
|
|
|
1:02:38 |
|
show
|
2:36 |
It's time for application number 9.
This one I am calling the real estate data miner app.
So what we are going to do we are going to take a whole bunch of historical real estate data feed it to this application, and be able to answer pretty interesting questions like what was the average price of a two bedroom house sold in this region during this time, things like that, so what it's going to look like- well, you'll see something like this, standard header, and it's going to load up a comma separate value, a csv file of historical real estate data and it will detect the header so we'll use this, basically define the columns that we'll use to answer our questions later, it can be somewhat dynamic with this, and then we can answer questions like what was the most expensive house sold period, well that's some kind of 4 bedroom, 3 bath house for almost million dollars in Wilton And the least expensive house was oh my gosh, like a house for a 1500 dollars, 3 bedroom 3 bath in Lincoln.
Something is going on there, who knows the history of that one.
But the average price was 234,000 dollars for 2.9 on average bedroom, 1.8 bath on average, right.
And if we restrict ourselves to say well let's just talk about only 2 bedroom houses and give me some stats on that, well the average price was 165,000 dollars obviously two bedrooms in 2 bedroom house and 1.4 baths.
So what are we going to focus on when we build this app.
The primary thing we are going to look at is something called list comprehensions and something very closely related to them called generator expression.
These two Pythonic concepts allow you to take what would otherwise be loops and condense them down into much shorter, more concise types of set based operations, in some sense it will move from procedural programming to declarative programming.
We also get to look again at the string and representation magic methods, string parsing, we'll specifically look at the csv file format but this concept could be applied to many different types of formats really, somewhat like we saw in app number 8, we'll be able to use these generator expressions and data pipelines similar to the way we did for generator methods.
You can think of generator expressions as simplified inline generator methods if you will.
Finally, we will get a chance to look at a challenge of writing code that has to run on both, on Python 2 and Python 3, as you saw, we are talking about averages and there is a nice statistics module that was introduced in Python 3.4, it is not available in Python 2, so what will we do there?
Well, you'll see that we can write the same basic code and just adjust our imports and make it work.
|
|
show
|
3:15 |
All right, let's write the first part of our real estate data mining application.
And we'll follow my standard convention of having a main method, at the top here, and then using the main live template in PyCharm, just sort of use the proper import safe way of invoking it, now we are going to do a couple of things, we are going to as always define a print_header() and we'll just print out some basic stuff as usual, so here is a standard header which we'll call from up here, the next thing we need to do is get the data file, so let's write a method not the actual data but just the file name for the data, so notice down here, let's talk about where are we getting our data from, we have a data subfolder next to our program, and we have the Sacramento real estate transactions for 2008, so this turns out to be all of the real estate transactions that I could find for Sacramento in 2008.
Personal individuals, no business stuff.
So we want access to this file, now we want to use again our OS so we'll import OS and a lot of times we've been saying .path, .join and we would give it something like '.' for the current directory and then we would say data and then we would say the name of this, in PyChrm you can hit ctrl+t and it gives you a rename which is probably the easiest way to just copy the name there, but this '.' assumes that we are always using the working directory just above the data folder, we always want to look right next to this program for its data no matter where it's running so let's get the base folder in different way.
We'll say base_folders os.path.dirname() and remember, there is an __file__, a __file__, implicit variable for every module that's going to say here is the full path, so we get the base folder and then we can just put base folder here, right, so now it doesn't matter where we are running our code, we'll always get back to this, and then we'll just return that, let's do a little format really quick, and let's get the file it's called filename say get data file and then just let's print this out really quick.
All right, that looks correct, I have it on my desktop and then here is our 09 real estate data and so on.
Perfect, so that should work for getting us the file now the next thing to do instead of this is to actually load the data so I'll just say data like this and we'll come back to what that means, I'll just say load file, so we are going to do this in a couple of ways, we'll start out with like really basic way and then we'll come back and do a little bit more.
Ok, so we got our data sketched out, and the very last thing that we are going to do, after we loaded up, maybe we'll print it out for a minute but I guess we can go and write query data and that will just spit out the report and so on so feed it data, like so, and that's the last method we'll write, and then, move on.
So there we have the basic structure for our app and I don't know if I have shown you this for a while in PyCharm but over here you can actually see the structure and jump around if you like that's kind of handy, even better when you have classes and hierarchies and these sorts of things, so next we are going to work on load data and actually parsing our csv file.
|
|
show
|
3:47 |
So now it's time to load the data, we've got everything just lined up for us to grab this file and run with it.
Let's do a quick little context manager we'll do a with open file name as we'll do read only text, and let's go and set the encoding, to utf-8, remember, this is the file we are going after and it's right at the top you can see we have the street then the city, then the zip code, then the state, beds, bath and so on, this will give us information about all the real estate purchases that have happened over here.
When we are processing the csv, there is actually a couple of options, one option would be to just parse this directly as strings, now three is another option we'll use in just a minute but let me just kind of sketch that out for you because understanding how that might go, would help you approach file formats that there is not a built in reader for.
The first thing we need to do is capture the header, and that's not the thing of the top that we are printing out but you know, the sort of column names at the top of this, so we can say fin.readline() singular and we could do something like print found header, to be and then we'll just print that out.
And then the next thing we need to do is we need to go through line by line and turn this into some kind of data structure, we'll start simple and we'll get something more interesting in a minute, so we can say for line in fin:, now we could have just started out this way but remember the first line is the header and then every subsequent line is actually the data, so we do one read line and then we stream over it, and we need to turn the line into the individual columns of data and with the csv comma separated value one way let's call this line data, one way to do this would be to say line.split() and we can just split on the comma, now this is not perfect because you'll see that there are ways to like escape commas and so on and the edge cases can be a little bit tricky, so we are not going to end up using this, but let me just print out well, let's build up all this we'll say lines, and we'll gather this here, and we'll say lines.append(line_data), and then let's just print out let's say the first bunch here so we'll say print lines, we don't want all like thousands of lines, so let's just say we'll take the first line, remember we can slice from 0 to 5 and just get the first lines like so.
So let's run this to see what happens.
Fantastic, here is our header, that looks good and look here is a string for High Street, the string for Sacramento, now there is two lists here, and we have basically a list of lists, that was the first row, that's the second row, now notice the very last bit has a \n and that's because it's coming form when we read a line out of a file, a text file in Python it keeps that on the end there, so let's do a strip and we can strip the bits off the front and the back and I suppose we could go and do that with the header as well, now we try that again, perfect, our latitude is looking solid.
Ok, so now if we wanted to go work with the data, remember, this is not where we are going to stop, this is where we are starting, if we wanted to work with like the number of beds if I want to just print out those, I could somehow go over to this bit here and for each line, and the line data, I could come over and put bed was I think the fifth one, it didn't really matter, let's say it's a fifth one, then I would say 4 and I could say bed count equals that, ok, now there is a couple of challenges, because this actually comes back as a string so I need to parse it and so on, but this is sort of the basic way of working with this data.
So that is kind of how we would attack csv files from scratch, but Python has built in support for csv and we can do better, so let's use the csv module and then build on that.
|
|
show
|
3:39 |
Ok, so that was kind of a cool start, but let's just save this here and call this load_file() basic or something like that, I'll leave it commented you can have that if you like, but now what we are really going to do is something a little bit better, we are going to go over here and use a module called csv, so there is a couple of formats that have built in support in Python, one of them is comma separated values, another one is Json, Json is actually my favorite text based format, and we also have xml.
So we can actually import this module up here at the top, I just wrote import csv at the top as you know, and then I can come over here and there is a reader, the most natural thing would be to say create me a reader, and what do we need to do, we need to pass an iterable, well, what is that iterable, here is one, fin, so we want to get the header out, like so, and we'll capture the reader, like so and then we can say for row in reader: I can just print row, let's see what happens here.
Perfect, look at that, it's already done the parsing and the reader has a lot of support for things like the delimiter and we could say like the delimiter is this and we could say what the escape character is the quote character to escape and things that might have a comma in them, like names and so on, so now you can see if we use this delimiter nothing gets separated, but if we use commas, boom, now it's separated again, right, these are lists of individual bits of data, split on that, just kind of like we did in our own code.
However, what we get back if I just print out the type here, with the comma how about that, is a list and that still means if I want to work with the bed I have to say something like row of 4 and I need to know that the fourth item is the fourth column is beds, or whatever right, 0, 1, 2, 3, 4, but if somebody changes this over time and they add another column like country or something up front, all those indexes break, our code is super fragile and we have to know like oh yeah, what if 4 means beds, of course it does, right, that means nothing to humans, so we can come over here and actually do better than this, we can come over and say reader=csv.DictReader().
Now, DictReader doesn't return rows it returns a dictionary for each row, so instead of using numbers to find the data, we use the names, what names- the column names, so we come over here and give it the same iterable and then let's loop over these again, now notice we have a dictionary and the type is residential, the zip is this, the latitude is that, this is so much easier, let me show you how to use it really quick, then we'll take a moment and look at the concept of dictionaries in more depth.
So instead of using numbers, we can use names so if I just wanted to print out bed count, that would be super easy now and I don't care about the order or how many rows or how it revolves over time, I just say row of beds, bed count 3, bed count 4, beautiful, and you can see over here in our data there are those.
So this dict reader is really the way I recommend you work with comma separated value files.
It gives you a lot more durability you don't have to know that like the fifth element is actually the bed count and if that changes it's fragile, all that sort of stuff, it just detects it and then builds up these dictionaries for you.
So what are dictionaries, let's take a moment and look at this core concept.
|
|
show
|
5:45 |
Let's put our app aside for just a minute and talk about dictionaries We can create dictionaries in a variety of ways and depending on how you want to store data, one way makes more sense than the other.
The idea of a dictionary, it's kind of like a list that you can put items into it and then get them back by looking them up, but instead of using numbers like list of 7 you actually use something called a key, now those keys can be strings, they can be numbers or they can be a variety of objects, whatever the natural thing that you use to identify something with you can use that as the index if you will, instead of a number.
So let's create a dictionary, and there is several ways to do it, we could say lookup= {} this if you have open close curly braces that's a dictionary, you could write the identical code say look up= dictionary, like this, or you could even initialize it, like this lookup = {'age': 42, 'loc': Italy}, right, and we could use this there is actually a variety of ways to do this we could say basically the same thing dictionary, but instead of using strings, we can use named parameters, keyword values like so, almost, like so.
So I could print look up here, you can see the sort of Python print out style is this, if we have a dictionary though we can get the values back, if I wanted to know the location I could say give me lookup of location, and instead of using numbers, I say give me the second thing, it doesn't matter where or what order this is in, in fact these are unordered collections, but using the extremely fast algorithm to find this value, so here you can see the location is Italy.
If we try to get something out of the lookup that doesn't exist, if I try to ask for the category, it's not super happy, if you are going to do this you have to do a somewhat non obvious but kind of cool syntax, you have to say if cat is in lookup, if the key is in there, then you can print out cat.
So if I run it right now, there is no cat in here, so there is no output, but I can add cat dynamically later after I create I can say cat and I just set a value is fun code demos and now when I run it, hey cat is in there and its value is this.
Sometimes you use dictionaries to store data like this, right, sometimes you store heterogeneous data I want to take an individual piece of information and say the age is this, the location is that, in fact whenever we create a custom class, if we have a class like this, remember the wizard, and we defined the dunder init, and we said it's going to take a name and a level, and we said something like this, this actually creates dictionary entry when the dictionary key values level and then the value is the name of it, so dictionaries are actually super central to object oriented programming, let's create a wizard really quick, now if I print out Gandolf you will see there is not much going on right here is Gandolf right here, main wizard, but there is an implicit dictionary that actually stores this data and you can see the level and the name, right, so understanding dictionaries is ultra important in Python because it's not just about an interesting data structure, you use sometime, in fact all objects are built on the concept of dictionaries so you really have to understand them well.
One use case of this is sort of storing in an individual item disparate data like so like level name or age and location.
Another one might be you have a bunch of similar data like what if I got a 100,000 users back from a database query or from a web service, and I'd like to be able to quickly pull them back by ID, almost like a database, like in memory I'd like say give me user 2,711, bam, and I want that back instantly, not seeking over an index, not seeking over a list and doing 2,000 comparisons and then finally going oh here is the one you were looking for, but instantly finding it basically.
So I have a little bit of code I stash here, look at that, so here we have our users and this is, imagine we have many, many more of them and we would like to be able to pull them back by their id, so we can also use dictionaries for this, let's say lookup = new dictionary again, and we can just say for each u in users we can say it to look up and I would like to when I am going to look up a user maybe I want to look them up by id, so I can just say this, any time I want if I have a million users if I have their id I can get them back ultra fast so I want to know what the user with id 3 looks like, we'll let's run it, bam, that's user 3 with email user3@talkpython.fm.
we could use a different index, we could use email, now if I run this it's going to crash, I say I want the user with the key of that email address boom, there is that user right there.
So we can look them up by whatever we want, if we can say we would like to have a bunch of users and be able to ultra fast pull out the one by you name it, we could do user name, we could do email, we could do id, you can only do one but this lets us store homogeneous sets of data but then find them by key or as up here, dictionaries were using more for storing disparate data, not different instances of it, but actually different but related data like both age and location are somehow tied together, we can access the part.
It's more this top style that we are going to use for our csv file, so let's take a moment, look at this core concept, and then we'll go back to working on our real estate data mining app.
|
|
show
|
1:40 |
Let's look at this core concept of dictionaries.
Dictionaries are kind of like lists and they will store a bunch of different types of data for you, but lists are very limited in the way you can get that data back, you can only get that back by index.
So I can get the third item, or the hundredth item or the minus one item even to go and get the last out of the list but that's problematic in a lot of ways, a much better way to store data if we are going to randomly access it is by some name that makes sense to people not just an index, so here we have an information thing, maybe it talks about a person's age and the location, so here we have a dictionary and we are setting the age to be 42 and the location to be Italy, so that first part we can kind of dynamically build it up, but we can also use the two steps below right here where we can allocate and populate the dictionary all at once, so those next two lines those are two ways to do the identical same bit of code to create a dictionary that has an age of 42 and a location of Italy.
When we want data out of the dictionary, instead of using a number, we just use a key in this case we would like that location information back so we just say quote location, here we are using a string, it could be a number, it could be something much more complicated even and that will actually give us the location back, that value is Italy.
If you are not sure if a key is in the dictionary, and you try to access it you will get a key error exception.
So if we want to get the age value out of info and it's not guaranteed that age is actually in there, you ask this somewhat non obvious but pretty cool way of saying if age in info and that will return true only if there is some value for age inside of info.
|
|
show
|
6:30 |
Ok, we saw that the csv dict reader was really the way to go from the text file into memory for comma separated data.
Now, there are still some things that are not quite perfect, like imagine I was given this data, I would somehow want to go through it, if you asked me what is the average number of beds, well, I still have to know every single time to say quote beds, and if I say bed instead of beds, crash, that's not great.
It's not discoverable what all the rows are, and the rows all come back as text even though beds represent a number, it's just a string that came out of the text file so if I look at the type here, I'm going to say type of that, it should come back as str, guess what, type is str because it's a string, we need to convert it to its either an integer or for things like longitude, latitude to floats, so let's bundle up that sort of implicit knowledge of the format here, and we can put that into a separate class, so I'll create a file and I'll call this just data types, probably a better descriptive name would be great there but let's define a class and we'll call this, let's just call it a purchase, right, I was going to call it real estate purchase but within the context of our real estate app hopefully the real estate component is clear.
Now, I am going to show you something that you've not seen in this class about classes, what we have done so far is we have defined a __init__() and we are going to do that again here, let me just really quickly sketch out the pieces that are going to go in here, you don't need to see me type this, ok, so now I've sketched out a fairly complicated initialization method but you know, it's just a matter of what pieces of data you want to store in here and where do we go.
Now, we could go back to program and make it this method, this load files job to allocate a bunch of those purchase objects do the conversion type here and stash the data in here by passing them to the initializer.
But, this class already knows the structure and it knows the pieces that it expects, so let's make this class responsible for creating other ones, ok.
So we can come over here and define the method, let's just call this create_from_dict(), something like this, and we'll call this a lookup, or something like that, ok, we will come down here we will say something this, return purchase and we are going to allocate one of these things, the first thing that goes in here is going to be the street so we would say lookup of street like so, again just let me type this out and I'll skip ahead, ok, so I wrote this method, now there is a few more things to do here, we want to convert the number of beds into an integer, so we need to parse this, similarly we want to convert the number of baths the square footage, I believe that is an integer and a price, let's make that a float, just to be safe.
I suppose we could do that here as well.
Ok, so this will create these purchase objects and from then on, I can say things like .
(dot) and here is all my list of items, and the price is always going to be a float, and that will be a strong structure that we can work with in our app.
But, let's try to use this method this is not going to be so cool, so come over here to our program, let's go ahead and make a purchases list here, so we are just going to store all the purchases basically one for each row, let's say purchases.append() purchase, let's just say p, keep it short to fit on the screen, so we'll say p=purchase, we need to import this, if I try this, it's- let's give it the row here, row is a dictionary, if we try to run this, it's not going to go so well, it says missing one position lookup, the other thing that it wanted here is self, we have to order these this way, I have to do this weird thing, I have to say p=purchase and I got it created and then I have to say p.
well, that almost works, notice this line little error went away, the line above now says oh, you are missing these 12 parameters, yayks.
So, let's go back through this.
This method has nothing to do with the individual objects, it deals with the class, right, this just is associated with this class but it doesn't have anything to do with the self parameter, it could be a static method, it could be one that we can just call right on the type and it maybe will allocate instances, of the purchase.
Now PyCharm is actually telling us to head as well like see the little squiggles there it says you know what, you can make this method static and the way we do that is we add what is called a decorator, a decorator starts with the @ symbol, and they have a cool color in PyCharm, so we can say @staticmethod there is other things you can say, you can say @property, and define the method that would be a property instead of a method and so on, what we are going to use is static method and that lets us go back here and instead of requiring an instance we say like this, purchase.create_from_lookup and it only takes the dictionary here, so let's go and append these, and let's top the print out here, I'll just delete those, when we are done, I'll just print let's just print the first entry, our first class, all right, run this- oops, I mean purchases, so we go this first one now that's not super helpful, let's use this internal dictionary just to see what the data is like really quick, here we go, sale date, such and such, state number of baths, perfect, and notice, the type, residential has a quote but the number of baths is an actual number, it's the 1, not quote '1', which means we can do math on it similarly for things like the longitude, latitude, number of bedrooms and so on.
So this is great, now we are just going to return our purchases, what will be in this list, this will be our specifically typed purchase class that has to do, that knows specifically about the data containing here and we can leverage that to help us do math like ask what is the average number of bathrooms and so on.
|
|
show
|
5:44 |
Ok so we've gotten our data out of the comma separated value file, converted it into our purchase class and we've got it a list of those and we are passing it to the query data method, now in this method we are supposed to answer 4 basic questions, what is the most expensive house, what is the least expensive house, what is the average price of a house and what is the average price of a two bedroom house.
So some of these are really easy to answer, some of them take a little bit more work.
So if we say data '.' PyCharm knows it's been able to detect that we are sending a list and it's retracted back from the load data back to the main method and down into here and says I know I know that you are sending a list but it's not quite sure what type is on the inside, notice when I go and pull it out I get nothing, we can actually give PyCharm a type hint, if we want and it doesn't affect the way it runs but will give us some help and we can say this is a list of purchase objects, now if I say data '.' again we get our list stuff but if I go to individual item you can see we have our baths, beds and so on.
So that might make things a little bit easier, ok, so the question is what is the most expensive house, what is the least expensive house, now if I could tell you that data so we can say if data was sorted by price, it would be really easy to get the most expensive and least expensive purchase so we could say high_purchase = and this is a list, remember, if it was sorted form low to high, high purchase would be the last one, so we can just say minus 1, this is a beautiful thing about Python, and let me move this over like so, and the low purchase would just be data zero.
Again, we are assuming there is some data this would crash if data is empty, but how do we sort it, well, that's super easy, we could go over here and we could say data there is two ways we can do this, we can do one which will modify the data list and one which would return a copy that is sorted but it doesn't affect data itself.
Let's assume we just want to sort this one in place so then we can say sort notice, we can specify a key, so we can say this, we can say the key I want you to sort by is something, now let me write something that might seem a little weird here to you, say def get_price().
And we'll give it a purchase, so if we are given a purchase we could say return p.price.
So if I go down here and say get_price and I don't call it but I just say the method name, when it's time to sort, when it's time to get the key to sort on, the list here will actually call this function on each element, that function will return the price of whatever the list passes to it and then it will do a sort on that new subset of data that projection of data.
So let's see if this is going to work, let's just do a print of high price.price, and down here we'll do a print, we'll do a better print in a moment, but say low price.price.
Remember, this should be like 800,000ish, this should be 1,500.
Let's find out.
Well, it looks like my cool little trick to get the type hints here isn't working so well, let's take that away again.
Excellent so you can see we've got the high priced house, and the low priced house and all we had to do is sort our list.
However, this is not the coolest thing to write this function here so that we can pass it off to the key method, wouldn't it be better if we could just put a little bit of code here that talks about how to get the price from a purchase, well, luckily, Python can do that, we can go over here and say key, instead of giving an existing method that we have written either a class method or just a regular method, we can just say I am going to create something called a lambda and a lambda is like a little inline method, and so we'll say given a purchase I would like to return the price, that's it, and we go over here and we can comment this out, we don't need that anymore, and let's try to run it and see if we still get the same information.
Perfect, so anytime you have to pass a function around and by the way how cool is that you can pass functions around, anytime you have to pass a function around, if the function is sufficiently small we can just say here is our little small function an inline function, lambda, this is the argument, there can be mini arguments like p, u, x, whatever, right, given this argument which is a purchase that's because the data list contains purchases given this we are going to go and return some value that will then be used to sort, so we are going to return a price, and there is kind of an implicit return here, right, you don't say return, you just say there is kind of an expression, this piece of data goes to that value.
So let's just like make this output a little bit nicer here, we'll say print the most expensive house is so here we've cleaned it up, the most expensive house is 884,000 dollars and the least expensive house is 1,551 maybe we want to say little more information about it, like with some number of bedrooms and some number of baths, because we are using our class this is super easy we just say beds baths, 4 beds, 3 baths, we can do something very similar, for the least expensive.
What do you guys think, is that a good deal, 1,500 dollars for a 3 bedroom 3 bath house, I have no idea what happened there, let's take a moment and look at this core concept of lambda expressions and then we'll go on to answer the more interesting question that involve little bit more math.
|
|
show
|
1:28 |
Let's take a moment and talk about a core concept called lambdas.
In Python, functions and methods are first class citizens.
That means they have their own type, they can be passed as arguments, and they can be treated as like a class or other elements in the language, and so that makes it very natural to write a method like the one we have here, find the significant numbers, to find_sig_num, so if it's on the slide, you pass it set of numbers, and then some kind of method and you can use this method to test is this number in this set or is it out of the set.
So this is an example of using functions as a first class citizen and it turns out often when you write code like this the little bit of test, the predicate you need to write and pass along is really specific to this one purpose where you are using it, it's usually very simple and it often does not make sense to make its own dedicated method, you just want to kind of say here is the little test, and it's lambdas that let us do this, so you can see on the second to last line we write lamda x: x % 2 ==1 so the test we are passing off to this find significant numbers, in this one example is find me all the odd numbers.
So you can see we have the Fibonacci sequence, the first bit of them, we are passing it off to this fine significant numbers and we are only looking at the odd Fibonacci numbers, and that's exactly what comes back, you'll find lambdas to be a key part of the language, and the more you dig into Python, the more you see that they are up here and they are helpful all over the place.
|
|
show
|
3:30 |
All right, now you've seen all about lambdas, and you've seen how easy the are to write to sort these data, and that allowed us to solve two problems really easily, if we had a sorted set of purchases, sorted form low to high by price, we can say well the most expensive one is the last one, and the least expensive one is the first one.
But let's ask some more interesting questions, what is the average house price, it turns out that there is a statistics module here, and on there we have a mean method so we'd like to use this but we need to import that at the top, thank you PyCharm, and we have to give it- well what does it say- data, that's kind of cool, it even has the same name so of course we should give it data, let's see how that works.
Hm, can't convert type purchase to numerator denominator.
Hm, ok so it looks like we need to give it an iterable set of numbers, not an iterable set of purchases which we just made up, so how do we get that, let's just go like this, here we'll say prices, and we'll create that to be a list and we'll say for purchase, let's say pur in data: prices this is really painful, right, so we are going to go prices and we'll say append and what do we want to append, we want to append the purchase.price.
And then we can get the average of prices so we'll say ave_price = this and let's just print, the average home price is this is in USD and let's put a comma separator, and a format, ave_price, let's try that.
Awesome, the average price is some number around 234,000 and let's go and round that off, there we go, 234,144 and actually that is probably truncating, but close enough for what we are after here, this is ok but this is not so nice, right, we created a list and we did a loop to grab the data and maybe we only want to, maybe there is certain times we want to do a test, so it turns out that there is actually a better way to do this, let's go and answer other questions while using this looping construct and then we can do something better Ha!, it looks like I left a pass here, no harm no fell, so it's going to look very similar to this, so here we are going to say give me the prices, and I want to go to there and grab each purchase and append the price, but in this case, we want only the case where it's a purchase of a two bedroom home so we'll say if pur.beds==2: then we'll do this, and this will be the average price of 2 bedroom, there we go, so let's run this again, awesome, so the average price of all homes is 234,000 dollars but the average price of a 2 bedroom home is a 165,000 dollars.
Maybe we also want to answer the questions like well, how many baths does it have on average, and things like that, hm, so this is going to be a little bit harder.
Well, it turns out this is like programming for all the programming languages almost all of them anyway, C, C++, Java, it does loop around here, write this procedural code, we can actually improve upon this in 2 ways, we can improve by using what are called list comprehensions, which let us write this much more concisely, and then we can actually improve upon them using something called generator expressions, we are going to get to both of those, first, let's look at the challenge looking around and trying to run this code on both Python 3 and Python 2.
|
|
show
|
4:51 |
This code is working perfectly in Python 3, as we saw.
Let's see how it works in Python 2.
Now, there is a minor problem we are going to run into with our file IO here so we'll solve that really quickly, but let's go add it the configuration and say hey, let's try to run this under Python 2.7.10.
No module named statistics.
Well, we need that, right?, we are using statistics.mean it turns out, statistics was introduced later on in Python, I think it was 3.4 but somewhere around that time frame, and it's definitely not in 2.7, so what do we do, do we just say well, we've got to take the mean so forget it, you can't write this code, or another possibility would just be to say well, we can't use the statistics module we are going to have to just write our entire own statistics mechanism.
I suppose there is some answer out on PyPi but as a way of demonstration, let me show you how we can fix this.
so that's the line where this is crashing, and if you don't know for sure, you click here it says line 3 click and it will navigate you right there, yeah, it looks like that's the line that's crashing, and that's because there is no statistics module.
So what we can do, we are going to dig into this error handling later, just roll with it here ok, what we are going to do is we are going to say we are going to try to run this code, and if it fails to import the statistics module, we are going to fall back to something else, so let's say try, this is one of the, this is basically the key concept of the next application, so don't worry too much about the details, what we'll say is we are going to try to run this code and if there is some kind of error I'd rather run error code instead.
So either we are going to successfully import this or we are going to have to do something else.
So let's add another file here, I'll call this statistics stand in for Python 2, ok, name it whatever you want, it doesn't matter, let me just capture the name really quick here, we are going to come over here, we are going to say if there is an error, we are going to instead import that now this is not going to work so well, because we are going to still try to use this name below and it's this is going to be here, right, so what we can do is we can say as this, so basically here is the real module name but forget it rename it to that and as long as it has a mean method, that takes data, and let's just say return 0 for a minute, things will run.
So remember, we were running on Python 2.7 first of all, let's go run it back on Python 3, works, right, here is our mean method, now this is going to return 0, we are going to put real numbers there in a minute, but let's just see that it doesn't crash in the same place, there is going to be another crash we'll deal with hold on.
Look, no errors, there is this problem with encoding and it turns out that this do like so, turns out that that didn't work in Python 2.7 exactly in that manner, so we'll drop it, but now watch this, it used our method when it wasn't available from the system, and that is in the case that it happens to be 2.7, it could actually be Python 3 but Python 3.1 which preceded the sort of creation of that module.
So I guess it's up to us to go write some kind of a mean method, so we'll have total=0 we'll have count=0, now we could do len of data but if it's a generator it's better if we just do it this way, for x in data:, count +=1, total+=x and now we want to return total divided by count, unless that's 0 then that's king of be a problem so then we could say max of 1 and count so that way a count of 0 we won't get it divided by 0 error but otherwise we'll use max.
Let's run it and see if we get some decent numbers here, look at that, in Python 2 we have exactly the same answers, right, that's Python 2, go back to Python 3, notice 234, 165, 234, 165, same numbers, so how do we do that, besides ignoring the encoding, we said look, we'll try to use statistics if for whatever reason it's not available, then we are going to fall back and use our own probably less performant, maybe bug written implementation, but at least we have something to fall back to and we named it this, but we for the purposes of this file, we sort of replaced the same name here as statistics.
There is a lot to know about writing code that runs on Python 3 and Python 2, here is just one example hopefully you'll find this useful.
|
|
show
|
2:07 |
Let's take a moment and look at this core concept of one mechanism for supporting both Python 2 and Python 3.
You saw in our previous example that we wanted to use the statistics module to compute the mean, now I realize there are many ways to compute the mean, we don't need to fall back on this, it's a pretty simple mechanism but the idea is there is some module or some feature in a later version of Python that wasn't supported in a prior one.
So this is not even about Python 2 versus Python 3, this is like Python 3.4 versus previous versions, ok, so the statistics module is imported in Python 3.4.3 if we run this code on anything earlier than that, it's going to fail, it's going to say there is no module statistics, right, if we have it it's a super easy way to compute the mean of a set of numbers, you can see below, we've got a bunch of numbers, static.mean numbers boom, out comes the average.
But it fails on a lot of the versions of Python.
So what do we do?
One way we can solve this problem is we can use a try and just an empty except, there are possible problems you can expose yourself to here, but in this simple case it definitely works well.
So, we can say it look, let's try to import the statistics module, if it doesn't work it's going to throw an exception and we could actually catch the right type of exception, we'll get into exceptions later in the next app, but it's easy enough for us to go and write the statistics to stand_in.py and we define a mean method and it takes the same parameters as the real one and the implementation you saw in the previous example write it, it was quite easy.
So the way we solve our problem and we get sort of code that runs on all the versions of Python is we try to import the statistics on new versions that just works and uses the best new module for that if it doesn't work it's going to fail fall into the except block and we are going to use this specific type of import say we are going to import statistics to stand_in and we are going to actually give it the name statistics for this particular file and since it's a mean method, the statistics.mean it works exactly as we would expect.
Boom, now our code works on all the versions of Python, just like that.
|
|
show
|
5:25 |
All right, I said there is a better way than using these procedural loops to answer these questions.
Now, check this out, let's change this here, let's write some code that maybe is a little surprising to you unless you have seen it, so we'll say again prices, and what we want to generate is a list of numbers, we wrote this code which generated a new list, it's equivalent to saying list, like so and then we iteratively looped over, and down here you can see we even added a test in our loop now there is a concept that lets us write a more declarative style of code where the implementation actually handles the looping and all that for us, so we can come down here and write what looks like brackets, we are going to define a list, but we are going to put something different in there we are put in an expression, first line is always going to be the projection or items to create, something like this, so what do we want, we want the purchase.price, here, now it's a little bit backwards because you have to imagine, all right, there is a variable p, that I am about to talk about, and it's going to be a purchase so I want the price and then the next thing down here is going to be the set to process, for lack of a better word, so here we'll say for p in data:, so what we want is we want to go through all the data, grab out every one of those we are going to call it this p and we are going to pull out the price, now we could pull things like a tuple, I could say p.beds like that, and even like p.state, no that's not going to be interesting, it's all the same state, let's say city, maybe we'll get something there and I could just print prices, I think in this case I need some parenthesis, yes, one of the rare cases where tuples need parenthesis, and let's just say return so it doesn't actually crash.
And notice, we looped through here and here is the price, here is the number of beds, here is the city, price, beds, price, beds city, so this is like a projection, kind of like if you are familiar with SQL, queries and those kinds of things, that's what this is but we just want one item, which is just the prices.
So here you can see all the prices.
If you want to know what type that is it's just a list, class list, that's its values.
So think about this for a minute, before what we did we had to allocate the price, loop over each item, and then do some processing, granted this processing is very simple, it's just added to the list, so it's a little harder to see the value here but here we can just declare expression that will build the list, and it uses the same mechanism as the list here, let's take the same idea and apply it the more interesting scenario here, this, so here we have the same thing, and the last part that I hadn't shown you before is the test or condition, if you are familiar with data bases this would be like the where clause, so we could say if p.beds, so now if I run this, we should get the same answer below, and we do exactly as we had before, but we don't need these tests, what we are really after is not just the average price of a two bedroom home, we also want to know the average number of bathrooms and maybe the average square footage, so this can get a little more interest in here, so we'll say the average price of a two bedroom home let me tighten this a little bit, so we'll say the average two bedroom home is some price with a certain number of baths and a certain square footage, now I put the 0 here so just this will run, here you can see we are still getting the same answers.
Now, suppose I want to know this, well, previously with this looping mechanism, I would have to come up with another list of let's say baths=something like this, and down here I would have to also say bath.append pur.baths, and so on.
With the conciseness of these lists here, we could do something like this, we could just say two bed homes like this, and let's get back here like so, and so what we'll get is a list of two bedroom homes and then down here we can say the average price is going to be the mean, now if we try to give it two bedroom homes, remember, this was crashing, so as I don't know what to do with, purchases, but we can go into this set and we can just write in line here and say what I would like is p.price for p in this.
And now we are back to our average, so I said look, write in this little bit of code here, we are just going to do this little projection, now, let's get the average baths.
So here I can say baths, and the average square feet.
All right, now let's see what their answer is, maybe we could round this off here, so we'll just use the round function here, so the average two bedroom home in Sacramento that was for sale were actually sold in 2008 was sold for 165,000 dollars, it had 1.4 baths and the square footage of 957.2 square feet.
How cool is that?
All right, so this concept here is called a list comprehension, and it's really cool, it has some performance benefits, and some performance drawbacks.
You will see that there is something extremely similar to it, called a generator expression, but first let's look at the concept of list comprehensions.
|
|
show
|
3:32 |
One really powerful language construct in Python is this thing called a list comprehension that lets us take procedural code and condense it down to declarative code.
Here we have a bunch of customers, and we'd like to know just the customers who purchased today.
And we don't want all the information about the customers, we just want a list of strings that are their names.
So paying_usernames is going to be the set of users who have purchased something today.
Now, this code works fine, and this is how you do this in many languages, we create lists, we loop over all the items, within out loop we do a test, and if you pass the test, did you purchase today, yeah, ok, cool, then we are going to take your name, we are going to stick in this list, there is a couple of drawbacks, one is this is basically impossible to condense down to something you can pass as an argument or simply assign a variable to, the other one is it's just kind of verbose and you have to say all the steps, telling the runtime, the implementation exactly what to do.
We can use something called a list comprehension to simplify this and condense it down to what is equivalent to a single line of Python, so then we can more easily assign it to variables, pass it to functions, things like that.
So we can write the exact same code like this, we can say paying_username= [ ] just like we are going to define a list, what goes in there is not static data, in fact we put this expression it has three parts, the first part is the projection, remember, we are given a set of users and we want just the names, we kind of got to think ahead a little bit, this is a little bit annoying about it but you have to imagine well if I am going to name the customers what variable will I use to name each and everyone of them as if this were a loop, we could say if that was going to be you then I would like to name, so u.name goes into projection.
Then you write the source, and it always goes like for something in some set, so here we have for u in get_active_customers().
And then finally the test or filter, so we say if and then we just put our test here our test was if the last purchase was today, so we say if you.last_purchase==today, done.
And the way I've written it it's not that much shorter on the screen but because it's a single expression you can do things like pass it the functions as single arguments, or sort of combine them and just have a lot more flexibility.
You also see as we move onto the next evolution of this concept, the generator expressions, then you actually get even more benefit, there is actually performance benefits or consequences depending how you use it.
|
|
show
|
4:41 |
So let's look at the code again that we are using our list comprehension, and you can see I've changed it a little bit.
Down here on line 107 I've written a method called announce, and now what it does is if you pass an item to announce, it simply returns that item back but along the way it does a print statement to let you know hey I am processing this particular item, and you can give it like kind of a little descriptor, so over here when we are going to the two bedroom homes, each time to the test we are going to go through all the homes that's going to say hey, tell me what home you are processing, and continue with the regular test, yeah?
And then later on, I decide hey, I only want to process the first five two bedroom homes, now the way this processing works is almost identical to the challenges that we ran into in application number 8, when we were doing text searches across gigabytes of text files.
Remember, for small quantities of data this mechanism where we had functions that recursively searched directories and files and each time it hit a file it would create a list put all the matches in there and return it up and build that up into an entire set of search results for the whole directory structure, and then at the end, pass that list back with all the matches.
They work fine for megabytes of text files, for gigabytes of text files, the memory went crazy, it took forever, there were all kinds of problems and you'll see that this is identical in it sort of processing characteristics, and that should be no surprise, because this list comprehension is very much like create a list, loop over it, fill it all the way up and then here is the answer, remember, our solution was to use generator methods, the yield keyword and it would sort of on demand as the client would pull, doing loop, a forin loop or some kind of processing of the end result, it would one by one pull those results back.
So we can use this thing called a generator expression to move from the list colon wait style to this yield return co-routine style but in the list comprehension and what we call a generator expression style processing where it's just not a method with the yield keyword but it's just an expression on a line.
Now the way we do this is remarkably easy but before I do it let me show you how the processing works here, so if I run this, you'll see even though we are only processing 5 homes, we are going through look at this, every single home we basically process the entire data stream and then, we go back and oh, actually only of the first five two bedroom homes, what would be much better is to just stop after we get to the first five two bedroom homes, so what do we have to do to change this?
Well, for list you use square brackets, for what we call generator expressions that have this yield behavior, this co-routine behavior, we just use parenthesis, that's it, we do nothing else.
Now, this you cannot index into those things any more, or slice them which basically uses indexing so I am going to need to create a little list so it's not a perfect analogy how we can flow these strings through, but we are going to use this as sort of the step to say hey we are going to stop after we get just five of these, and we don't really need this anymore, do we, because that's actually doing the same thing, so remember, previously, this part right here went through the entire data structure, let's try again.
Ok, so we are still pointing in the five items down here, but as we go through, we can see there is a lot fewer, we just go until we have enough two bedroom homes to run through here, and then we stop, we don't have to process the whole data set, and down here we can get the average and stuff, the averages of course are different because we are not averaging across all two bedroom homes just the first five, really the way we have things structured this is the cheapest five two bedroom homes.
In fact, there is a few more places where we can use generator expressions in our app.
So we were using the generator expression up here, but now we are creating a list, filling it all the way up and then passing that to the mean.
We have no reason to do that, we can use parenthesis instead of square brackets, and we'll only have one of these little projection pieces in memory at a time for each step, make our mean calculation a little more efficient.
Try again, you see we get exactly the same answer, different performance characteristics.
So you see how we can use list comprehensions and later generator expressions to replace procedural operations with more declarative ones, and get the performance benefits of generator methods without actually writing methods at all.
|
|
show
|
1:34 |
Let's quickly talk about this cool concept of generator expressions.
Generator expressions are sort of the next evolution from list comprehensions, and list comprehensions of course take the idea of writing procedural code, declaring a list, and writing some kind of loop within that loop doing a filter maybe somehow transforming the data and putting into the list.
Generator expressions do the exact same thing but instead of computing the entire thing into a list in memory it works just like the coroutines that you get when you use the yield keyword, you don't actually do the computation, you don't pull the item back from the get active customers in this example, you don't do the test until you start to pull items out of it, so if you say for n in paying usernames, and you only pull three of those out, maybe within the first five of a million active customers there are three ones who are active today well you are only going to process five customers instead of a million.
That can have a lot of really powerful positive effects for your application performance.
Now to be fair, there are certain situations where this doesn't work as well, you can't index into generators for example, or you can for a list comprehension, every time you loop over paying usernames, you recompute this information, so if you are going to use the item over and over and over, maybe it makes sense to use a list comprehension, often though with these little expressions you create them, you blast over them and you are done, in that case, generator expressions probably are the way to go.
|
|
show
|
2:34 |
For our final concept in this app, let's look at building data pipelines out of generators.
Now, these generators could be created from generator methods or generator expressions.
So let's see how we might use this to answer some questions about the housing market.
Our first task is let's find all the houses that match my requirements, and let's say personally I am looking for at least three bedrooms, New York city, any city will do for a first pass and so on.
Next, maybe we want to find all the houses that are for sale that I might in theory buy, so that houses that come out on the first set, maybe those are some are for sale some aren't.
Here we want the ones that are for sale, and then, I'd like to take the ones that are for sale and then look around me.
Ok, so I am going to start out with this the results of task one and filter them further down and process them more in task two, and then maybe I'll process them still further in task three, so how do we do that?
Here we can get all the real estate transactions and use this data to start answering these questions, so I've got this is interesting method and that was kind of like check for three bedrooms at least and so on, so some sort of criteria that I am looking for.
Now there is no guarantee that these transactions represent houses nearby, or that they are even for sale.
But then I can take the results of this generator, interesting tx, and pass them to another generator, in this generator we look for the houses that are for sale, we don't care about all the houses that are for sale, only the ones that I would be interested in.
And because they are both generators, they do this in a progressive as you consume the data they pull the data, they don't process it all at once, they don't load it in memory all at once, all those sorts of things so we saw in application eight.
Now, we can ask further questions, we can say I would like the ones that are potentially saleable, which when I pull from that set it of course does its test but to get those items it pulls back from all the transactions so there is kind of this pipeline or chain depending on what which direction you think of it from, and we'll loop through that using yet another generator and we'll say for all the ones that are for sale that are interesting to me, I would like to reduce that to ones just nearby, maybe I don't want to move cities I just want to look at hey let's find all the houses I'd be interested in Portland Oregon, or wherever.
So, we can use these generators to build pipelines, you can take one generator and pass it to the next and then take that generator and pass it to the next and it creates this really cool pipeline of processing that's extremely efficient and it doesn't even require us to write methods.
|
|
|
30:59 |
|
show
|
2:05 |
Application number 10, let's build a movie search app.
Now it turns out we are going to put this movie search app into a particularly unreliable environment, so we are going to have to do a lot of work to make sure our movie search app is durable and it doesn't crash or behave too weirdly.
Let's see how that's going to go.
We are going to build an app that looks like this, standard header.
We are going to enter some text and this text is going to be the title of a movie.
So, here we might say Top Gun, and we'll try to go out to the internet and find information about movies with Top Gun in their title, but you can see in this case it said oh sorry, the network happens to be down so no searching right now.
But our app doesn't crash or give weird errors it just says hey, the network is down, check your wi-fi or something like this.
Then later we try again, search for Top Gun and boom, we get real live movie search results off the internet.
So it might sound like this is about searching movies, but really what we are going to focus on is error handling, writing durable reliable applications.
So we are going to focus on error handling, the primary way to do this in Python is with exceptions, and try/except blocks, you'll see that we can have multiple sort of error handling clauses in any particular situations, so there might be a host of reasons that our movie app doesn't work, maybe the DNS server is down, maybe we entered a bad title, maybe the wi-fi is down, maybe the other server actually gave a 500, maybe malformed data came back, so we can actually catch different errors and give different messages back.
We might want to tell the users something different if the data that came back was malformed, rather than if the network was not connected.
Right, so we'll use multiple what are called except blocks to do this and that will allow us to differentiate errors based on the type of the error, and ultimately, we are going to talk about writing reliable code and you also see how to raise errors and exceptions not just catch them because we are going to throw some to sort of make our app particularly unreliable.
|
|
show
|
3:40 |
Let's begin this section, this app by talking about where we're going to get our movie data from.
We're going to use a movie service at movieservice.talkpython.fm.
Now this is just a simple service with a subset of the movies, there's only a couple of thousand movies here, but these are the movies that you can go and search, and you'll see that there's a couple of things we can do with this basic api, we can search for movies by keywords, so if I click on this, it will run a little example, in Firefox, it happens to parse and give this pretty view but if we hit raw, you can see what comes back from the server is actually this json we get a couple of things, we get the keyword sent back just to say what the server thought we searched for and the hits, the movies that matched.
So here we can see we've got Blade Runner when we searched for run, we've got Running With Scissors and a couple others, we've got Kite Runner, things like that.
So we can go and search for anything, put the keyword up here, we could search for particle, it comes back with this movie called Particle Fever this is an absolutely awesome movie about Large Hadron Collider and search for the Higgs Boson, one that I really like is one called Capital C, this is about crowd funding and kickstarters and stuff and this is really cool and interesting movie; so let's see what we can find out about that, so if we come over here and we search for capital, you'll see that we get a couple of things, we get Super Capitalists, we get Capital C, that's the one I was talking about, Capitalism a Love Story, okay, we have things like their IMDB code, so we can pull them up on imdb.com and things like that if we wanted.
We have keywords, genres, the year, rating and so on.
Okay, so we're going to use this movie service to go search for movies by keyword we could also search by directory, or we could just pull them up by IMDB, so if I click here, you can see if this is searching by Cameron, so James Cameron or Cameron Crowe, anyone who matches that, here's James Cameron, The Abyss, Terminator, Judgment Day, Titanic, somewhere in there is Avatar, things like that.
So we can search by director or just by IMDB number so for example this one pulls up the Abyss by James Cameron where the IMDB code is right there.
Okay, so this works pretty well, I wouldn't use this for a real app and just because there's not that much data behind it, it's just built for this course, for this specific purpose.
But there's enough there that you can do some interesting queries; if you are looking for a real API that's very similar to this it will work in a similar way to what we are doing, is you can check out the OMDB API, the Open Movie Database.
So, this actually used to be free, and you would be able to just go and use their API, the API is very similar, you could come down and you could click on say Capital and it will do a request in a really similar way, and it will give you things like if we put Capital C we could probably get that exact movie back from it, yeah, there you go.
So, we could do a similar search here but you have to pay for this, the minimum is like a dollar a month or something, it's not crazy but it turned out that this free thing was kind of a victim of its own success, it did 44 terabytes of traffic in just one month and that turns out to be super expensive.
I ended up making this one just so you have a nice stable simple service to work with, but if you want to build a real app, I'd recommend checking out OMDB API.
|
|
show
|
7:06 |
Now we know where our data is coming from let's write some code and get started on this app.
We're going to do it in two steps, so let's take this url here this movie service.talkpython.fm/api/search/ the name there and let's go over here and we're just going to write some sort of play around code in the beginning and then we'll structure this into a proper program or proper app in a minute.
So we're going to go and make a request against this and let's make it really clear that there is a search term happening here so we'll say format search, search equals this, I'll search for a capital and we're going to need to make a request against this service, well, we've already seen one of the best ways to do that is to use the request package, so import request; now I don't have request installed in the virtual environment here so we'll go ahead and install that, you might be using it your system wide Python and have it left over from other examples or maybe using the same virtual environment but here I'm an going to install it separately; we're also going to be using named tuples, so I'm going to import collections while we're at it, so let's start by going and doing a request against this, so we'll say the response is request.get url, and then let's just print out the status code and go ahead and run this, see what we get.
Status code 200, that's good, so we could actually print out the text that we got back that looks like the javascripty json text that we're looking for, we could use the json module and parse that, but it turns out that if we have let's say I have a variable here it turns out that request is very commonly used to talk to these javascript apis and it can automatically turn this into the Python dictionaries that we're going to work with, so now I could print out this let's say the type of movie data and maybe data, we'll see what we get back so if we run this, we get a dictionary, not the strings, but an actual dictionary which we can use to start working with this data.
So notice we have our keyword capital in our hits which is an array of these movies, so we're already on a good path here, we probably should do some kind of check to make sure everything is ok and we could say something like if response.status code is not 200, do something, but request has a nice little thing here we can say response.raise for status so this is going to cause an error an exception if something went wrong basically if it's not a successful status code, all right and we'll talk about how to handle these types of situations in a little bit but for now, let's just keep playing around with this.
So we've got our movie data, and we actually care about our movies and that's going to be going to the movie data and we want to get out the hits, so let's print out our movies here and see what we get.
Now this time it shouldn't come back as a dictionary, notice the square bracket, this should come back as an array, a list; so here's a list, and then these are all of the movies, now the next thing we want to do is convert them just from these flat dictionaries into something that's more useful for application, we've already seen that named tuples are really powerful and let us work with data better, we could go and create classes like we did in the wizard app, but I think probably named tuples are good enough for what we're trying to do here, so let's go and set that up.
Let's call this a movie result and we're going to set that to be a collection.named tuple, and we need the name right here to be exactly the same so movie results, and then the next thing that goes in here is actually a list of all of the fields, and you can see we have rating we have duration, we have title, we have imdb code and it needs to match up, well, it should match up exactly to this, to make it as easy as possible, we could of course transform it and if we don't want imdb code we just want to say imdb or just code or something like that, we could change it but we have to account for that later; so our plan is to just use the same field names or attribute names here, so I've already copied those over, so you don't have to watch me type them in, so we have this movie result and let's go instead of printing these out, let's actually go and loop over all of this data that we got back and build up a list of these movie results, so let's rename this to movie list or something like that and then we'll define our movies, this is the thing we really are after we really want to work with, we're going to add a bunch of these movie results converted from the data we got from the web service.
So we'll say for md in a movie list, all right and then we want to come down here and we're going to create one of the movie results and I'm going to do this in three different ways, I'm going to start out in the most sort of verbose least Pythonic way and we're going to improve it in the next video in a couple of different ways.
So we're going to say m is a movie result, then we need to set a bunch of stuff, we need to set the imdb code is equal to md.get, imdb code we're going to come over here and we're going to set the title to be the title and I'm just going to knock the rest of these out.
There we go, that was a lot of typing so we've got all of these here and some of these are numbers so we might want to be a little careful we could say things like the score if you don't have it in here in this dictionary get instead of giving us none you could give us zero, same maybe for the year or the rating, things like that but this is not entirely necessary I believe all of them have a year and rating.
Okay, so now we're just going to say movies.append, and give it this m, so now we could do things like this we could just print out something about the movies, so we can print them out and something like this we could say, we could just put the year and the title we could also save print found some number of movies for search something like this the length of the movies and the search string that we sent in, okay, so let's run this one more time and we should go find a couple movies for capital and then print them out here.
Look at that, searching for capital, we get Supercapitalist, Capital C and Capitalism A Love Story, let's go and search for a runner, oh look at that, we found Blade Runner, Kite Runner, Logan's Run, let's make it feel a little more real, I had let the user input something here, so let it come down here, alright, so now we could do blade for Blade Runner or something like that, we got Sling Blade, Dragon Blade, Blade Runner, we come down here and look for a runner, get these again beautiful all right so it looks like this is working, we'll see that we can actually improve upon this in a couple of ways, make this much more concise and Pythonic.
|
|
show
|
4:19 |
Let's return to how we're parsing these movies here.
What we're doing is we're creating this movie list we're looping over all the results, we're allocating one of these, and then we're adding it here, let's address this part first.
So it turns out that this keyword value keyword value thing that we're doing actually has a shortcut, a simplified version, when you're working with dictionaries as the source, and this md is a dictionary, we'll be able to condense this down assuming that the keywords here and the keys in the dictionary that it's coming from actually line up exactly, if that's the case, you can do what I’m bout to show you.
So let me save this, let me make a copy of this and we'll try another one, so quick diversion so you guys understand what's going on here, suppose I have a method or function and it has some positional parameters, so like x y and z, right, if I'm going to call this method, I would say seven, one and I could even say z = 2, right so I could either call it positionally like x and y or I could call this function using the keyword named parameter thing.
Some methods are extensible, so I could say format = true, age = seven but you can see, PyCharm is saying no, no, this is not going to work; so in order to make this sort of extensible, to say look you can pass other stuff and maybe we can work with it we're going to use this concept that's often called in the variable kwargs, keyword arguments.
Now this name here is just a convention, it could be jerry it could be anything, it doesn't matter, the symbol that we use is this ** here, so if I print out kwargs, like that, if I run this, let's see what we get; you can see kwargs its format is true and age as seven like this extra step that we passed here, under **kwargs, becomes, you can see the curly braces- a dictionary.
So, with that in mind, there's a way to reverse this, so what we're doing here is we're taking keyword arguments and we're turning them into a dictionary, but we can use in a reversed direction we can use this ** to go from a dictionary to keyword arguments, so what we can do down here is we can replace all of this with just going to the dictionary that has the data md and we can say ** md, and what that means basically is exactly what we had written besides little zeros, besides this little part right here where I was saying alternate default values but this is what it means, it means go here and get all the values out and apply them as keyword arguments.
So if I run this, it should run exactly the same, if I search for 'runner' we should see things like Blade Runner and so on.
So this is pretty cool, again I said it's only going to work if the keyword arguments or the arguments expected exactly match up with the keys, if there is one is capital title one is lower case title this isn't going to work.
This is really, really nice.
Now, let's do one more thing.
We can improve it one more time, we can come down here and whenever you want to create a list and then loop over something, and then put some sort of transformed or computed element into the list like this, we can do this with a list comprehension.
So, we saw that before, we can come down here and just say this maybe write like this for md in movie list, and what are we going to put in, we want to put in this movie result thing, so taking the md and converting it to that, there.
Now, this should run more or less identical, the results should be identical, so if we go down here for runner again, we get exactly the same results.
So look at how amazing this is, look how short and clean and concise, we want to go to the movie list and convert it into a list of movie results by assigning all the values that are in the dictionary, compare that to this big puppy, this one is more flexible and maybe you need to use this form like I said if the keys don't match up or things like that, but I think this is really beautiful and we'll use that in this particular app.
|
|
show
|
3:04 |
Now, it's fun to play around and sort of explore the api, but let's build something that's a little bit more formal, a little bit more of a proper app, and instead of having you watch me type it all out, I kind of put it together and we'll just go over the skeleton bit of it, and then we'll actually make it work here next.
So, we have this main method, and it's going to do two things it's going to print out the header and you can imagine that just prints out movie search app, nothing major there, and then it runs this search event loop here, and we're using our dunder main convention; so down here it's just going to say go through this while loop, and long as you don't press x, it's going to let you keep searching, so press x to exit, but if you don't press that, it's going to go do a search, right, we'll go down here, and we're going to run this little search, so we'll go to this thing that I've created called a movie service, and we will call the find movies function, and then this is exactly the same thing we did before, just printing out the year and title, and if you do hit x it gives you this little message like see you.
Let's look at the movie search, really I just moved the stuff that was in a play around function over here and put it into a function we can call, so we've got our movie, result, name tuple, exactly like before, we've got our url, now we're passing in the search text, and we're calling requests, checking for the errors to make sure everything's ok to carry on, converting to json, getting the movie list, doing our cool list comprehension here, with our dictionary unpacking thing there, and then we're returning them.
I guess one more thing that might be fun, let's run this real quick just to see that it basically works the same, except for now it goes in a loop, so we could search for let's say 'cats', we get a bunch of cats, we could search for 'runner' we get runner, and so on but notice, the years are just kind of random, whatever order it comes back from the service, suppose that we would like instead to see the newest movies first, so let's go ahead and upgrade this, we'll same movies.sort and here we're going to sort, we're going to say key = lambda given a movie say m, how are we going to sort it, we could say sort by m.year, and that would show oldest to newest let's try that, 'runner', see Logan's Run for 76, then Blade Runner, now it's sorted, that's great, but not what we were hoping.
We can sort for numerical values, which this is a number luckily, we can sort from numerical values and reverse this by reversing the key, so put a negative there, now if I search 'runner' then we get 2014, 2013, and so on, I installed little programs, every time we restart it, PyCharm is creating another one, let's get rid of these, and we can fix that real quick.
We'll go up here, it's just single instance only, now it's going to restart it, so if I run it again, it will say I'm going to restart this, it's fine.
Great, so it looks like this is working, we have our skeleton of our app going, and now we've moved this finding movies capability over here to this movie service and our program is just using it here.
So it looks like everything's great, but it turns out things on the internet can break, so let's go explore that next.
|
|
show
|
3:17 |
Here we are in our program again, and it looks like everything's fine if I go look for 'runner' we get some results, but remember, this is on the internet, so what happens if say or wi-fi is down, now let's try it again, if I come over here and I search for 'runner' this is the exact same thing, that's bad, look at this, connection error something to do with requests, could not connect to that url, things like this.
Our app is fully crashing and notice, it exited with code 1, usually code zero is good, anything not zero- not good.
All right, so something is going wrong here, and it's because when we go here and we do this line right there, we don't get access, we can't reach the url, I'm not sure which line it is but if it's 12 or 13, but check this out, if you click here, go and find some of your code and you click there it'll actually take you to the line, so it's actually line 12 not even the raise for status like it couldn't get to the point where it got a status, okay?
So how do we convert this from crashing our app to just giving a message to the user like hey that didn't work.
Let's go over here, and the way we're going to do this is we're going to use the exception handling in Python, now what we're going to do is we're going to go find all the code that we want to run and we're going to indent it, and we want to come up here and we're going to put it into what's called a try block, so we want to say try to run all of this code and if it doesn't, if there's some kind of error, we can print 'Yikes, that didn't work'.
Ok let's run this again, and now the network is still off and I search for 'runner' 'Yikes, that didn't work', search for 'boo' it didn't work, turn on the network— and now, if I search for 'runner' hey, it's back, so our app is way more reliable, it's not fully crashing, it's just saying that didn't really work, you want to try again, something like this, right, until we decide to exit.
So that's really cool, however, we're going to notice that there's some other interesting problems here, one of the things that could be wrong is that the network is down, it could be a problem where those service doesn't respond or we got bad data from the service, it could be other things, like for example watch this— so the network is on and if I search for 'runner', it's great, if I search for nothing, 'Yikes, that didn't work', hmm I don't actually know why that didn't work, well, I do, but there's nothing clear, there's nothing obvious about why that didn't work and you have to know a little bit about the service to actually know why that didn't work.
Even though the network is here, that also failed, but we're given exactly the same message back to the user and in fact, if you look over here in PyCharm, it says this is too broad of an exception clause, we're catching everything, in fact, we're even catching like control c trying to break out of the app and watch if I try to hit stop here— it's going to just get this weird like skeletal face and say 'Yikes that didn't work', you can't exit me basically, is what it is saying, I will live forever.
So this is not really the best thing to be doing, we should be looking for both different types of errors and reporting different messages as well as not catching like exit application type of exceptions or messages as well.
|
|
show
|
6:13 |
Now we have our error handling in place, our app can fail a search either because the network is down or for other reasons, and we do catch it, but we have no information about it here, it just says hey, that didn't work, sorry, something like that, so we can add a little bit of extra information here to carry that error over, and we can work with it, so we could say except exception as and then we name a variable, the format is a little bit like context manager, so except exception as x, and so this x is going to have all the details, let's just say it like this, so now we can print this out and we can see what the details are, so if we come over here and turn off the wi-fi, I'll search for network— boom, that didn't work http connection pool, let's see we got some kind of error we'll figure out what that is in a second, but how do you think users are going to feel about this error, they are going to love it, they think this is a great error message that really tells them what's going on— no, they're going to hate it, so we need to do something a little more specific, the first thing I want to look at is just what is the type really quick of that error, the actual type, so the type is a request.exception.connection_error so let's put this back for a second, and go up here to the top and import that; ok, so now we'll be able to over here and we can have multiple except blocks so watch this, we come up here and we create another one we can say request.exception.connection_error as this should be named differently, say ce, something like that so for this one, we could change this and we could say something to the effect of error, your network is down, or host unreachable or something like that.
Now, we could put in details here, but really I feel like that message already carries over enough of the information, so let's just try this again with the network off, I search for J — error, your network is down; notice it didn't run this 'Yikes, that didn't work', the way it works is we do the try block and if there is an error Python will start looking from top to bottom to say the thing that is the error is it one of these types, the very first one that matches it's going to go with that.
So the ce thing, this is also an exception because this is a base type in terms of inheritance, it does match this one, so the first one it finds it stops and it uses that so this means that you need the most specific ones first and these more general ones have to go at the end because it's just going to go top to bottom and stop as soon as it finds one.
Now notice here we're using the x and we're putting the details here but in this one, we're not, like we're not actually using the ce and PyCharm has it grayed, so we can omit this like so.
Now there's another problem, let me put that back just for a second so I can show you the details, ok let's run this, now when the wi-fi was off, we had a particular problem, like if I do this it crashes, but let's turn the wi-fi on.
Now if I search for 'runner', it should work, but if I search for nothing, if I just hit enter, it crashes, Yikes, that didn't work client 404, this was not found, what is going on here?
So if you go look at this, notice the url structure has to have an api search and then the keyword, if the keyword's not there, you just get 404 because hey, I don't know what to do with that.
All right, so that is a problem, we need to somehow take that error which was not a connection error, it was a different type, it was, I think some sort of raise for status thing, yeah http error, more general one.
What we want to do is we want to actually go over to this movie service here and say not even let them try to call this, like why are we doing this, if this data is invalid we should check that, so we can come over here and we can do this, we can say if not search text, so if it's completely like if it's equal to none, we want to test this or, not search text.strip so if they just put like space or new line or something like that; if that's the case, we want to communicate an error back to them as well, we don't even want to let this code run, we just want to say no that failed.
Now we could return false, but that's very tricky to check, how do you indicate empty result versus false, and things like that, so what we should use here, just like request some things like that are, we should use exceptions.
In this case, we want to create an exception, not catch one, so we'll say raise, and probably the best thing to say is value error, something like search text is required, something to that effect, okay.
Now if we run our code, we have our network on, and if we search for 'cats', we get results, if we search for nothing we get Yikes that didn't work value error, so let's go back and add another specific error message for value error, so we go over here, say value error and here we'll say something to the effect of search text is required let's clean this up a little bit, like this.
Excellent, so if there is a value error, we give them this message, if there's a network error, we give them this message, otherwise we'll just say unexpected error, details like that and let's not put the type, there we go, so I feel like this is pretty good, let's give it a try— network is on, we search for 'runner', do we get something, yes, we search for nothing— error, search text is required, network turned off, what do we get— error, your network is down; search for nothing— error, search text is required, turn the network back on, last time— and we're back in business, how about that?
We're able to use the type of error to actually catch it, and handle different cases differently, and we're also able to raise these errors and indicate some kind of problem before it cascades into something non obvious like the http error, like why does that mean that the search text is empty, let's be more proactive and make that really obvious in a way that we can deal with it specifically.
Alright, so that's error handling in Python.
|
|
show
|
1:15 |
Let's close out application ten by looking at this concept of try except blocks.
Here you can see we have a couple of methods that we are going to call, method one, two and three, and they may call other methods, and those methods call other methods, down deep into the Python standard library and maybe in one of them way down there there is a possibility that one throws a connection error.
Kind of like we saw with our request method calls, when the network was down.
Now, it doesn't matter how deep down the chain you are or anything like that, if there is an exception and it is not handled, then it's going to be raised and it will fly up here, it will immediately start making its way, if let's say method two has the exception, so it will start run method one, successfully, method two will run until it hits the exception- done, it will skip method three it goes straight to here.
Now test, is the error a connection error, does it derive from or is it exactly this class, if that is true then we'll be int his exception block.
If that's not true, it will keep going down the list.
Now the order here matters a lot if you say exception at top and then connection error at the bottom because it just stops and the first one it hits their matches, it said you are done for it, it will actually never ever process the connection error if you have a more base error or a more generic general error handling block at the top, super important to go from most specific to least specific as you go through this.
|
|
|
11:52 |
|
show
|
1:27 |
Here you are at the end of the class, you've made it through all ten applications, I hope you really enjoyed your time learning Python and found this to be a really productive and engaging way to learn.
I want you to congratulate yourself, stop for a moment and just think about all the stuff that you have learned because if you have built all these ten apps you've come a long way, there are many things that you've learned and we are going to go back and review them in this short final session.
If you just put it all on the screen at once, and look at what you've learned it's a ton, every single point here is pretty big topic so one of these is file I/O another one is classes, and inheritance, and screen scraping and generators and just all of this stuff you guys have seen in action and hopefully, became at least familiar and comfortable with if not have mastered it yet, but you have a really good foundation to go and become a Python web developer or data scientist using the Python tools or whatever it is that inspires you and you need to go build.
Now before you go, make sure that you've grabbed the source code I am sure that you probably have already got this from going through the course, but go to github.com/mikeckennedy/Python-jumpstart-course-demos with dashes separating it, and you know, start the repository or bookmark it, something like that, I want this to be a resource you can come back to as you learn more throughout your Python career.
|
|
show
|
8:10 |
Let's review what you've learned in each one of these apps.
When I first introduced the apps I told you what you are going to learn but you had never seen it so you know, it probably didn't really resonate the way it will now.
So let's go through and just kind of review really quickly and if you had to reinforce one of these concepts, just jump back to either the concept video from that app or the code, or play around with it some more.
So in app 1, this was our simplest app, it was the Hello World app, remember, the goal was really to test your Python environment get your editor set up, things like that, not really to prove that you can write print hello world, that's silly, right.
So, we sort of introduced PyCharm here, we talked about variables, strings, and accepting user input and this was really sort of getting our feet wet, getting started.
Next app was, what I kind of considered our first real app, that's Guess That Number Game, and remember, the computer randomly select a number between 0 and 100 and then it would tell the user hey, you are too high, too low, as it took in the guesses.
So in order to do that, we had to understand boolean conditions, we had if, else, elif statement, while loops and when we start talking about these concepts like if else statements and while loops we have to talk about the shape of a Python program and here we learned about the white space and indentation and how the smart editors help with that, we also talked about string formatting with the curly braces to sort of generate strings in a much nicer and more flexible way, and we got the very basics of functions go in there as well.
App 3, When Is My Birthday this one was all about dates, times and time spans, now these can be tricky in Python so I made this special app just to help you become comfortable with those and sort of reinforce what we learned in the first two apps.
App number 4 was our Journal or a daily diary and this was pretty big, we covered a lot of stuff, this was our first sort of major app I would say.
And, because it was a bigger app we actually broke our code into multiple files, we had our journal, module, and we had our main program.py and we had those work together, we did a lot of file I/O and to facilitate that we did path management and in OS independent way using you know, not surprising the OS.path; we used for in loops and iterators, obviously playing a key role in the for in loops, because we had these different files, we might want to reuse the methods and features of one of those modules, and if we have been just calling main, or just writing our code directly, like write in line there, we wouldn't be able to import those, so we talked about the dunder name convention, to differentiate between when your program itself is being run and when your script is being reused.
Also we talked about documenting your own code with docstrings.
App number 5, this was our weather forecast app and we went out to weather underground and we grabbed the HTML and we did that using a technique called screen scraping by making an http request, using the request package pulling that down, taking the HTML, send it off to the BeautifulSoup package and then writing css selectors against that in memory to actually pull out the data we want.
In order to get Requests and BeautifulSoup, which are external packages, we used pip and we could further isolate our particular environment in our application using virtual environments.
Along the way I also got to introduce you to a very Pythonic concept called slicing.
App 6, I hope this one made you laugh, this was the Lol Cat Factory, And this was all about consuming services and working with binary data, so recall, we went out to our lol cat service, we downloaded a bunch of binary images, and we saved them to the file system and then we used Python to subprocess capability to launch the finder or folder browser explorer depending on your os, we'd launch that up to actually show the UI since we are not diving into things like PyQt and so on.
App number 7 was our Dungeon and Dragons Style Wizard Game, this was one of my favorites and this one is about a very core concept in modern programming and Python classes, inheritance, things like that.
So we talked about how you create classes, and model your data structures with classes, and inheritance allows us to sort of separate that into layers, so it's more reusable, more manageable Python has magic methods, sometimes are referred to as dunder methods, like __init__(), __str__(), for overwriting behaviors of our classes, our objects for example we can write the __init__() to take control over the creation and initialization of our objects, and speaking of initialization, we saw when we are doing inheritance that you need to be careful about chaining these together in the correct order so the whole hierarchy get setup correctly.
Typically, this is where you define your fields and these __init__() methods and when we are using our objects you don't have static typing in Python, instead we use something called duck typing to achieve polymorphism.
In application 8, we built a file searcher app and we got to feed a ton of data to it, remember we had our 2.27GB of text what we were searching with this thing, so in order to make that much more performant and efficient we used generator methods using the yield and yield return keywords, and because we were trying to process a hierarchy, a tree of data, the file structure, we used recursion which models that scenario very well.
In app 9, we got hold of that comma separated value data on the Sacramento real estate market in 2008 I believe it was.
And we loaded that up, we did all sorts of interesting processing on it, we saw that at the heart of Python are dictionaries, and dictionaries of course played a key role in our comma separated value processing as well; we wanted to sort the data that we got back out of that file and we did that by passing a method we saw that methods were first class objects in Python, and when you want to write really small concise ones, it's much better to use a lambda method or lambda expression rather than creating a method somewhere else and just so that you can pass the name in where you are.
We talked about the csv file format and how Python has built in support for processing that, we also saw that we can run into a situation where Python 2 and Python 3 behave differently, remember we had the statistics module that's in Python 3.3 and above but not in Python 2 so we wrote special code to sort of do a polyfil or fill in that missing piece for Python 2 and the consumers further down in our app didn't have to worry about whether there was the statistics module we introduced a less efficient but sufficient one for them to use.
we also saw that many of those situations that are solved with loops and lists, so looping over a bunch of data, doing some tests, things like that, can be solved much more concisely with list comprehensions.
And while solving problems with lists is great, in app 8 we saw the performance implications that you can run into if you are processing a lot of data in a pipeline.
So, in app 8 we used generator methods, there is a similar concept in this comprehension style called generator expressions so we use those generator expressions to create efficient data pipelines.
The last app we built was app number 10, The Movie Search App.
This went out to the open movie database api and it pulled back data.
And that was interesting, we were processing json and things like that, but the key focus of this app was to build reliable code.
When you have a network based code chances that the network is down are pretty good, especially if you are running on a client machine like a laptop where it could be out of wi-fi range or whatever.
So we need to be able to handle those errors we introduced the try except block to do that,we also saw that you can use multiple except blocks to handle errors by type, remember, put them both specific one first the most general one last, we also talked about validating arguments by raising errors; we had some kind of argument pass doing init method for a class the init method doesn't have a return value that you can pass back and say false or none or something like this, but we still want to validate the arguments coming in and so we saw that we could raise an exception when invalid data was passed and then the object would never even be created unless it started out in a valid state.
|
|
show
|
1:24 |
If this class has made a difference for you if you feel like you've learned Python like you haven't before, if you had fun while you were doing it, I would really appreciate if you could send out a little bit of love on social media, maybe a message on Twitter or a post on Facebook, even better, tell your co-workers about it, I have special pricing for teams and I'd really like to help groups of people become better at Python.
So, if you think your co-workers could benefit form this course, tell them.
And, if you are really inspired write a blog post about it, that would be awesome.
Thank you for taking my course and I wish you the best of luck on whatever it is you are building next, enjoy Python it's a great ecosystem and you can build amazing things with it.
If you are wondering what to do next, you can always dig deeper into these concepts by subscribing and listening to the Talk Python To Me podcast.
That's a podcast I created and I host weekly, it's the most popular Python podcast at the time of this recording, so I really encourage you to subscribe even if you don't understand everything, all the experts that come on the show talk about, being exposed to the ideas will make a huge difference and accelerate your Python learning.
So, head on over to the talkpython.fm and check that out.
I am also building more advanced courses, keep an eye on what is coming next at training.talkpython.fm.
Thank you so much, it's been great to share this time with you and I hope you've learned Python in a fun and engaging way.
Thanks and goodbye.
|
|
show
|
0:51 |
This course was made possible by a Kickstarter project that many of you have backed.
I'd like to take this moment to say thank you, thank you, thank you, I really truly appreciate it.
For a long time I have dreamed to starting a whole library of courses like this and making a difference for helping developers, learn more and become better programmers.
So those of you who contributed to the Kickstarter you helped me take that step and I really do appreciate it.
There are few folks who contributed more than others, and they backed a special contributing student level, and I wanted to say especially thank you to them, from Mike Herman at Real Python, Mat Makai Full Stack Python, Mahmoud Hashemi my former guest on the Talk Python To Me podcast, and everyone else.
Thank you so much for being part of this course and helping it happen.
|