What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Python: Getting Started
by Bo Milanovich
Python is very simple, yet incredibly powerful programming language. You can use it for writing web and desktop apps, scripts, and more. This course teaches you the basics of Python syntax, functions, creating console and web apps, and distribution.
Resume CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Course Overview
Course Overview
Hi everyone! My name is Bo Milanovich, and welcome to my course, Python: Getting Started. I have been a Python developer for a long time, and I'm super excited to share my knowledge of this awesome programming language. With Python, you can build web apps, desktop apps, do scientific computations, create scripts, artificial intelligence software, and even home automation software. This course is a getting started course for developing python applications, and you don't need prior experience with Python at all. Some of the major topics we will cover include installing Python on every major operating system, learning the syntax and the basic features of the language, developing a console application and converting it into a web app, and creating executable files and setup wizards from our Python app. By the end of this course, you'll know the basics of Python programming and be ready to develop applications on your own. From here, continue learning by diving into Python frameworks and libraries with courses on Django for web development, TensorFlow for artificial intelligence, SciPy for scientific computation, and PyQt for cross-platform desktop applications. I hope you'll join me on this journey to learn Python with the Python: Getting Started course at Pluralsight.
Introduction
Introduction
Hi! Welcome to the Getting Started with Python course. My name is Bo Milanovich, and I will be your instructor for the course. In this course, we will first learn the basics of Python, things like data types such as integers or strings, functions, classes, and of course some unique Python-only features. You will see how to put it all together. Throughout the course, we'll be working on a console application that we will later convert to a web application while reusing most of our code. Now how cool is that? Python is a very simple, beautiful, yet extremely powerful programming language. You can get started almost immediately with minimal setup required. It is cross-platform, which means that it can run on most major operating systems including, of course, Linux, Windows, and Mac OS. And you don't have to modify your code at all. But what can I use Python for? you wonder. Well you can use Python for pretty much anything. Use it for building console applications, scripts, scientific computations, web back-ends, desktop applications, and even home automation and machine learning or artificial intelligence apps. The list just goes on. This is a beginner-friendly course so I don't expect you to have any previous knowledge of Python. But I would appreciate it if you had atleast some basic general programming knowledge, meaning you know what a function does and what a variable is. Even if you do know Python, I highly recommend that you continue with the course. Python consistently ranks as one of the most popular programming languages in the developer community. In the end, you will really see how simple Python is yet how amazing it is at the same time. But what is it exactly that makes it so amazing?
What Makes Python so Amazing?
Let's take a look and see what makes Python so amazing. First off, Python can do a lot of things, and I mean a lot. Did you want to write a small script to automate some boring tasks like parsing CSV files every 30 minutes or doing some regular server maintenance? You can use Python for that. Complex scientific computations and graphs? Sure. Now how about a cross-platform desktop application that has a native look and feel on every operating system? Yup, use Python. Android development? You bet. Build a website back-end and write one line of code to have a completely-working administrator interface? Use Django, a famous Python web framework. Finally, do you want to write an app that is automatically going to recognize pictures and categorize them? Is it a dog or a cat or something else? Go for it. Use Python with the excellent TensorFlow library. Here are some of my personal examples. I built a desktop application that is cross-platform using Python and PyQt. In fact, I have a course here on Pluralsight about building desktop applications with Python. Then recently I was working on two websites, a simple one where I used the Flask micro framework and a more complex one where I used Django. And they're both excellent Python web frameworks. But I also started experimenting with home automation a little bit. I bought a humidity sensor and a smart power outlet. My idea was to have the room humidifier turn on automatically if the humidity drops below a certain percentage. So I connected my humidity sensor to my RaspBerry Pi, which loves Python by the way, and wrote a script that reads the humidity sensor data every 60 seconds and then if the humidity value drops below a certain cutoff point, it sends a command to the smart outlet to turn on the humidifier. The best part? This is all done in less than 20 lines of code. There are just so many Python libraries available for all sorts of things. You will see some of them later in the course, and you will learn how to install them as well. To top it off, Python is very simple to write. Here's the Java code that prints the HelloWorld to the console or, rather, standard output. Now here's the Python version of the code that does the same thing. Granted, the Java has a class defined and a function, but you must define those in Java. Not so with Python. Python also kind of forces you to write readable code, at least more so than other languages. You see, where most other languages use curly braces to delimit blocks like an if statement or a function body, Python actually uses indentation. Yes, Python uses tabs or spaces, I don't want to start a war here, trust me, instead of curly braces. In addition, Python's rules if you really want to call them that come in the form of PEPS, or Python Enhancement Proposals. There are many PEPS, but one of the first ones you will hear about is called PEP8. This rule is a guiding principle as to how you should write your code to make it more readable. For example, that there needs to be two empty lines before a new class declaration or that function names should be all lowercase with underscores for spaces and not CamelCase like with so many other languages. PEP8 is usually already integrated in most Python editors including the one you will use in this course.
Python 2 vs. Python 3?
If you have looked up any information about the Python programming language before you started this course, I'm sure you have encountered questions and arguments on both sides that talk about which version of Python you should use--Python 2 or Python 3. The eternal debate. Online forums are full of people saying you should use Python 2 because of this or Python 3 because of that. But in reality, just use Python 3. For reasons that are really outside the scope of this course, Python 3 adoption did not go as smoothly as it was hoped. Therefore, even today, you will see some versions of Python 2 being used, hopefully at least one of the 2.7 versions. Now granted the last version of Python 2.7, the 2.7.13, was released in December 2016, so it is not ancient, at least not at the time of recording this course. However, Python 2 is going to reach the end of its life in 2020. There'll be no Python 2.8, and there'll be no new features added in 2.7 either. Even so, some systems, including those designed by a certain fruity company, still come bundled with Python 2.7. And on the other side, we have Python 3. It brings a lot of improvements, new features, and some nice new tricks. Python 3 was released in December 2008. Yes, it has been that long, and there are still adoption problems. There are not that many differences between Python 2 and Python 3 at least as far as the syntax is concerned. Mostly some things are working differently under the hood. As a beginner, one of the first differences you will notice is that Python 2 treated print, the ability to print to the console, as a statement, meaning you would have code such as print "Hello World," whereas in Python 3 it's a function. So the code has to be print("Hello World"), things of that nature. If you are for some reason stuck with Python 2, I encourage you to keep following this course. You will be able to use probably all of the code samples that we write, and I will try to point out the differences between Python 2 and Python 3 as we move forward. Whatever the case, in this course, we will work with Python 3. And at the time of recording this course, the latest version is Python 3.6. So let's go ahead and install it.
Installing Python
Well, we've talked about Python so much, the next logical step for us would be to install it, right? Installing Python is quite simple. It involves a download from the Python website if you're using Windows or Mac OS, or installing it from a Packet Manager if you're using a Linux distro. If you're using Windows, simply open your browser, navigate to www.python.org. From there, go to Downloads and click on the Python 3.6.0 or whatever latest Python 3 version is there when you're watching this. The website will recognize which operating system you're using, so you don't have to worry about selecting Windows specifically anywhere. Once the download finishes, open the Setup file. Make sure to check the Add Python 3.6 to PATH option in the installer, and then click on the Install Now. When the installation is done, let's verify that we have Python on our machine now. Let's close the installer window and go to the Start menu, search for PowerShell or cmd, whichever you prefer. Now let's type python --version. If you get this message, it means that the installation was successful. If not, try to repeat the steps in this video. That usually helps. Most Linux distributions come with Python already installed, but often the default one is Python 2. I have Ubuntu here, so let me open the terminal. And if I type python --version, I see that I have Python 2.7.12 installed. And this is on Ubuntu 16.04 LTS. But if I type python3 --version, I see that I also have Python 3.5.2 installed, and it's okay that it's not 3.6. It's not a big deal. Let's try it. Python 2 and Python 3 can coexist on the same system just fine. However, we do need to install one more tool that we're going to use and that Ubuntu does not come with. You will learn more about this tool later. But for now, just type sudo apt-get install idle3. If your Linux distribution did not come with Python 3, I'd suggest you look up instructions online on how to install it on your machine. It should not be too hard at all, something as simple as sudo apt-get install python 3, or using your distro's Package Manager should suffice. Note that some Linux distributions already come with Python 3 installed and as a default, so make sure to check the regular python --version first to see if it's Python 3 or not. On Mac OS, the procedure is similar to Windows. Simply open up www.python.org and download the installation file. Again, it's going to recognize your operating system, so you don't have to bother on clicking Mac OS anywhere. After that, follow the standard procedure for installing an application. Once the installation has completed, to verify that you have the correct version of Python installed, open the terminal app and type python3 --version. Notice that we have the same issue here as we had on Linux. So if I do just python --version, it's going to show 2.7.10 as this is the default version of Python that Mac OS ships with at this point. Try not to use that one. There is another way to install Python 3 on Mac OS and that's using homebrew. It is a little bit more involved, so I'd suggest you search the web for a homebrew Python 3 installation especially since there are certain advantages to installing it through homebrew. For what it's worth, I will always be referring to the Python terminal command as simply Python for the rest of the course and never Python 3. Alright, well we have our environment all set up, but what do we do with it now?
The Awesomeness of the Python Console
Once you install Python, you will have this handy tool in your terminal or PowerShell called Python. If you just type Python in your PowerShell or Python 3 in your Linux or Mac OS terminal, you will have access to the entire Python programming language. This is something that's pretty different from other languages such as C# or Java. If you're a front-end web develop, think of this as a JavaScript console in your browser's developer tools. So what does this mean exactly? Well being able to just type Python in the terminal gives you some advantages as a developer. For example, suppose you're not really sure of a result of a function or any other code output, you can simply start the Python interpreter on your machine and see what happens when you use the function. In fact, many Python IDEs come with the integrated Python console, although that's the same as if we ran the Python command through our terminal. Unfortunately, this does come with a small drawback but nothing we can't really deal with. This also means that every Python program we run, we have to prefix it with the Python command. Let's say we have a hello.py file that's simply prints Hello World to the screen. Well in order to run it, we have to open our terminal and type python3 hello.py. Now during development, this is usually abstracted from us a little bit as the editor will give us the option to run this program specifically. Now you may be thinking, Geez, PythonB, well this is inconvenient! I thought you said Python is easy and all. I don't want my users to install a whole programming language just so that they can use my program. And they don't have to. Towards the end of this course, we will see how we can package our app so that it is one binary file, like a .exe files for Windows, that you can run on any operating system. As mentioned, we definitely have the full power of Python if we run Python from our command line. But if you want a little bit more than that, such as syntax highlighting, you will want to run the IDLE, or Integrated Development Environment. Note that this is not just any IDE. This is the one that comes installed with Python by default or the one you installed as the IDLE 3 package if you're using some flavor of Linux. To open it, simply search your machine for IDLE. Now you may notice that my terminal and my IDLE look almost the same. And you know what? As far as Python, they pretty much are. Remember, running IDLE is the same as if you type Python in your terminal. There's no difference in the functionality of Python behind it at all. The advantages the IDLE offers over a regular terminal are code completion, syntax highlighting, although all at a pretty basic level. In fact, it will be the same even if you have an application that has thousands of lines of code and spans across multiple files or Python modules as we call them. You'll run the one main file through the Python interpreter, for example, our hello.py file mentioned earlier. In the beginning of the course, we will be using IDLE just to familiarize ourselves with the basics of Python. Very quickly, though, we will jump into a great free integrated development environment called PyCharm. So let's go ahead and get it now.
Installing PyCharm
Although we will start with Python's IDLE, later on we will be using an IDE called PyCharm. This is an excellent IDE that is built by the same folks who brought you IntelliJ IDEA, ReSharper, WebStorm, and many others. They have a free community edition of PyCharm available for download from their website. Of course, you don't have to use PyCharm if you don't want to. Any text editor will work just fine including just Notepad. But Atom, Visual Studio Code, Sublime Text, Wing IDE, Vim, Emacs, you name it. For the purpose of this course, however, we will be using PyCharm. To download it, simply go to www.jetbrains.com/PyCharm, click on the Download Now button, and then download the free community edition of PyCharm. Once you download, install, and start PyCharm, click on Create New Project. Make sure that Pure Python is selected on the left. This is where you can put in your working directory, and this is where you can select your Python interpreter. If you click on this, you will see that PyCharm should be able to automatically recognize all the installed Python interpreters on your computer. If for some reason it does not, you can add the interpreter manually by clicking on the three dots. Make sure that you select Python version 3 so whether it's 3.6.0 or 3.5, it doesn't really matter a whole lot. As you can see, though, I have several Python interpreters available on my machine. But many of these are something called virtual environments. What are those? Well virtual environments are a fairly unique and incredibly useful feature of Python that we will take a look at at the end of this course. You don't have to have a virtual environment created for now or worry about it at all. Simply click on Create with the Python 3 interpreter selected. Also I'm going to name my folder PluralsightCourse and then click on Create. With the PyCharm's main window now open, you can right-click on our folder that we just created, select New, and then Python File. Let's name it helloworld.py. In this newly created file, let's just type print "Hello World". Now right-click on the same file and select Run 'helloworld.' Voilà! You can see that your first program, as simple as it is, just executed in PyCharm's integrated terminal. Congratulations! If you're using a different text editor, simply create a helloworld.py file on your system somewhere and run it through the terminal. Just navigate to the directory where you saved your helloworld.py file and type Python helloworld.py. If your text editor has a Python plugin, even better. I'd suggest you utilize that. Now I know I have been boring you with all the introductory talk but so little code, and I'm sure that most of you by now are super anxious to get some coding done. So without further ado, let's do that.
Types, Statements, and Other Goodies
Introduction
Welcome back! Hope you're enjoying the course so far. In this module, we're going to get our hands dirty and see some Python code. We will take a look at Python's built-in data types such as integers, strings, Boolean, flow control statements like if, for and while loops and lists, which are very similar to arrays, and you will then learn Python dictionaries, which is a fairly powerful data type, and it closely resembles JSON. Throw in some try and except into the mix so we can see if our code will execute and, if needed, handle something called exceptions. Of course, Python has a lot of other built-in data types such as tuples, sets, frozen sets, complex. My hope is that by the end of this module, you will be familiar enough with the basic Python data types that you will be able to learn the rest of them by yourself. In the end, you will see that you don't really have to worry about data types in Python as much as you do in some other languages. Namely, you don't have to declare a type of the variable in advance. Python will figure that out for you. There are some advantages and some disadvantages to this approach. So let's take a look.
Types in Python - Wait, What?
So I named this video Types in Python. Wait, What? Because I feel that the way Python handles types is different from a number of other programming languages. In many programming languages, including Java or C#, you have to declare a variable type before you declare the variable itself. That variable type can be an int, float, a string, a char, a bool, and many others, or your own custom type such as student. So before you even declare a variable, you have to write something like this. Granted, some languages allow you to omit the it part or the type part in general and simply write var and then the compiler will figure out the type for you. But this is not always applicable. Python takes this approach in the opposite direction. With Python, it is not necessary to declare a type anywhere. In fact, you would declare variables like this. The same thing goes for any other type including your own types that you will create later on. Aside from that, Python simply does not have as many typed as C# or Java. We don't have to deal with longs, doubles, unsigned or signed integers, shorts, etc. Now there are certain advantages and disadvantages to this approach. The jury is still kind of out on that one. For example, in languages like Python that do not require you to declare types up ahead, also called dynamically typed languages, many argue that it simply speeds up the development time. You do not need to spend a lot of time worrying about types. This may not seem like a huge obstacle with built-in types or in the beginning, but it becomes somewhat burdensome when you're dealing with a plethora of your own custom types. It simply makes the language a bit too verbose. But there is the problem. Not declaring types up ahead can lead to a fair number of early and subtle bugs. For example, let's suppose that we have a simple function that adds two numbers together. That function will look like this, right? Now this function call should work just fine, right? And it does. But what if we call it with this? We get a Python TypeError telling us that it cannot add an int and a str, or an integer and a string. Python does not really have a mechanism to tell you this in advance whereas in other languages where you are required to declare types called statically typed languages, the editor itself will usually tell you before you even run the program, or the compiler will simply fail to compile the program if an error like this occurs so the program won't even run. Now there is something called type hinting in Python. This is a relatively new feature added in Python 3.5 that can help you add data types to your Python program. However, note that this is essentially just helping Python editors and IDEs. It is not going to prevent your code from running if you put a wrong type, merely, your editor will just yell at you. While this is definitely helpful, it is far from a full-out replacement for a completely statically typed language. How do I deal with this? I try to write lots of unit tests with Python perhaps more so than with other languages. I throw in all the different data types into the mix and expect a large chunk of my unit tests to have their assertions fail. Whether this is the right approach or not, well, this has been a part of an ongoing debate for decades. So let's take a look at some of the basic Python built-in types. And since we already talked about them, let's start with integers and strings.
Integers and Floats
Put it simply, integers are numbers and floats are decimal numbers. Defining integers or any other type in Python for that matter is very easy. Simply type the variable name and assign it a numerical value. That's it. Integers are number that can be positive or negative or 0, but they cannot have a decimal point. They have an unlimited precision and support all kinds of mathematical and arithmetical operations such as addition, subtraction, getting a remainder, absolute value of the number, and more. Aside from integers, we also have floating point numbers, or floats, which can be decimals. They too support the same operations as integers. Python 3 also introduces complex numbers as a type. Remember, though, I said that there's no real need to worry about types in Python, at least not in the beginning. You can seamlessly add integers and floats if you would like to, and Python is not going to complain about you adding two different types and what not. It will produce the desired result. So normally you don't even have to think about the fact that whether or not an number if an integer or a float. Just go ahead and do whatever operation you want with it. Integer type is int, and floating number type is float. This is great because we can use these type names to convert or cast a variable to an integer or to a float when we really need to. Simply surround your variable with int or float to convert it. Be aware of those pesky little value errors that can arise if you're trying to convert something that's clearly not a number to, say, any integer. What comes right after numbers? Why text of course, or strings.
Strings
We use strings to represent text. By default, it's Unicode text in Python 3 but ASCII text in Python 2, and this for me was one of the biggest advantages to upgrading to Python 3 by the way. Strings can be defined using single quotes, double quotes, or three times the quotes, either single or double. There's absolutely no difference which one you use. You will often see though that three-times quotes are used as a function or a class documentation. Yes, although we use the pound key to have comments in Python, we don't have anything like multiline comments interestingly. But it is perfectly acceptable to just put a multiline string using these three-times quotes, but that string is not going to be assigned to any variable. It's just going to be sitting there as a comment. In fact, your editor will even recognize it as a comment. Again defining strings is super simple, just type a variable name and assign it that textural value. Strings in Python support a lot of methods including a lot of useful utility methods. Some of them are a capitalize, which will make the first character a capital letter, replace, which takes two arguments, the first one being the character to be replaced, and the second one is the character to replace it with. Then we have isalpha or isdigit, which will return true if all characters are letters or digits respectively. A couple of other methods you might find very useful when dealing with strings is split, which also takes one argument, and format, which takes a variable number of arguments. Split function is called when you want to split a certain string into a list using a character you specify as the argument. This can be useful when you receive some data as a string. But as a matter of fact, you see that data as a list of values separated by comma, tab character, pipe character, whatever. String format is normally used when you want to replace existing text in a given string with a variable value. You will see it at the end of an existing line of text. For example, let's say we have a variable called the name defined as PythonBo and machine defined as HAL. But those variable values can change at any time. We can have a different name or a different machine, yet we still want to print the message that says, Nice to meet you-- name. I am--machine. In order to do that, we have to write the code that looks something like this. This looks a little bit weird, doesn't it? What you should know is that the variable name and the number 0 go hand in hand. The number 0 here indicates the position of the argument in our format function. So since we have a name as our first format argument, and Python is zero-based so first actually means 0th, we put the number 0 in our sentence so that evaluates to name. Similarly, we put the number 1 so that it evaluates to machine. We can keep going on with this forever. In fact, we can put 0 again anywhere in our sentence, and it is still going to refer to name. You can add as many arguments as you want to the format function, but wait a second. How can you add as many arguments as you want to any function? Don't worry, you will learn that soon enough. Format is a pretty powerful function. It supports padding, printing class attributes, and much more. In fact, there is an entire website dedicated just to the format function neatly documenting all of its uses. Python 3.6 did introduce string interpolation though. So now you can do something like, Nice to meet you, (name). My name is (machine). Note that these are directly variable names themselves. But you have to prefix the whole string with the letter 'f' outside of the string itself, in front of the string. Though this is not the only time you use a character in front of a string in Python. There is also the letter 'r' to denote a raw string so that Python does not automatically escape backslashes and what not, or the letter 'u' to tell Python 2 that it is a Unicode string we're dealing with. Which one you should use? The choice as always is yours. I personally like the string interpolation, but, like I said, the former function is pretty powerful. All this time and we only went through integers and strings, and here I was saying how simple Python is, right? Don't worry, I'm just trying to build your knowledge from the ground up. There are some other interesting string properties like string slicing, but I think you've had enough of strings for now. Let's move on to Boolean and None.
Boolean and None
I promise this clip will be simpler than the last one. Boolean indicates a True or a False value. You can assign any variable to be true or false, and declaring a variable as a Boolean, what do you do? You guessed it. Simply type the variable name and assign it True or False. Boolean is most often used with if statements or when you're trying to set a flag for something like, Has this task been completed yet? Yes or No, or True or False. One thing to note about Boolean in Python compared to most other programming languages I've dealt with is that they both start with a capital letter T and F for True and False. Interestingly enough, you can convert a Boolean to an integer, and it will give you a value of 1 if True or 0 if False. But know, however, that converting True or False to a string will simply give you a textural representation, so a string with a value True or False. None is similar to null in other languages. It denotes that a variable has been defined so that us, the developers, have typed the name of the variable somewhere, but that it is not associated with any value. We found no aliens so far, well, at least at the time of recording this course I guess. I find it useful sometimes as a placeholder variable, something that I will use later, but I don't have a value for it right off the bat. It also evaluates to False in an if statement, so that can be useful as well. However, there is a difference between a None variable and a variable that you have not defined at all. Simply typing, say, K in the IDLE will produce a name error saying that K has not been defined. But if we assign None to, say, aliens_found and then type it, it will not give any result, which is normal for None, but it will not cause any errors to show up either. The type of None is called None type. Now let's take a look at see how we can use these beauties in if statements.
If Statements
We have gotten to the if statements. If statements in Python are very simple. Yep, I use the very simple phrase again. Let's take a look. This is what a normal if statement looks like in Python. Now is as good a time for a reminder as any. Remember, Python uses indentation instead of curly braces for any code blocks. This includes if statements, loops, and functions as we will see later on. Notice also that our if condition and our else both end with a colon. This is also a requirement in Python. We use the double equal sign to check for equality in Python. We also sometimes use the keyword is to see if two variables are pointing to the same object in memory. In this case, we have an equality check. We're asking if the variable number is equal to 5, which it is. This if statement works as it should. It'll print the Number is 5 to the console. Let's suppose that you just want to check if a variable has a truthy value. Python has this concept of truthy and falsy values. For example, any number other than 0 has a truthy value. That includes 1, -50, 3,000,000, whatever. Similarly, a string that has any value or any text, so as long as it is not an empty string, also has a truthy value. This means that the truthy integer and the truthy string will evaluate to True, and as such, our if statement would pass. When you're dealing with lists, which are similar to arrays, and we'll learn them pretty soon, in many languages, you have to check the length of the list and see if it is greater than or equal to 0 in order to put it in an if statement. With Python, you could just write if, let's say, student_list, so you'll just have if student_list. Many times you'll find you don't really care about the value of a variable in an if statement. You just want to make sure that it is defined. So in that case, you will write something like this that you see on the screen. Now maybe this number of text value will change over time, but we don't really care. We need to execute the two if statements every time their conditions have a truthy value. There is no need to do if number is not equal to 0, just to check and see if the number is defined or not. I mean why would you if you can keep it simpler and do it this way? You will see some other programming languages have utility methods especially on strings called IsNull or Empty or just IsEmpty or similar. There's no need for those in Python. Of course, the same can be said for Boolean and None. If you have a python_course = True variable defined, there is no reason for you to do anything along the lines of if python_course == True. That's just silly. Simply typing if python_course is enough. The None type we discussed in our previous video has a falsy value as you might assume. So if the variable has a value of None, say, aliens_found = None, the if aliens_found will not execute because this condition is not true. Rather it will skip this if block or hit the else block if one was defined. To do an opposite if check, meaning to check and see if your condition is false, you normally use the not equal (!=) sign. And sometimes is not as a counter to the is mentioned earlier. That allows you to not have to write an empty if condition and then just put your code you expect to execute in the else block. Additionally, when checking to see if a value is truthy or falsy, you can prefix it with the keyword not. You will see this very often when it comes to dealing with Boolean and None. What else? You can also check for multiple if conditions in one line generally using and and or keywords in Python. No they're not double ampersand or double pipe as in many other languages, but rather just words--and and or. The way it works with and is that both variables to the left and to the right of and need to have a truthy value. Only then will the condition be true, and your if block will execute. If one of them is false, your if statement will not execute. With or, one of the two variables on either side need to have a truthy value. The other variable can be falsy. If they're both falsy, your if statement block would not execute. You can add as many variables as you want to your if statement, although it's probably the best idea to not keep that list of variables too long. Python does support one-line or sometimes called ternary if statements. If you have used other programming languages, you've probably seen question marks sometimes thrown in the middle of an if statement. Python works a little bit differently. Let's say we have two variables--a is 1 and b is 2. And let's say we want the text bigger to display if a is greater than b or smaller if a is smaller than b. We could use the normal if statement, but we can save ourselves some space and write it this way. We see that we get smaller because 1 is smaller than 2. So let's analyze our if statement a bit. All we really did is rearrange the position of our condition check and the output in our code. We still have our condition, a is greater than b, and our action in case it is greater or else. Really, try to read this in broken English. Instead of reading it as if a is greater than b, then bigger, else smaller, try to read it as bigger if a is greater than b, else smaller. Although they may look useless, they can actually be a pretty powerful tool especially when dealing with list comprehensions. And speaking of lists, let's take a look at them next.
Lists
Up until now, if we wanted to declare a string or an integer variable, we would type code such as x = 5 or instructorName = PythonBo. Our variables now have a specific type or object. In this case, we have an integer and a string. But sometimes we want to hold multiple objects under one variable. For example, instructorName = PythonBo is fine, but student_names is going to contain a list of students enrolled in this Pluralsight course. So under variable name, we can have Mark, Katarina, Jessica, and so on. This is where the power of lists in Python comes in. Let's start. To define a list in Python, what you do is write a variable name, so let's take our student_names example, and assign it empty square brackets. There, you have just created an empty Python list. However, let's say we already know some of the students enrolled in this course, and we want to create our student_names list with their names already in it. In that case, we simply replace our empty brackets with Mark, Katarina, and Jessica. Now our list has three string elements. Notice how they all have their own enclosing quotes because they're strings and how they're separated by a comma. In order to access an element in a list, we use something called an index. An index is an integer value starting from 0, which corresponds to one and just one element in the list. For our list, if we wrote code such as student_names(0), we would get Mark. If we wrote code name such as student_names(2), we would get Jessica. The important note here is that the list indices in Python start with 0. So even though we have Mark as our first element, the index is 0. The next element, Katarina, which is our second element in the list has an index of 1, and so on. Now if we wanted to get the last element in our list, we can write something like student_names(-1). This is going to give us the first element from the right side of the list, or in our case Jessica. Here the indices do start with 1 though. It is a bit confusing. But just remember there's no such thing as -0. Replacing an element in the list is also as easy as checking for a specific list element. So let's say student_names(0) = James. If we now print student_names, we see that Mark is gone and that James took his place. But let's say that we already have an empty list or a list of three students, it doesn't really matter, and we want to add more students to our list as our code executes. Well we can't do student_names(3) = Homer, but what we can do is use a function in our list called append. We can pass any object we want to the append function as its argument. Once we do that, the object we have passed through the append is added at the end of our existing list. Obviously sometimes we want to check and see if we already have an element in the list. Is Mark still in it or not? Well if you know English, you know Python because this is as simple as "Mark" in student_names. If the Mark element is in the list, this will give us True. If it's not, it will give us False. Many times, though, you want to see how many elements you have in a list. By simply using the Python's built-in function called len and passing it our list as an argument, we will get an integer telling us how many elements we have in our list. This len is short for length, and it can be used for other types as well by the way. Like len of a string will count the number of characters in a string. Lists in Python are very similar to arrays in many other languages, but they come with some added benefits. Like what? Notice how as is usual with Python, we never told our program that our list is a list of strings. We just started typing strings. For that reason, we're completely free to add the number, such as an integer or a None or a Boolean or any other type to our existing student_names list. Python is not going to complain. Having multiple types in a single list is just fine. Although in my experience, it's best if you restrict your use of a list to a single type just to minimize potential bugs. Now you may be wondering if you can just use None in the same list or an empty string or whatever, then how do you delete an element from a list. Let's say I just don't want Jessica on the list anymore at all. Good question. If we just did something like student_names(2) = None, we will remove Jessica alright, but our list size would not shrink. Simply None would replace Jessica. If we wanted to get rid of that element in the list completely, then we use the del keyword, short for delete. So we would have code such as del student_names(2), and, yes, it's del without any parentheses. Note that the list is going to shift to the left after this operation. So whatever elements we have kind of to the right of Jessica, which is just Homer in our case, they're going to get moved one position to the left, meaning that student_names(2) is now going to refer to Homer and not Jessica. Another nice list thing we have in Python is called list slicing. Let's say we wanted to ignore the first element of our list, Mark, and get all the other ones. Well what we can do is write student_names(1:). This skips the first element of our list and simply gives us the rest of them. Now remember how earlier we said that we can use negative indices to get the last element in our list. Similarly here we can use it to slice the list and ignore it. If we write student_names(1:-1), we have just ignored our first and our last elements in the list and printed the rest of them. Note, however, that this does not modify the list. The list will still stay the same. It's just that we are temporarily ignoring certain elements, for example, when printing. Of course, I just went through the basics of Python lists in this video, believe it or not. There are many other parts of the list, methods such as pop, reverse, etc. But the true part of lists in Python comes with something called list comprehension. For now, however, let's see how we can print every individual element in our list using loops.
Loops
In the last video, we saw that one way we can use or print a single element in our list is by referring to the list itself and then adding an index using square brackets at the end of the list. But what if we wanted to print every element in our list. Sure, we could do something like this. But this becomes very ugly very fast. Let's say we have 50 elements in our list. Are we going to call the print function every time? No, we shouldn't really repeat ourselves. This is where loops can be extremely helpful. Loops in Python allow us to go through a list or something called an iterable object and repeat a certain block of code that we define for every element in our list. There are two main loops in Python, for loops and while loops. A for loop is probably the one you're going to use more often, so let's start with that one. This is what a typical for loop looks like in Python. Running this code is going to print the name of every student in our student_names or, in other words, every element in our list. This name variable we introduced refers to the single element in our list, in our case, name, which is why we call it that. You can read this code as for every name in the student_names list, do this. Every time our code goes through a for loop, we call it an iteration. Once all the names have been printed, that is once all the list elements have been used, the loop finishes, and the rest of the code outside that for loop block executes. If you have encountered for loops in other programming languages, they usually look something along the lines of this. In this other code example, we're defining a starting point, which is usually 0 because it normally gets used as an array index inside the for loop, then we tell it how many iterations to perform, which is usually the length of an array minus 1, and an increment, which basically tells our loop what to do with our index we defined once an iteration finishes. Normally we want to increase it by 1 so that we can access the next element in our array. However, this doesn't look close to our Python for loop at all. Now you may be surprised to find out that this does the same thing as our Python code. Python as usual hides some things away from us a little bit so when it detects that you're iterating over a list, it automatically assumes that you want to start from the first element in the list and that you want to increment by 1 every time the loop finishes. And it detects the length of the list as well so it knows when to stop. Python for loop in this case works like a foreach in other languages, but Python does not have the foreach. It will just do that for you automatically when it detects that you're iterating over a list. But what if you do what numbers like a starting point and a step and all that? Don't worry, that's where the Python's range function comes in. Let's actually jump into the editor to see what kind of results we'll get. I'll open up IDLE. Suppose you have a piece of code that you want to be printed ten times on the screen. You don't have a list defined anywhere. You don't care for it. In fact, you don't like lists at all. That's fine. Let's see how we can use a for loop in that case. Let me write some code. I'm going to have a variable x defined as set to 0, and then I'm going to start the for loop and use this range function, which I'll explain in just one second. Every time we have an iteration, I want to increase the value of x by 10. And one way I can do that is simply by doing this. But a shorter way to do it is to do something like this. Then I want to print the value of x. Now let's see what happens. Once we enter the for loop, the x value gets increased by 10, so the first result is The value of X is 10 all the way to 100. But what does this range function do? Well it basically takes whatever number you passed it and kind of converts it into a list. In our case, that will be a list that looks something like this. It would go on all the way to number 9. Wait, what? Essentially, think of this number as the number of iterations or how many times you want your for loop to execute. We pass it 10, so we want our for loop to execute 10 times. In this case, the index variable we defined will be the element of the list. So, first, it's going to be 0, then 1, then 2, and so on. But what if I don't want the list to start from 0? I'm going to use this index in my for loop somewhere, but I don't want it to start from 0. Well I'm glad you asked. Range function also supports two arguments, so you can call it like this, range(5, 10) for example. In that case, our list is going to look something like this. It's going to start from 5 and go all the way to 9. In this case, our index variable, the first value it's going to hold in a for loop, is going to be 5. But wait! What if not only I don't want it to start from 0, but I also want the index to increase by 2 every time, not by 1. Glad you asked. Again, range function also supports three arguments where the last argument is the increment. So here you would have something like this. So in this case, the number 2 denotes the increment. So our list would end up looking something like this. Once the for loop finishes whether we are iterating through the list or using range, it doesn't matter, we will exit the for loop block and continue executing any code we have defined afterwards. But what if I wanted to exit the loop before it finishes? Or what if I don't really want just this one iteration to execute, but I do want to execute the other ones? Can I do that? Of course you can by using break and continue.
Break and Continue
In some cases, we just don't want the for loop to keep executing itself until it--runs out of elements in a list. Let's assume that we are searching for some string in this giant list of strings, so we're iterating over the list trying to see if it's there and then do something with that string. Well once we find it, there's no point in keeping the loop alive, right? We already found our beautiful string. That's where the break keyword can be very helpful. Simply putting the break keyword will cause your for loop to stop executing and exit without reaching the end of the list or the end of the range function. To demonstrate this behavior, I'll open up PyCharm instead of IDLE this time just so that we can get familiar with it as well. We are back with our lovely student_names example except in this case, I've expanded the list to include some more names. And I have a for loop that's simply testing to see if the name is Mark. And if so, it just says Found him! and prints his name. Otherwise, it just prints Currently testing and then the name or every element in this list. Now to execute this code, you can right-click on this file that's open in PyCharm and click on Run 'helloworld', or alternatively, you can right-click on the same file in your Project viewer and do the same thing, Run 'helloworld.' Let's run it. So we see that we found Mark for sure. But we also see that the for loop keeps going on and also tests Bort and Frank Grimes and Max Power to see if they're Mark, which they're not. So normally we don't really need to test it either. The only thing we need to do is add the break keyword. I'm going to add break right after this print statement, and I'm going to execute the code again and see what happens. Now after executing the code this time, we see that the for loop does not test for the last three guys in our list. This may not seem like much, but imagine if you had thousands of elements in your list, and the element you're looking for is in the second position. You can and should save valuable processing power simply by breaking out of the list. That's great. Now let's say that we want our for loop to execute for every element in our list except for Bort because, come on, Bort? When it reaches Bort, we just don't want our for loop to execute any of its code, but we do want the for loop to continue and execute its code for Frankie and Max Power, which come after Bort in the list. How do we do that? Simply by using the continue keyword. So the first thing I'm going to do is replace Mark with Bort here. We're looking for Bort now. And I'm going to get rid of the break keyword and add continue. Now let's run the code. And we see that we have our iterations for every single element in the list except for Bort. That's because by using the continue keyword, we tell our list to skip executing the rest of its code and just proceed or continue onto the next iteration directly. So everything below the continue keyword will not be executed. In fact if I hover over this print statement in PyCharm, it's telling me that this code is unreachable. Now Python doesn't just support for loops. It has while loops as well. Let's see what similarities and differences there are between the for and while loops.
While Loops
If you understand for loops, you will understand while loops just fine. While loops look very similar to for loops, also have break and continue, and the whole point is for the code to repeat itself while come condition is true. Now this is what a while loop looks like in Python. One difference that you can notice from the for loop is, other than the obvious use of while, that we have to manually increment our index or x value in this case at the end of our while loop. While loops are useful because they check for a condition before they even enter the loop. Here we have the x is less than 10 condition, but if we modify the x somewhere else in our code and made it 20 before the loop, our while loop would not execute at all. Similarly in our current code, once x becomes 10, the while loop will exit. One usage of while loops, although probably not always a great idea, is writing a piece of code that looks like something you see on the screen right now. This is going to call something called an infinite loop. Since True is always True meaning since our condition is always passing, this while loop will keep executing forever. How do we get out of it? By using the break keyword just like in for loops or by modifying the condition like changing the value of num in some other part of our code. The while True or other ways of infinite loops can be useful when you want to keep repeating a certain code all the time until something happens, at which point you would change the value of num above. Enough about loops already. Let's take a look at one of my favorite parts of Python, that is Python dictionaries.
Dictionaries
I love dictionaries in Python. Dictionaries allow me to store key value pairs of any data very easily. If you have used JSON in the past, then you'll be very familiar with dictionaries in Python. Remember how we had a list earlier that contained the student names? Well what if we wanted to add more details other than just the name to a single student like student Id, but we still want the student Id to be associated with the name. We can use a dictionary in that case. This is what a Python dictionary looks like. In a Python dictionary, we have keys and values. In our case, the keys are name, student_id, and feedback, and the values are Mark, 15163, and None. A key and a value make a pair or more precisely a key value pair. Each key is going to correspond to one value. Now the value can be of any type. Just like with lists, we don't tell our Python interpreter, Hey, this dictionary is going to have string keys and integer values. We can just add any type we want. In fact, a key can have an entire dictionary as its value. In this case, we would have something called nested dictionaries. In our dictionary here, we have three keys, which are all strings, one value, Mark, that is a string. The other value is an integer, and then we also have a None, and this is perfectly fine. Dictionaries are very useful when it comes to storing some kind of structured data. Obviously we can expand our student dictionary to have it also include grades, course name, etc., and I use them a lot when I do web development with Python because it's very easy to convert them into JSON and vice versa. If we want to group multiple dictionaries together, we simply create a list of dictionaries. Notice that I do have square brackets here defining a list. Then we can iterate through the list and use the data each dictionary contains. But how do I get the data? How do I just get the name? I don't want the whole dictionary, I just want the name value like I do with list. Can I do something like student(0)? How about you do something better. In order to get the value for a specific key, simply pass that key as kind of an index inside square brackets just like you would do in lists. So for our name, we'd have to do something like student "name". This will give us Mark. Awesome, right? One thing to be careful, though, is that sometimes if you refer to a key that does not exist in a dictionary, for example, let's say student "last_name", Python is going to raise an exception and say KeyError, which basically means I could not find that key. One way you can prevent this from happening is by setting the default value that you want to see in case Python cannot find a key. Instead of writing studentLastName, what you can do is write student.get "last_name" and then pass some default value as a second argument. Now in our case, it's "Unknown," so this is what Python is going to give you in case you cannot find last_name in the dictionary. If you wanted to just see all the keys that are in the dictionary, you can simply call dictionary.keys or in this case student.keys. This is going to return a list of all the keys, which in our case is going to be name, student_id, and feedback. And similarly for a list of all values, just call student.values. Changing values and deleting them with dictionaries is the same as it is with list. Using student "name" = "James" will change the student's name from Mark and James. And calling del student "name" will delete the name key value pair completely from the dictionary. So in this video, I mentioned how Python can raise an exception called KeyError if it cannot find the key with the name you told it to in the dictionary. Yes, using the .get method is one way of taking care of that. But there is a different way to handle exceptions in Python. Let's take a look.
Exceptions
Exceptions are events that occur during your program's execution that normally cause your program to stop executing. It usually means that some error has been encountered and that your program simply does not know how to deal with it. Recall that in our previous video where we were looking for a student("last_name"), but there was no such key as last_name defined, so Python screamed for help and said KeyError. There are many types of exceptions in Python. I'm not going to go through all of them, but there is a way for us to account for them and provide a graceful mechanism for handling those exceptions. Let's open up PyCharm. I have the example from our last video here, my student dictionary where we tried to access the last name without the .get method. Let's see what actually happens when we try to execute this code. And as expected, we get the KeyError as mentioned in the previous clip. To handle this error gracefully, we use something called exception handling. To do exception handling in Python, we work with try and except blocks of code. So if we want our code to handle this exception gracefully, it'll write something like this. We'll write it together. Don't forget to indent the last_name here. And then when we have the KeyError, we can print something like Error finding last_name, and just to verify that it worked outside of the except log, we can print This code executes! Now let's execute this code and see what happens now. And we see that we have Error finding last_name and also This code executes! But our exception is nowhere to be seen. So in the try part of our code, we are telling Python to attempt and retrieve the last name of the student except when there's a KeyError. If there's a KeyError, then we tell it to print a statement saying that it cannot find the last name. Note that both the try block and the except block can have more than one line of code. They can have as many lines as you want, although it's probably the best idea to keep it short. So what are the advantages of this. Why not just keep it the regular Python standard issue exception? I can think of a big one. Under normal circumstances, if an exception occurs, your program will simply stop working. The desktop application you develop will simply crash, and you'll have to restart it completely. However, when we account for exceptions, and especially when we are able to handle them, our program will not stop working. So it will not crash. It will simply execute whatever piece of code is in the except block and then continue execution with the rest of the code. One common example would be when you are trying to write to a file but do not have the proper permissions. Normally, this would cause an exception, but if you handle it, you can just present the user with an exception saying, You don't have permissions to write to this file. In fact, this can go so far that it is possible for you as a developer to define an unhandled exception handler so that any exception raised anywhere in your code base is handled, even the ones you do not handle specifically like we did here. Now one thing you may have noticed in our code is that we are just checking for the KeyError. I mentioned earlier that there are many exceptions in Python. One of them is called a TypeError, which occurs when you try to, for example, add an integer and a string together. Let's modify our code a bit and see what happens. So first of all, I'm going to add the last name to our dictionary, and I'm not going to add it to the dictionary directly but, rather, do it this way. So we'll call it Mark Kowalski and separate the code a little bit. And then over here, I'm actually going to have a variable called numbered_last_name, it doesn't really matter. I'm going to try to add number 3 to a last_name, which we now do have defined. So now let's run this code and see what happens. Now run it, and it says TypeError: unsupported operand type for int and str. So we got the exception, but we didn't get the Error finding last_name, nor did we get our This code executes text either. Well this is because we're only testing for the KeyError, not the TypeError that we got here. Luckily adding the TypeError into the mix is pretty straightforward. We can just go here and then chain the exceptions together. And we can write something like I can't add these two together!. Now let's execute the code once more. And we have the I can't add these two together message, as well as the This code executes message. But what if there's some error that you don't even know what it's going to be? Who knows if it's a TypeError or a KeyError or whatever. What you can do then is add a generic exception handler by simply adding except Exception. So let's do that quickly. I'll just print the Unknown error. The except Exception will handle any error that comes its way that has not already been handled by the KeyError or the TypeError that we accounted for. In fact, many times you will see an exception handler that just has except Exception. This will handle all the errors, but usually it's not a great idea to have just that since you do want to try to know what error you got. In fact, if I hover over the except Exception in PyCharm, it's going to tell me Too broad exception clause. Another thing that I hope some of you notice is the fact that when we had our unhandled TypeError exception earlier, it gave us a nice message saying, Hey, TypeError happened here on this line of code because of this. But once we handled it, all of that went away. We just ended up with this message we printed. That leaves us developers with little information on where this code failed exactly. Luckily, we can address that issue too. We need a small modification to our except bar. In fact, let me get rid of the except Exception completely. And then over here, we add as error, and then we can simply print that error to see what it is. So if I run the code now, we get a message saying unsupported operand types for int and str, which is the same error message that we got when we had the unhandled exception. Unfortunately, this still does not give us any information about the line number. To get the line number and all the other information about the exception, we would need the built-in Python traceback module. It's pretty simple, but we haven't talked about modules yet. But don't worry, we'll look at that soon enough. There is also a way for you to create your own exceptions, raise any exception you want at any point of time in your code, and have a finally handler after your exception block, which will basically always execute. All those are awesome, but they're outside the scope of this course. Now, the big question is, When do you use exception handling? What determines if my code should be in a try and except block or just not be in it? That's a really good question. And in reality, it depends on you, the developer. There are some places where you should probably always have an exception handler like when writing to a file, but, otherwise, it really depends on you. If you feel that a certain piece of code has a tendency to fail, this more often than not happens when dealing with user input, then wrap it inside a try and except block. As you improve yourself as a developer, so will your knowledge of when to use try and except blocks and when not to.
Other Data Types
At the beginning of this module, I said how you don't really have to worry about types in Python as much as you have to in some other languages. We went over some types like integers, strings, and floats. There are more types in Python that I did not mention that can be useful but are a little bit more intermediate level. I'll give you a brief overview of them. For numbers, we went over integers and floats. We also have a type called complex, which denotes complex numbers. And Python 2 had a type called long, which doesn't exist in Python 3 anymore. It was replaced by the integer. Then in Python 3 at least, we have bytes, which are essentially a sequence of integers in the range of 0 to 255, a sequence of strings or other objects, etc. There's also bytearray, which is similar to bytes. We also have tuples, which are similar to lists but are immutable. You cannot change their values. Finally, we also have sets and frozensets, which are again similar to lists, but they only have unique objects. So one neat way of using sets is to get rid of any duplicate elements in a list. If you had a list with values 3, 2, 3, 1, and 5, if we converted that list to a set, we see that not only it got rid of the duplicates, but it also ordered the list. From my experience, the one you will see the most out of all of these is tuples, but that of course depends on the kind of work you do with Python.
Summary
We learned a lot in this module. I did my best to present really the foundational part of Python, basic data types, lists, dictionaries, loops, try and except blocks. We saw how we can use these individual pieces. These individual pieces are really what will help us create building blocks of Python. In our next module, we will talk about functions and classes and all the fun stuff, but they will all be using everything we learned in this module, the basic units of our Python program. I would like to tell you one thing though. Even though we went over loops and data types and everything else, that does not mean that I touched on every single thing there is to know about loops or data types. There are many more functions and many more uses for all of them. Heck, there's a Python module called itertools, which takes the iteration or loops the whole next level. But armed with this knowledge you have so far, you should be able to continue with the rest of the course where we actually start writing our app.
Functions, Files, Yield, and Lambda
Module Overview
Hello and welcome back! My name is Bo, and in this module, we will expand the knowledge of our individual code pieces and start working on building blocks. We will learn more about functions and their parameters, opening, reading, and writing to files, and even lambda expressions, which is a fancy name for what in reality is a function. But more excitedly, we will finally start working on our app. So what is our app going to be about?
Our App - PyStudentManager
So my mom is a biology teacher. She works at a small school, and at that school, she actually uses a pretty darn powerful Excel spreadsheet to enter student information like names, grades, absences, and other stuff. I always thought it was silly to use an Excel spreadsheet for something like that. But you know what? It has been working great for her. But I decided to help her and build an app where she can do the same things, so student names, grades, etc. I'd have it all be a web app. Unfortunately, I made this promise back in 2014. Yes, a great delivery on my side. But now I thought what better opportunity to build this app than when I'm teaching this course. Our app is going to be called PyStudentManager, boring but Pythonic name. We should be able to enter some student details like name, Id, and feedback, and make it easy for us to expand it later on so that we can add more features like grades, absences, etc. Initially, this is going to be a console app, but we will later convert it to a web app. I have not built this app yet. I too am starting from scratch. We will work on this app together as we go along the course, try to make it so that when we learn functions, we add functions to our app and see how that helps us, the developers, and the clients as well. So let's get rolling.
Functions
A function is a block of organized and reusable code that is used to perform a certain action. Python comes with lots of built-in functions, some of which we have seen already like print, but of course, we can define our own functions. Usually you will want to strive so that one function does exactly one thing. If you have a single function that does multiple or, worse yet, unrelated things, that's generally a sign that you may need to rethink the design of that function itself perhaps by splitting that one function and making two functions out of it. As I've said, we've already seen some of the Python's built-in functions, most notably the print one. But there are many, many others. For example, if we wanted to allow the user to enter certain text and then store that text in our program, we would use the built-in input function. Note that this is raw_input in Python 2. Now remember the str, int, and dot format for strings? Those are all functions too. This is what functions can look like in Python. The first thing to note here is the def keyword. This we use to indicate to Python that we are defining a function. Then we have the function name like get_students_titlecase, print_students_titlecase, and add_student. After that we open and close parentheses. Finally, we add a colon at the end, and then we start the function body. This is the code that's going to get executed when we call the function. There are a couple more things that I would like to point out. Notice that in our first function, we have return students_titlecase, but what does this return do? Usually when we call a function, our code looks something like this. So let's write it down. And you see that I have auto-completion in PyCharm as well, so I'm just going to hit the Tab key. Now in this case, we want our student_list variable that we just introduced to have the values that the get_students_titlecase function is going to return to us or give us back. So we expect the get_students_titlecase function to do some work and then give us back the result of that work. You may be thinking, How is this different from the second function, the print_students_titlecase? It's different because the print_students_titlecase function will do just that, print the student list. And that's it. There is very little else we can do about it. In comparison when we retrieve the data from the get_students_titlecase, then we can submit it for further processing. Maybe we want to add some special characters or slice the list or something completely different. So if we introduce a variable and assign it to our print_students_titlecase function, like how we did here, this variable will not contain any data. It will not refer to our student_list, whereas if we return the data, then the variable will contain that data that comes out of the function. The other thing I wanted to point out is the part inside the parentheses in our last function, the add_student function. This is called a function argument or a parameter. There is a slight difference between the two but nothing you should worry about right now. This is some data that we can pass to our function at the time when we are calling it. Note that many times you will be calling a function from another function, so it is useful to pass the data around. In our case, we would call this function like this, add_student, and then we would pass the name like Mark. This will then add Mark to the global list of our students. We'll see how this can be useful later when we are prompting the user to input a student name. A function can have 0, 1, many, or a variable number of arguments and even optional and default arguments. We'll take a look at them in the next video. One of the things that functions are good for is decreasing code repetition. Do you see some repetition here? Let's examine this a bit. So our print_students_titlecase function really just needs a list with all the student names and titlecase, and that is already provided by the get_students_titlecase function. So then we can get rid of our code here and then just have a new variable called student_titlecase and have it call the get_students_titlecase. I made a typo here. Fix that, and we're good. Now, remember, this get_students_titlecase function returns a value, so our students_titlecase variable here is going to contain a list of all students and titlecase. Then we just print that variable. Another thing that functions are good for is when it comes to unit testing, it is much easier to test a single unit of code, which can be a function, than a giant morphic blob of code that spans hundreds of lines and has no clear organization. So these function arguments are pretty interesting. Let's take a look at them.
Function Arguments
Adding arguments to functions involves us adding some variable names within the parentheses in the function header. This is the part where we have the def keyword or function name and our arguments. Then we can reuse those variables from within our function. Those variables will be scoped just to the inside of our function or, rather, our function body. So in the case of add_student(name), we cannot use this name variable outside of the add_student function. So if I tried to do something like this, you can see that we have a red squiggly line underneath. And if I hover over it in PyCharm, it says Unresolved reference 'name.' Let me delete this now. Adding parameters or arguments is extremely useful because more often than not, your function is going to depend on some external data. Rather than making that data global and available to every single piece of your code in your program, which is a headache in itself as we will see in the next module, we can simply pass the data around and have the function use it. When you have defined the function with an argument such as name, calling that function now requires you to pass some data as that argument. So if we have two arguments when we call the function, we have to pass two arguments as well. Otherwise, an exception will be thrown, and our code will break. So let's modify our add_student function a little bit and add something like student_id. It will also be a requirement. And now if I hover over our add_student call, you can see that PyCharm is telling you Parameter 'student_id' unfilled. So I'd have to modify this and pass some Id, let's say, 332. But what if we wanted to define a function that takes a student name and also takes the student Id, but we want to make the student Id be optional, i.e. if the user supplies the student Id, that's great. But if not, we want to create our own student Id. Now that's where the default arguments come in. Let's make a small change to our add_student function declaration and add the equal sign at the end of our student_id and put something like 332. Now I can remove the 332 from our call. And if you notice, PyCharm does no longer have the Parameter 'student_id' unfilled. What we are doing here is telling Python to assume the value of 332 for the student_id argument if it is not passed, though normally you would want the student_id to be unique and probably automatically generated as well. So now if we use the function like add_student("Mark") like we have here, the student_id will default to 332. However, if we call the function with add_student("Mark") and then we pass 15 here, the default value of 332 will be overwritten, and the student_id will then be 15. Now we're not doing anything with the student_id in our function, so let's change that a little bit. Let's declare a new dictionary called student and have the name be equal to name and the student_id be equal to student_id. Then in that case, we can append the entire dictionary to our students list. Python also supports something called named arguments, which can be useful especially when dealing with a function that takes a lot of arguments. The same way we call add_student("Mark", 15), we can also get the same result by calling add_student and then having name set to equal and then also student_id set equal to 15. This sometimes makes it clearer to us as developers exactly what it is we are doing. This is also very useful when you are dealing with third-party libraries and APIs, and you're telling it specifically what you want it to do. But here's one interesting part. Did you know that we can call the print function and pass multiple arguments to it? So we can do something like print "Hello". But we can also do something like print "Hello", "World" as the second argument or print "Hello", "World", 3, None, "Something". How does this happen? They don't have a print function defined for every single number of arguments, do they? No, of course they don't. This is where a variable number of arguments come into play. A variable number of arguments allow you to place a special argument, usually called args, with an asterisk before it. The function would look something like this. Let's actually create a new function just for demonstration purposes. And I'm going to call it var_args for variable number of arguments. And let's for again demonstration purposes pass it a regular argument like name, and then we would define this args as *args. The only thing we want this function to do is print our arguments. Let's have it on two separate lines. When you want to refer to the args inside of your function body, you do not use the asterisk. You just use the args. Now I want to follow the PEP8 standard, and it's expecting two blank lines between the functions. I'm going to add the blank line. And then instead of this print here, I'm going to replace it with our var_args call. Let's again pass it Mark as the name, and then we can pass it something like Loves Python, None, Hello, again, any number of arguments that you want. Let's add a Boolean as well. So now if I run this, it is going to add a student and do all the expected things. And it's also going to print everything in the var_args. I'll right-click and click Run 'functions,' you can see that I got Mark as our first argument, name, and then the rest of it is Loves Python, None, Hello, and True. Now notice that the Mark is defined as the name because it's the first argument in our list. But all the other ones are considered args. Now this is very useful, but it is still a list, and you'll probably end up sifting through the data trying to find out exactly which argument you need inside of your function. Wouldn't it be better if we had a way to have as many arguments as we wanted but make them all have their names like name="Mark" and description="Loves Python"? Well you're in luck because we can do that with keyword arguments. Similar to args, keyword arguments are defined with a double asterisk and then the kwargs. Remember it as keyword arguments. Again, we can access this kwargs inside the body of our function, only now it is a dictionary, not a list. And the keys of the dictionary are the names of the arguments we passed. Let's take a look at this. So let me replace this args call with the **kwargs, and we're going to have to modify our code here. Let me delete this for now. And we can set something like description="Loves Python", let's say feedback=None just yet, and Pluralsight_subscriber=True. Now the way we access these arguments in our function body is going to be something like this. And similarly you're not going to use the double asterisks when you're referring to kwargs inside the function. Let's just do this for now. Run the code. And you can see that we still have Mark as our name, and then Loves Python and None. So we access this description because we pass a description in our function call. We access the feedback key because we pass feedback in the function call. Pretty cool, right? You may be thinking now, When am I ever going to use this? Well at a beginner level, you probably won't a whole lot. But I have found that when you reach that point when you do need to use it, you'll be extremely grateful that Python provides you with such a flexible way to handle a variable number of arguments in a function. Let's update our app now so that an end user can add students to it.
Adding Students to Our App
Our app lacks one crucial functionality. I mean we don't want to add students programmatically, we want to allow the user to input the student name and the student Id and have that be a part of our students list. Let's update our code a bit. Let me delete this demonstration var_args function and the call to it of course. Let's also delete the add_student function call as well. So we have a list of student dictionaries or a list of students. In order to allow the user to input the data, we should use the Python's built-in input function. If you're on Python 2, use raw_input instead. Using it is pretty straightforward. So let's define student_name. Same for the student_id. Add the student, and then call the print_students_titlecase function that we have defined earlier. Now let's run this code. The Enter student name: we have defined in our input function is simply the prompt in the console that instructs the user about what to do. Note that the code execution stops while we're waiting for the user input. After the user inputs the student name and the student Id and presses the Enter key, we call the add_student method and pass it the student name and the student id we just obtained. To verify that it all worked, we call the print_students_titlecase function that is going to give us all the students that we have in our program. Then we just exit the program. Let's try this and see if it works. I'll input the student name first, so let's put something like James in this case. And the student Id, let's make it 456. And it doesn't work. We get an exception. Why is that? Let's look at this stack trace. It says AttributeError: 'dict' object has no attribute 'title'. Now where does that happen? Well in PyCharm, you can click, and you can see that line 7 it says it doesn't have attribute title, which looks like this, but what's going on here? Recall that in our get_students_titlecase function, we iterate through a list and then update the string name in the list to titlecase. Oh, a string name. But we don't just have a string name anymore. We now add an entire dictionary to our list, not just a simple string. So in our get_students_titlecase, the student now refers to our entire dictionary, the same one we added in our add_student call. The fix is pretty simple. So let's just add square brackets here and type "name". There, that should work. Let's try this again. So I'm going to put James again, all lowercase on purpose, and then 456 was the Id. When I hit Enter, you see that we have James, capital J. Now why is it just James? That's because we are just getting the student name as the titlecase from our get_students_titlecase or our print_students_titlecase function. This is also a good reminder about when to put your code in a try and except block. As you could tell, my code broke, and the entire program just failed. Imagine if this were a more complicated application that you are selling to your clients. I'm sure they wouldn't be happy. Now, obviously, this leaves a lot of room for improvement. We're not saving the data anywhere so when we restart the program, we end up with an empty student list again. Also, we don't do any kind of validation of the user's input, so they can just add empty student names or a number of a student name, etc. But that's a story for another video. I am going to give you a little bit of homework right now. I want you to come up with a way to ask the user if they want to add a student or not. If they type Yes, you will add student to our students list and keep doing that until they type No, at which point you will print all the students in the list and then simply exit the program. Feel free to ask questions in the discussions part of this course.
Nested Functions and Closures
Just to relax our brains a little bit, here comes a pretty short video. You can next a function within a function if you ever have a need to do that. So this is perfectly legal code. It may look a little bit complicated, but in reality, it's just our get_students_titlecase code from the previous video with some code sprinkled on top of that. Sometimes you want to delegate a certain task that is only going to be used in one function. You don't want to pollute the rest of your code base with that task, so you simply declare an inner or a nested function within an existing function and then call it from the outer function. One interesting tidbit here is that the nested function does have access to the variables that are defined in the outer function. We see that our nested get_students_titlecase directly accesses a variable that is defined outside of it, the students list. This is called a closure, and it allows for better interoperability between a nested and an outer function.
Opening, Reading, and Writing Files
So one of the disadvantages of our PyStudentManager app so far is the fact that it loses all of its data once we restart the app. It would be best if we could save that data, so our student names, to a file every time we add a student. And then every time the app is started, we read that file and automatically populate our students list with the names of our students. Let's add a couple of functions to our program. In fact, I'm going to paste those two functions. So what the heck is going on here? Well on the first line of our save_file code, we're opening a file called students.txt. This is the first argument to the Python's built-in open function. This file does not have to exist because we have this 'a' as the second argument. The 'a' denotes that we want to append some text to this file. This entire second argument is called the access mode. There are many access modes in Python including writing, reading, appending, writing and reading binary files, combination of the two, and so on. One big thing to note here, if you open a file in write mode or 'w', it is going to automatically overwrite anything that is already in that file. If you simply want to add more text to an existing file or a new file, open it in append mode. Use the letter 'a' like we do right here. On the second line, we are actually writing to our file. We use the write function, which takes a string and writes it to a file. We also add the carriage return, \n\, so that ever student name we write is written to a new line in the file. This will make it easy for us to parse it later. Finally on the third line, we close the file. We must close files in order to prevent any memory leaks and to indicate to the operating system that we're currently done working with that file. Many operating systems put a lock so that if a file is already open, it cannot be opened again even from within the same program. The read file function is somewhat similar, the only difference being that now we are opening the file in the read or 'r' mode and that we are calling the readlines function. This function will read every individual line in the file and create a list out of them. Using write with the carriage return earlier, we wrote one name per line, so readlines is going to give us one name per line as a list. We iterate over this list of student names and then call the add_student function for every one of them. One thing I'd like to say before we proceed here is that this is just going to save student names to a file as a list of strings, not the student Id's and not our student dictionary. That's only because it is a little bit more involved to save a dictionary to a file in Python. It involves a Python module called pickle. We haven't learned modules yet, and that's coming up, so I don't want to introduce the unnecessary complexity in our code at this point. Let's just save names alone for now. Now we can integrate these two functions with the rest of our code. So first of all, let me actually delete this student_list variable, and let's delete this print_students_titlecase function call at the end. Now the first thing the user wants to see when they start the app is the list of current students that we have in our students.txt file. So in order to obtain that, we are going to call the read_file function. After that we can call the print_students_titlecase function so that the students names are actually outputted on the screen. Now in order to save new students to our file, we can call the save_file function and pass the student_name that the user has inputted after our add_student function call. Let's test this out and see if it works. Let me run the code. And when we run this program, we see that there are two lines of text that show up before our Enter student name: prompt. The first one is Could not read file. Now notice that this is the same text that I put in our exception block of our read_file function. Remember that in the exception video in the last module, I talked about how it is always a good idea to wrap any file manipulation in the try and except block because you never know what might happen. Now in this case, in the save_file function, the access mode 'a' does not require a file to exist. But in the read_file function, the access mode 'r' does require a file to exist. So Python is looking for the students.txt, cannot find it, and then raises an exception. However, since we handled the exception, our program does not crash, and it continues to execute. We also have these empty square brackets. That's just the empty list being printed form our print_students_titlecase. Now let's enter the student name, and let's enter some Id, and our program exited. But if you look to the left, you see that we have students.txt file, and you see that it has Mark. We can open this in any text editor that you want. Let's restart the program one more time. And now if we run functions, you see that we no longer have the exception message because our file is there and that we do have Mark because his name is already located in our students.txt. Let's also add James as a new student and put his Id. And the program exited again. Now if I open students.txt, we have Mark and James on two separate lines. Finally, let's run our code one more time. We just see James here. Well isn't that weird. Our students.txt has Mark and James, but our program is just printing James. Let's examine and see what's going on. So we read the file just fine and then print_students_titlecase, let's see that function. So print_students_titlecase gets the get_students_titlecase, so let's examine get_students_titlecase. There is the error. Now this was the honest error on my part. As you can see, I'm defining the students_titlecase as an empty list, but then when I'm iterating, I'm actually reassigning the value of the students_titlecase variable just to the name of the student and titlecase. I'm not adding this data to my existing list. Now this may be a good reminder also to talk about the types in Python. If I had done this in a language such as C# or Java, the editor would complain and say, Hey, you cannot do this because you have defined this as type list. But with Python, that's not the same. Python does not care about the type, so you can reassign variables to be any type you want. The fix for this is easy. We can just call the .append function here and append the titlecase for our student name. Now let's restart the program, and we can restart by clicking this green arrow here in PyCharm. And if I restart it, you see that although not beautiful, we have both Mark and James as a list of our students. Well this works. It's coming along very nicely so far even though we are fixing some bugs as we go. But I think in the end, this can be a very promising application.
Yield
So we looked at functions, and we looked at for loops in our previous module. If you recall, for a for loop to function properly, we need something to iterate over like a list or the range function. This would be an iterator. But notice how range is a function. How does one iterate through something that's a function? Don't we just return some value from a function, and then that's it, right? There's nothing else left. True, but there's also a way for us to use the yield keyword instead of return in order for us to create something called a generator function instead of a regular function. It is a little bit hard to explain, so let's take a look at an example. I created a new Python file, but we have our read_file function from the previous video. In it, we call the f.readline, which reads the file and returns every single line as a new element. That element, in our case it's just a student name, gets added to our list. But let's see if we can write this readlines function ourselves. So the requirement is to create a function that we can iterate over in a for loop and that is going to return a new element or a new line for our list. Let's try something like this. Let me add some space for us and create a new function called read_students. And it's going to want that file that we open. So we need to iterate over that file. We can do something like this, and then instead of saying return, let's yield line instead. Now one thing left for us to do is replace this f.readlines with our own function call and pass to the file. Now let's run this program and see what happens. Notice that we got the same results that we got in our previous video when we used the built-in readlines function. So we created a new function called the read_students. All it really does is it iterates over the file and then yields a single line from that file. Now this still looks a bit confusing. We have two for loops. Well that's because when we use the for loop in our read_students generator, it iterates over the file that we pass it as a function argument 'f'. But during every iteration, our read_students generator function yields a single line from that file to its caller, the read_file function. It will keep doing that until it runs out of things to return or yield. But we also have to use our for student in read_students loop in order to continue executing our generator causing it to yield other results meaning the other lines in our file. So in essence, we have two for loops because (a) we want to iterate over the file on our hard drive and, (b) we want to go through all those results and add them to our list one by one. And with this, we get the desired result. Still confused? Don't worry. Generators do take some time to understand and master. And if it makes you feel any better, in my years as a Python developer, I've very rarely had to use the yield statement or create my own generators. But that obviously depends on what kind of work you do with Python. Let's look at something a bit more cool, the lambda functions.
Lambda Functions
Lambda functions, a fancy name that automatically triggers the sensation of, I will never learn this because it sounds too complicated, in many of us. In reality, all lambda expressions or functions really are are functions. They're just anonymous functions that really have a function name or the def keyword we used to define our functions. They're very short, one line of code. And they can take arguments. Let's take a look. Suppose we have a number, and we want to double it. That's it. Pass a number as a function parameter and multiply by 2. What we can do is this. Simple, right? Converting to a lambda function, it's even simpler. That's it. We still have our x as an argument, and we still have our one line of function body, X * 2. We called both using the same way, double(5), which will get us 10. And they're interchangeable. It is not possible for you to be required to write some lambda function instead of a regular function. That's really all there is to it when it comes to lambda functions. Simple one-line functions that try to save you some space and time. So when are they useful? They're useful in something called higher-order functions, which are regular functions in Python that actually take another function as their argument. That's a little bit beyond the level of this course, but one of those functions is a built-in filter function, which can filter elements from a list. In order to tell it how to filter them, we need to pass it one argument, which is a function, lambda or otherwise, and do the filtering algorithm in it.
Summary
We are at the end of our functions module, and now we know the building blocks of Python. We talked about functions and its cool perks such as the variable number of arguments, named arguments, opening, reading, and writing files, lambda functions, generators, and yield. We did go through a lot of material. In fact, we have our app functioning to a certain extent at this point. In the next module, we will keep extending our app. So now it's time for us to learn more about object-oriented programming. We'll see the awesomeness of classes, what all hidden features they have, their special methods, see when it is a good idea to use classes and when not to, and also take a look at Python packages or modules including how to create our own modules.
Object Oriented Programming - Classes and Why Do We Need Them?
Module Overview
Hi again! Welcome back to the Getting Started with Python course. My name is Bo Milanovich, and in this module we are going to learn one of the main components of Python--classes. We will unravel the power of classes, take a look at inheritance, their special methods. And we'll try to answer one very important question, Do we really need classes in Python? We will also take a look at Python modules or, rather, how we can split or wrap into multiple files for easier organization of our program. Throughout this module, we will continue working on our PyStudentManager app and, again, continue to employ the newest techniques of programming we learned. In the end, we will have a refactored PyStudentManager app that is going to be using classes rather than just functions. Alright, let's start.
Classes and Why Do We Need Them?
We have reached the point when it is time for us to talk about classes in Python. I'm sure that you have heard and possibly used classes before. Some languages like Java even require you to write code within a class. Python does not make such a requirement. You can write a code inside a class but you don't have to. The choice is yours. Nevertheless, there is still a very important part of object-oriented design, and Python is definitely an object-oriented language. In Python, we define a class using a class keyword similar to how we used the def keyword for defining a function. So what are they exactly? Well you can think of classes as a logical group of functions and data. The functions in a class are called methods, but they're the same things as functions, just a different name. So what is this logical group of data I speak of? Let's take our student example. Each one of these students contains some student details like name or student Id, and we can, of course, expand to add more to it. Now all those student details are stored in a dictionary. We also have some functions such as add_student. But we want to add the possibility to calculate the student's GPA, get all the student's absences, get student's enrollment information. Did you notice a word I kept repeating now? Student. Everything I mentioned relates to a student. So in this case, we can have a Student be our class. Then we can add functions or methods to our Student class called calculate_gpa, get_absences, etc. Moreover, we can have class attributes, which is essentially data for a particular class like student_name, student_id, and so on and so forth. So what are the advantages of using a Student class instead of a dictionary? Well classes allow you to write more readable and more maintainable code. Imagine if you had a larger application that not only dealt with students but also teachers, staff, maintenance personnel, work projects inside a school. It can be significantly easier dealing with a program that puts all the student-related information in one class or one object called Student. Compare that with just having functions all over the place performing the same task but passing the data around and, in the end, adding it all to this dictionary that has a variable name Student, and you're never really sure where the function is or what it really does. Are you sure it's just used for students or possibly someone else? Moreover, when we create our own custom class, we can force certain behavior. For example, we can prohibit the user from creating a new student if they don't supply a name. Granted, this was possible with functions as well but in a little bit uglier way. As you probably guessed by now, defining a class in Python was easy. You simply write the class keyword followed by the name of your class. After the mandatory colon at the end, you would write your class body, so all your methods and attributes or, in other words, functions and variables that are related to this class. When we want to create a new student, we create a new instance of a class. We can create many new instances, and each one of them will be independent of the other. So the first instance of a student we created is completely independent of the second instance of the student we created, meaning, they're different. Think of a class just as a blueprint for our building block and the instances of the class are the actual building blocks. So they all follow the same patterns and designs and the rules, but they're actually different. They can have different student names, Id's, and all the other different information. They do not depend on each other. Like I said, classes support methods, which, again, are pretty much the same thing as functions. But classes add something more on top of that. They have a nice trick up their sleeve. Suppose that we want to add a new student as a dictionary to our students. Well we can do that just fine, but one thing to note here, and I've mentioned this before, is that there is nothing requiring us from adding a student Id or a student name. That's just a choice we make when we're adding a new student to a dictionary. Then we worked our way around that issue by adding a function that requires us to pass a parameter in order for it to create a dictionary and add it to our student list. But we can just as easily create a dictionary of a student ourselves without the name or the Id and just add it to that same list. Notice how in here I have a string inside the parentheses when creating a new instance of our student class. In the previous slide, we saw that in order to create new instances, I just had empty parentheses. So then what is this? With classes, we can use something called special methods in order to try to solve this problem we have with requiring student name. Special methods are only available in classes. There are many special methods, but one of them is called a constructor method. This method is going to be called every time we create a new instance of our class automatically. By default, it is hidden from us, but we can define it or override it and provide our own custom implementation. This method is like any function so we can also require parameters to be passed like name and student Id. But the added advantage here is that if we require these parameters, there is no way for us to create a student using a class if we don't supply these parameters in the first place. So our student names, we find the string inside of the parentheses, so Mark, James, and Jessica, will be passed as parameters to the special method called constructor. Confused? Don't worry. There's a whole video dedicated just to special methods. Enough talk. Let's go and see some code.
Defining a Class in Python
Let's take a look and see what a Python class looks like. So we're back to PyCharm, and I created a new file called classes.py. And I'm going to write a simplest as possible class. Let's call it Student, and we just type pass. Now pass is a keyword in Python that just tells the interpreter do nothing. You can use it for functions too. It's very useful when defining some placeholder functions or classes. Anyway, our class is pretty useless right now as it really does not do anything. But we'll build on top of it and add features and data later. Whatever code we put inside of our class is instructions on how to build that class or how to create instances of that class. To create a new instance of a class in Python, what we do is what we've always done, define a variable name and set it equal to Student with the parentheses at the end. Note that the Python convention wants us to use the capital first letter when defining classes but lowercase letter for functions and variables. Since Python is a case-sensitive language, having these two have the same variable name only with different casing essentially is just fine, although you may want to avoid it, and we'll see why soon. What can we do with this instance of our class? Let's print and see what happens. I have to run the code now. So we got something a little bit odd here. Why didn't we get a prettier name or maybe even student's name when we add it? We'll get to that later. It is possible. For now, Python is just telling us that it is holding a reference to a student object at this location in the memory. Now let's see what happens when I create another instance of the same class. So let's just call it new_student and point to the same class, and then let's print the new_student. Run the program again. So with these two both printed, the results are almost the same except for this hex code we have here at the end. There's a slight difference. This is telling us that these two objects are at two different locations in memory, meaning that they're two different things altogether. We talked about this in the previous video. Two instances of the same class are independent of each other. This class is boring, no pun intended, so let's add some methods here.
Adding Methods to Our Class
One of the main features of our app is adding students, right? Let's implement that inside of our class. In fact, we already have that function defined in our functions.py file from the previous module and already selected, and I'm just going to copy/paste our add_student function, paste it inside of our class, and replace this pass part. Now we're missing students, which is a list of all our students, so let's add that on the top of the file. Now we have an empty list. And now I'm going to add a parameter here called self, which I will explain in just a minute. Also, let's get rid of all this that we had in our previous video. So the self part is the first argument in the add_student method, and it refers to the instance of the class. Now this is not an argument that you have to pass when calling the method. It is going to be automatically inferred from the instance itself. Using self is the way we refer to our class from our class. So if we wanted to call add_student method again from inside of our class, we would call it as self.add_student. In fact, if you like recursion, you can have something like self.add_student, and of course, in this case, you would have to pass it the name and optionally the student_id argument. Let's get rid of this. This behavior is something that's a little bit unique to Python. But try to almost think of it as this in other programming languages. Add to that the fact that self is going to be the first parameter of a method in your class. But in order to use this method outside of the class, we need to create a new instance. We already did that in our last video, so let's do it again. Then from the student variable we just introduced, we can call the add_student just like we called it before in our last module when we just had a function. So let's do that. And then let's pass the good old name of Mark. Now in order to verify and see if this worked, let's print the students list and run the program. And we see that we have Mark with a default student_id of 332 defined in our list. Now you can add as many methods as you want to a class, although it's probably the best idea to not make your class gigantic. Try to keep your code readable. There's also something called static and class methods in Python which don't require you to create a new instance of a class, but they're a bit more intermediate level so not for this course necessarily. One thing that's bothering me about this class is, well, I don't want to have to call add_student every time I want to add a new student. I mean I'm already creating a new instance of student. Can't I just pass it the name and the student Id at that time? Of course you can with the constructor method.
Constructor and Other Special Methods
A constructor method is a special method in Python classes that gets executed every time you create a new instance of a class. So when you have code like the one that's highlighted on your screen right now, in the background the constructor method will get called. We have not defined the constructor method yet. By default, it is provided to us, but it is hidden. We can, however, create our own and customize the class instantiation behavior. For our Student class, we want the capability to immediately add the name and student Id as soon as we create a new instance of our Student class, so do the same thing as if we were calling add_student. We can do that by providing our class with a constructor method and requiring us to provide it with parameters when we create a new instance of our class. Since we're replacing the same functionality that we already have in our add_student, the only thing I'm going to do here really is actually rename our add_student method. Let me rename it quickly here. And let's quickly get rid of these two since we no longer have the add_student. The constructor method in Python is this odd-looking fellow called init. Try to remember it as initialization. It has double underscores on both ends of the method name, and this is fairly common with other special methods. We'll see one more in this video. Now notice that the only change I made is the method name. We no longer have the add_student method, but we have this init method. But what this change allows us to do is create a custom constructor. Now when we're creating a new instance of a class, we must pass the name parameter and optionally the student_id parameter. Let's take a look at see what happens if we create a new instance like we did before without the parameters. I'm going to type student = Student without any parameters. And you see that the PyCharm highlights this and says Parameter 'name' unfilled. Either way, let's run the program and see what happens. We get a pretty clear error. Init is missing 1 required positional argument: 'name'. Let's fix that. So in order to pass an argument to the constructor method, we put that argument inside the parentheses when we are doing our class initialization. So for name, I would put Mark here. And actually let me rename this variable from student to mark because we are creating Mark who is a student after all. Now let's run the program again and see what happens. And we see that we correctly have a student called Mark with the default student_id of 332. One point of confusion that I have noticed with some developers is the fact that the parameters that we pass here when we're creating a new instance of our class are directly tied to our init method. Later on when we talk about inheritance and polymorphism, you will see parentheses after the class, Student, itself, and inside that, we will have some references as well. Those are not at all related to us creating a new instance of that class. But we'll see more about that later. One thing is still there though. If I type print(mark) now, I still get that ugly memory reference we saw in the previous video. Can't I get something like Student? Yes, and for that we use another special method called str, which is short for string. So let's add it now. I'll use the auto-complete. And in this case I'm just going to return Student. Now str is a method override. We'll learn more about overrides when we talk about inheritance and polymorphism. But for now in PyCharm, you can notice this little blue circle. And if I hover over it, it says it overrides method in object. Some other languages will use the override decorator or the override keyword, but Python doesn't have that. Now let's print("mark") and see what happens in this case. And I'm going to delete this print("students") for now, run our program, and we see that instead of our ugly memory reference, we get actually the text that we wrote here in this str method or just Student. There's one big thing still bothering me about this class though. Sure we have a student class, and it's nice that we can add student name and Id immediately when we create an instance of it, but we still depend on the student dictionary here, so we haven't really solved the problem we talked about earlier about manually modifying the dictionary itself, right? Is there a way for a class to be aware of its own attributes, meaning can this Student class know its own name and its own student Id? Of course it can. That's where the class attributes come in.
Instance and Class Attributes
Class and instance attributes are simply data that can be found in your class like a string, an integer, or really anything, and that can be accessed from all the methods inside your class. If you have two methods such as the special constructor method, which requires us to add the student's name, and also a get_name method which returns us name alone, you would use something called a class or an instance attribute. There is a difference between the two of them, so let's take a look. We have our class here from the previous video, and I'm going to make some modifications to it so bear with me for just one minute. And I'm also going to add one more method called get_name_capitalize and have it just return a capitalized version of our student name. So what did I just do? First of all, notice that the student dictionary in our constructor method is gone. Finally, we got rid of that dictionary. But what I do have now are these two lines of code which are setting the name and the student Id variables to the ones we obtained from the method argument. Recall that the self refers to the instance of our class. Therefore, when we set the name and the student Id for our entire instance, those two variables will be available throughout the entire instance including any methods that we have. That's why we can use the self.name in our get_name_capitalize method because that method will be aware of all our instance attributes including the name. At the end using self again, I add the entire instance of our class to our student list. Now our student list is going to be a list of all student classes with all the methods and attributes it carries. Let's do one more cool trick. Our str method is also part of our instance, so I can do something like this. Let me add an empty space here. Remember that the str method is going to be called every time we print an instance of our class. So in this case since the str method is aware of the student name, it's going to include the name when we try to print it. In fact, let's run this code and see how it looks. You can see that now we have the name listed as well when we print the instance of our class. Let me hide this. Now there are generally two ways to set instance attributes. One is using the constructor method like what we have here, and the other is using setters. So you can have a method called set_age or set_student_id or anything, and that is going to set the instance attribute for our particular student. Remember how I said that every instance is different from the next. The same applies here. If you create two instances of our Student class, they both have a unique name, age, or whatever. But what if we always wanted to have some class attribute be the same across all students? What if we really did not care about different instances? We simply want some data to be the same across all instances. Let's say all of our students are going to the same school. How do we do that? In that case, we can use something called class attributes. Class attributes are very similar to instance attributes with the difference being that they're usually not defined in a method, and they're not tied to self or the instance. They are static. They do not change with the instance. To define them, simply put them outside any of the method bodies in your class, but inside your class body. So let's use our school_name example. I'm going to define a variable inside of my class body, and let's call it Springfield Elementary of course. Now the school_name is an example of a static variable or a class attribute. You can refer to these static variables from your methods just fine. But in that case, you would need to add self before referring to them. Let's take a look at an example. So I can define a new method called get_school_name, and I can just return self.school_name. Why is it like this though? Why do I need self when this is not an instance attribute? Suffice it to say that there's a whole chapter out there on static variables, static methods, class methods, and so on that is unfortunately outside the scope of this course. But I can tell you that there is one more way that you can refer to this school name outside of an instance. So let's comment out our mark instance creation over here and printing mark. And another way that we can print the school_name is by calling it directly from our class, not our instance. So we could do something like Student.school_name. Notice that we do not have empty parentheses here. We're not creating a new instance of our Student class. We're just calling it directly. That's because the school_name is not an instance attribute, it's a class attribute. It does not care about the instance. Let's run this code to see if it works. And sure enough, we get Springfield Elementary. Now let's take a look at one more very powerful feature of classes, inheritance and polymorphism.
Inheritance and Polymorphism
Inheritance and polymorphism--they sound like big words, but in reality what they mean is that when you have two classes defined, you can kind of tie them together. I feel, though, that if I try to explain it using words, I'm probably just going to confuse you even more, so that's why I have PyCharm open with our code, and we have our student class like in our last video. But let's say we want to add a similar kind of student, maybe a high school student. This high school student is also going to require a name and a student Id, and we also want to have all these methods that we have for a regular student. Well one way we can do that is copy/paste our code and call our new class HighSchoolStudent, but copy/pasting code is rarely a good idea. So what if we could inherit the behavior of our student class and derive a new class out of it called HighSchoolStudent. We can do that. Let's call our new class HighSchoolStudent. First, let me get rid of these lines of code here. And in order to inherit a behavior from a specific class, what you need to do is at the end of your class name, open parenthesis and type the name of the class that you want to derive the behavior from. So in our case, that's Student. Within our class body, we can override the functions, class attributes, or instance attributes from our parent class, which is Student in our case, but we can also add new methods or new instance attributes and new class attributes. It doesn't really matter. Let's override the school_name, and you can see that PyCharm is offering me auto-completion because it knows that I'm deriving from the Student class. And in this case, we're going to call our school Springfield High School. Now recall when we defined the str method in our Student class, we have this blue icon here in PyCharm that says Overrides method in object. Similarly, if I hover over it at the bottom, it says Overrides attribute in Student. It's telling us that this is an override from a parent class. So just to make it clear, the Student class in this case is the parent class, whereas the HighSchoolStudent is the derived class. The awesome part about the derived class is that it still has access to all the methods and attributes from the parent class. So we can actually create a new instance of our HighSchoolStudent and call the get_name_capitalize method. Actually, let's do that. Let's create a new instance called james, and James is a HighSchoolStudent. Let me pass the name of James. Actually, let's pass the lowercase j so that we can see how it works. And then we can print directly james.get_name_capitalize. If I run the code right now, you see that it works just fine even though I have created a new instance of HighSchoolStudent that does not have the get_name_capitalize method defined. Let me hide this. So we can access the parent class methods, but can we override the behavior of those methods? Of course we can. So in order to do that, we simply define the same method that we have in our parent class and use it in our derived class. Let's override the get_school_name method in our HighSchoolStudent class. And you see again that PyCharm is offering auto-completion in this case. And what we want this method to do is just return the This is a High School student. Let's experiment with this a little bit. So we'll just replace the get_name_capitalize with get_school_name. And now if I run this code, you see that we get the This is a High School student. No mention of the school_name, Springfield High School or Springfield Elementary anywhere. Now this is probably not the best example because the get_school_name should return the name of the school. But this is just for demonstration purposes. Sometimes, though, you will want to override the method but still execute the parent's method as well. We can do that by using the super keyword. For example, let's override the get_name_capitalize method. So what I need to do is define get_name_capitalize in our derived class. And we want to keep the behavior from the Student class but also add a suffix, -hs, to denote that it's a high school student. So we do want the original_value of get_name_capitalize, but we also want to modify it a bit. So let's call our variable original_value, which is very unoriginal. And then we can call the super().get_name_capitalize. The super keyword here refers to the parent class or in our class Student class. So Python is going to look for the get_name_capitalize in our parent class, which is Student. Now we can modify this string and have it return original_value + "-HS". Let's go down, and let's print this get_name_capitalize and see if it works. If I run the code now, you see that we have James-HS, just like how we defined it here. That's all that inheritance and polymorphism really is. It allows you to inherit one class and kind of merge it with another. In Python, you can inherit from multiple classes, although you will want to be careful when doing that so that you don't have several methods with the same name clashing with each other. So when are you going to use inheritance? It's a good question. And in the beginning, you may not at all. I have found it useful to sometimes create this basic class that I'm going to derive my other classes from. In our case, we have a Student class, which could be a basic class, then we'll have a HighSchoolStudent that has all the properties that Student does plus some more. Down the road, we may add an ElementarySchoolStudent, which, again, has the same properties as Student, maybe a little bit modified, maybe something extra on the side. It does allow you to write cleaner code. If you're coming from other languages, it is worth noting that Python does not have access modifiers. So there's no private, public, or protected. All methods are public, and there's no real way to change that, although many Python developers use the underscore prefix for a method name to denote that a method should not be overridden or even used directly. There's also no override keyword or decorator. And there's no such thing as interfaces in Python either. Our code is becoming a bit too big for a single file. Well not really, but still let's try and see how we can separate our code across multiple files or Python modules and then import those modules into one main file.
Breaking Our App into Modules
Generally, you don't want to keep your entire app, especially if large enough, in a single Python file. You'll want to separate it into multiple files usually creating a single file for a logical group that may be a bit larger than a logical group for a class. For example, in our case, I have created three files-- student.py, hs_student.py, and main.py. Our student.py file is simply going to have our Student class. The same can be said for our hs_student.py file. It has our HighSchoolStudent class. And the main.py file is the file we're going to execute. And this is the file that creates a new instance of a HighSchoolStudent or a Student. So it needs to be made aware of both of those classes. If I open the main.py file right now, we see that we have the same code as before creating a new instance of a HighSchoolStudent, but we see that we have red squiggly lines. PyCharm is complaining, Unresolved reference 'HighSchoolStudent'. To solve this problem, we need to import HighSchoolStudent. Important basically allows you to use the code in one Python file, such as our main.py, that is defined in a different file like student.py or hs_student.py, in our case hs_student.py. Normally you would import classes from different files, but it's completely fine if you import functions, variables, whatever you want. Using import, you can also take advantage of many, many libraries, utilities, and frameworks that Python comes with and the third-party ones you download. We'll see how to do that in our next module. So we need to import HighSchoolStudent here. The import syntax is import hs_student. Now this is the name of the file. This still does not fix our problem. The PyCharm is still complaining about the unresolved reference. Well that's because in this case, we need to prefix it with hs_student, put a dot, and this is the way that we refer to the HighSchoolStudent now. You see that the squiggly lines are gone. Now, obviously, this is not always convenient since it may require you to rewrite some code. We'd rather go back to just HighSchoolStudent in this case, right? Luckily, there is a different syntax. We can do something like from hs_student import HighSchoolStudent. We see that once again the red squiggly lines are gone, and this code works. Additionally, you can use the asterisk at the end if you want to import everything from that file. So we may as well replace the HighSchoolStudent with an asterisk, and our code will work just fine. This may not always be a good idea, just so that you know exactly what you're importing. However, it is useful if you have multiple classes, functions, or whatever that you want to import. Very quickly you will start using Python modules or packages. You will probably start off by using Python's standard library or modules that come installed with Python such as os for dealing with operating systems like renaming folders, finding the path of a file, or datetime, a module that allows you to easily write the time and date, find the difference between two days, and so on. The Python standard library is very large, and it consists of many modules. But soon enough, you'll work with third-party modules as well, and this is something we're going to take a look at in our next module when we start building a web app.
Comments
So I've used them all over the place including the course slides and the code, but I never really bothered to explain them. I'm talking about comments. Single-line comments in Python start with the pound sign. Note that there are some PEP8 rules that you should follow, meaning that you will want to separate the pound sign and the first letter of your comment by an empty space. Also if you're adding a single line of comment at the end of the line of code, you will want to have two spaces, then a pound sign, then another space, then your comment. Obviously, you don't have to follow the PEP8 rules if you don't want to. But it's a good idea to do that just for the code consistency purposes. As far as multiline comments, Python does not have them. Or at least it does not have them in a way that many other languages do. In Python, a multiline comment looks like a string separated by three double quotes and not assigned to any variable name. When documenting your functions or classes, it is defined within the function or class body, not above them like in C# or Java. So the first lines of your code in your function or class body for documentation purposes should be the comment. We also have a kind of a syntax for function parameter names, their types, and a description that shows what they are and maybe how they'll be used inside that function.
Summary
So, do we really need classes? That's entirely up to you to decide. Here's a fun fact. Probably the most popular Python framework, Django, allows you to write web views using both classes and functions. You just have to pick. Do you want class-based views, or CBVs, or function-based views, FBVs, or even maybe a mixture of the two? Python is flexible enough to allow you any choice. Either way in this module, we learned a lot about classes from writing them to creating new instances, adding special methods, or writing behaviors in derived classes, and so on. I tend to use classes because I like that I can group my code under one umbrella and then override the behavior of that code in some other derived class. In our next module, we're going to convert our app into a web app using Flask, a web micro framework. And in that module, you will see how functions are being treated as first-class citizens.
Putting It All Together - Let’s Make It a Web App
Module Overview
One more time, welcome to the Getting Started with Python course. My name is Bo Milanovich, and in this module, we're going to convert our console app into a web app using a fairly popular Python web microframework called Flask with the goal of reusing most of our existing code. We will also learn about installing third-party packages using Python's pip and talk a little bit more about something called PyPI.
Installing Python Packages and Flask
There are many, many web frameworks made for the Python programming language. Probably the most popular one is Django. This is a web framework that powers a large number of famous websites such as Pinterest, Instagram, Mozilla Help pages, The Onion, Nasa. But there are also other frameworks such as CherryPy, web2py, Tornado, and, of course, Flask. Flask and Django try to solve the same problem but with a somewhat different approach. While Django is a wonderful out-of-the-box solution for everything, Flask takes a minimalistic approach and expects you, the developer, to address any requirements that you may have. For that reason, it is often easier to get started with Flask because it really just requires seven lines of code to serve a page. This is not the case with Django. So let me copy this code that we see on the Flask website and run it. You see that I have already created a web app folder, but that's for the next video. For now, let's just create a new Python file called test_webapp, and paste our code, and then just run this code. When running the code, you see that we are informed that we have a local host server set up on port 5000. Let me bring up Firefox, and open a new tab. And we see that we get the text Hello World! displayed on our page. Now if I go back to our PyCharm, you see that our hello function is returning Hello World! So as expected with these few lines of code, I get a simple Hello World! in my browser. But if you try to do the same thing on your computer, running this code would fail. Why? Because you don't have the Flask package installed. In order to use Flask on our machines, we need to first install it. Flask is a Python package, one of many available, and in order to install a Python package, we use a tool called pip, that's p-i-p. Pip is a package manager for Python similar to npm for Node or NuGet for .NET. If you have at least Python version 2.7.9 or 3.4 installed on your machine, then pip is already installed as well. Otherwise, installing pip is a one-line command as well. In order to use pip, you need to open the terminal or the PowerShell or command line. Now if you have both Python 2 and Python 3 on your machine, it is very likely that you will have the pip command for Python 2 and the pip3 command for Python 3. For this video, we need to install Flask, so we can just type pip install flask, exactly what we saw on the Flask website. After a few seconds, you have Flask installed on your machine. Awesome, right? Of course, we have an index of all the Python packages there are on the PyPI website. This stands for the Python Package Index. At the time of recording this course, there are over 100,000 Python packages available for download. And searching Flask up to the right here will give us Flask after a little bit of digging through the search results. Now the package detail page on PyPI contains useful information about the package such as the latest version, direct download links, and usually has a link to the website and documentation for that specific package. Now Flask deserves a course in itself, actually probably deserves more than just one course, so in our Getting Started course, we won't really delve inside Flask and see how it works. Rather, the goal of this module is to show how we can reuse our console application code and convert it to a web application. Let's start working on that now.
Making Our App a Web App
As I said, this is not a Flask course. For that reason, you will probably see some code in this file that you do not understand, like this @ symbol or the __name__ check at the bottom. Don't worry. Those are required in order for Flask to work correctly, and they're a bit more intermediate level, so we won't talk about them in this course. Now let's do a small crash course on web development. Each time you open a web page in your browser, your browser sends a request to the web server expecting a response. Flask will provide this response. This response can be anything from a text response like we saw in the previous video to JSON and even an entire HTML page. There are several types of web requests including a GET request generally used when you're fetching a web page and a POST request used when you're posting or sending some data to the web server like when submitting a form. Our students_page function will handle both the GET and the POST requests, which is why we need to inform our @ symbol-shaped friend that we expect both of these requests to hit this function. This is because this one function will deal with both displaying all the students in a table when the browser fetches the page and adding a new student to our students table when we fill out the add a student form on our web page. This fellow is called a function decorator, by the way. Inside this function, there's an if check to see if the incoming request is POST. And if so, we obtain the student Id and the student name from the form on that page or, rather, the requested form object, which is a dictionary. These dictionary keys correlate to the name attribute in our form fields in the HTML. Then we create a new student with the student name and the student Id and append that student to our students list. Now it's a very bad idea to store your persistent data inside a variable or, rather, inside your RAM. For this situation, a database would be perfect, but I did not want to introduce the unnecessary complexity at this point. At the end of our POST request handling, we make sure to redirect the user back to the same page they came from. This is considered good practice. Now outside of the POST request, so in our GET request handling, remember, we're just allowing GET and POST requests, we have one line of code instructing Flask to render index.html page and pass it the students variable. This students is going to be used inside of our HTML. Speaking of HTML, let's take a look at it. I already have it open, index.html, it's under the templates folder. Now this HTML is a very standard issue Bootstrap 3 theme with a table and a form and a jumbotron and what not. There is one piece of code that is somewhat unique that I would like to show you, and it's right here. In the students table, we have this odd syntax that looks like an iteration. That's because it is an iteration. We are iterating over the students list. This students is the same as the students we passed as a parameter to our render_template function earlier. Then we display the values found for each individual element in our student list, and in this case, our Student class. Flask can render regular HTML but also HTML that uses a template language called a Jinja2 allowing us to interject values and even perform some minimal logic inside of our HTML without even changing the file extension. Enough talk. Let's run this app finally and see if it works. You see that yet again we are running on the localhost server, the port 5000. Let me copy/paste this. I could've just refreshed here. It doesn't matter. And our lovely app is showing up, and it looks amazing, doesn't it? We see that we don't have any students in our All Students table, so let's add one and see what happens. I'll just put 1 for student Id. Let's put me as a student name, so Bo. And the last name is PythonBo. If I hit Submit, you see that the student Id, 1, and the first name, Bo, are there. Last name is not showing up though. Why? Well we'll see that in the next video. Now let's add another student and make sure that that works. Let's put some random Id number and then put the first name as Mark. We can omit the last name. Hit Submit. And now we see Mark added to our students table. But the biggest question here, and the whole point of this module, is to find out how much code did we share compared to our console application that we worked on earlier. Let's take a look at that.
How Much Code Did We Share?
We finished the previous video with the working PyStudentManager web app. Now let's dig into the code and see how much code is shared with the console app we built before. At the top of the app.py file, we import the Student class from our students file that we created earlier. So what exactly did I change inside this Student class? Let's take a look. Do you notice any differences? Neither do I. I did not change a single thing in our Student class, not a single thing. So how the heck does it work? Well remember the students list we passed to our Flask HTML template, the one that we iterate over? It generally expects a list value to be a dictionary, but we pass it instances of classes. However, what you should know is that instance attributes on classes become dictionary keys automatically. Moreover, if you want to customize how a dictionary is created out of a class, that too is possible in Python. But it's an advanced topic. The flexibility of the Python programming language is clearly visible here. While this is all fun and games, I have to warn you that it is not always going to be this simple. But then again how often will you be presented with a challenge of converting a console application to a web application? Still, I wanted to show you that this is definitely possible with Python. Now remember how in the last video, we did not have the last name show up in the All Students table. That's simply because our Student class does not even account for the last name. Instead of me making the change, how about you consider it your homework--modify the Student class so that it accounts for the last name as well.
Summary
In this relatively short module, we looked at several important aspects of Python programming. First, we saw how we can easily install third-party Python packages usings pip, the package manager for Python. Then using pip, we installed Flask, a microframework that allows us to serve web pages to the end user. Even though it's small, Flask is definitely used in production websites across the globe. But most importantly, we looked at code reusability. We converted our console application to a web application simply by adding the code that just deals with handling web requests. That's it. This is one of the reasons why I really like Python.
Python Tips and Tricks
Module Overview
Hello again. In this unique module, we're going to take a look at some interesting features of Python, as well as some tools that we can use to help us distribute our application. We will see how we can work with virtual environments, and they are amazing by the way, how we can debug our application using PyCharm, how we can create binary or executable files from our Python app, and by this I mean the .exe file for Windows for example, and even how we can create a setup wizard so that it is easier for the user to install our app. So let's begin.
Working with Virtual Environments
There is something in the Python world called a virtual environment. What is it exactly? Well, virtual environments allow you to set up independent Python environments when you're working on your Python application. Imagine you're working on a web app. The web app you're working on for your client uses Django 1.8 even though that's not the latest version, but it is the latest long-term support release. However, you also want to have Django 1.11 installed on your machine because you want to get your hands dirty, maybe check out that new admin interface. Either way, you cannot have both versions of Django installed on your machine. There's going to be a version conflict, so you can only have one of those two installed. But what you can do is simply create two isolated Python environments. In each one of these isolated environments you can install whatever packages you want completely independent of each other, and they both live on the same machine. Heck, you can even use the different Python interpreter versions as well. On my personal machine, I have a virtual environment with Python 3.6 for this course, then Python 3.5.1 for my personal web project because that's the Python version that my web server provider has. Then I have one for version 2.7 and so on. It is fairly common to have many virtual environments set up on your machine, especially if you're working on multiple applications. They each have their own packages, and they don't care about each other at all. Now this may all sound silly and unnecessary to you at this point, but when you start working on an even slightly more complicated Python application, you will really appreciate the power and ease of use of virtual environments. To start using a virtual environment, you first need to install the virtualenv package using pip. So just run pip and install virtualenv. From then on, the virtualenv command will be globally available in your terminal or PowerShell. Creating a virtual environment is super simple. Just type virtualenv and then followed by the environment name. The environment name can be whatever you want. For example, I created a virtualenv for this course called Pluralsight Getting Started. Now that's a mouthful. I usually name my virtual environments according to the project name. You can specify the Python version that you want your virtualenv to use by passing the --python flag and putting the path to your Python executable. Also, here's a tip: It's totally okay to keep all of your creative virtual environments in one folder. For example, I have a folder in my home directory called venvs. This is where all my of virtual environments live. This is perfectly fine. Finally, when you're done creating an environment, you need to activate it. When the environment was created, it created a folder with the same name as the virtual environment name and several subfolders. In order to activate the virtual environment, simply type source, then the path to your virtual environment folder, followed by the /bin/activate. If successful, your terminal or PowerShell prompt should be prefixed with the virtual environment name. Now when your virtual environment is activated, if you install packages using pip, it will only be installed to that specific virtual environment. It will not be available outside of it even in the global Python environment. Similarly, when you use the Python command directly, it will use the Python interpreter that you created for that virtual environment. To leave the virtual environment, simply type deactivate while your virtual environment is active. PyCharm is also able to work with virtual environments and can help you set them up as well. Simply search for the interpreter options in the PyCharm settings. Speaking of PyCharm, let's explore another great feature that it comes with, and that's debugging our Python code.
Debugging Python Code
Throughout the development process, it is incredibly important for us to be able to debug our program without necessarily printing every single thing to the console. One way we can do that in PyCharm and other IDEs is by setting breakpoints in our code and inspecting all the variables and the values that have been assigned to them. What is a breakpoint exactly? Well a breakpoint is a point in your code base, usually just a line, that when reached will pause the execution of your program and allow you to inspect your programming environment. Let's see it in action. We have our PyStudentManager web app here. And what I'm going to do is set a breakpoint at the new_student instance creation. I want to inspect to see if this new_student_last_name variable is getting assigned a value even though I'm not really using it anywhere. To set a breakpoint in PyCharm, simply click on this vertical bar next to the line number like so. Now I'm going to run the app in debug mode, which is very easy in PyCharm as well. Simply click on the Debug 'app' instead of the Run 'app'. We see that once again our web app is running on port 5000 on the localhost. So let's open this up in Firefox. Notice that nothing happened when we requested this page initially. That's because our breakpoint is set on the line of code that is inside the if condition that checks to see if it is a POST request or not. Remember, POST requests happen only when we are adding a new student. So let's try to add a new student and see what happens. Set the student Id as 821 for no reason whatsoever and put the first name as Bo and the last name as Python. Now once I click Submit, the PyCharm window shows up immediately. And in this debugger panel, we can see a list of all of our variables and their assigned values including the new_student_last_name. A couple of other cool things you can do here. First, you have these buttons here that allow you to step over, step into, and step out of the code. When you click on Step Over, what happens is that your code hits this invisible breakpoint at the next line of code enabling you to slowly step through your entire code base and see exactly where the error occurs. This step function does not shy away from the third-party libraries either. It will go on and enter the Flask's code as well. Another neat trick you can use is this little calculator-like button here. This Evaluate Expression button allows you to inspect other parts of the code that are not necessarily visible in the Variables panel. So if I open it up and let's say you want to inspect the entire request object, well, you can simply type request and hit Enter. And, voilà, you have the entire object right here. We can see that request.form contains our entire student details, so last name, name, student Id, and the request.method is POST. Of course, we have all the other details related to the request object such as mimetype, referrer, and others. Let me close this. When you're done debugging your code, what you can do at least in PyCharm is just click on this green arrow here, which is going to resume the program and continue as if you never debugged it in the first place. Debugging your code can save you a lot of time. Obviously using PyCharm is not the only way to debug your Python code. Many other IDEs support it too, as well as the command line. But we've already worked with PyCharm in this course. All this is great, though, but running our app still requires us to have a Python interpreter installed on our machine. What if we wanted to distributed our code, say the console version of PyStudentManager. We don't want to force the users to install the Python interpreter on their machines, do we? Let's take a look and see how we can create an executable file for our Python program.
Creating an Executable File from a Python App
I mentioned a couple of times how it can be challenging to ship a Python code, especially the one designed to be a desktop application, whether it's console or a GUI application. The challenge stems from the fact that, well, you need to have an interpreter installed in order to run it. But what if we packaged an interpreter with our code and created one single file that is going to not only hold all of our code but also any third-party Python packages and the Python interpreter itself. Enter pyinstaller. Pyinstaller is a Python package that allows us to create binary executable files from our Python program. It is cross-platform so it will create the .exe file for Windows and a .app file for Mac. It also works under Linux 2 and a gazillion other platforms. I will be using Windows in this video because I want to show how we can create a .exe file. I feel that you will more often need to create Windows binaries rather than Mac or Linux ones. One note though: At the time of recording this course, the production version of pyinstaller, and that's version 3.2.1, does not support Python 3.6. The latest supported Python version is 3.5. However, the development version of pyinstaller, which you can find on the GitHub page, does support 3.6, so the support is definitely coming soon. Either way, the process for Python 3.6 is not going to change. In the meantime, I downgraded my Python version to 3.5 on Windows for the demonstration purposes. Well the first thing we need to do is install pyinstaller using pip, so pip install pyinstaller. Once that's done using PowerShell or command line, we navigate to the path where our Python code lives. I'm already in that path, so I'm just going to list all of the files that I have here. And you'll notice that main.py file that we were working on earlier. Now let's create the .exe file. To create the exe file, you simply type pyinstaller main.py and hit Enter. After a lot of warnings that you can safely ignore right now, your exe file is created. That's it. That's really all there is to creating a .exe file. The main.py file that we selected here is the main Python file that you would execute just as if you were running this program through the Python interpreter. Don't worry about imports either. Pyinstaller will take care of those for you. Now if I list the directory again, you see that there are these build and dist directories created and main.spec file created. Those are the directories in a file created by the pyinstaller. I'm going to navigate to this dist folder using Windows File Explorer. And if I enter dist and then main, you see that we have a lot of files generated right here, including some DLL files, pyd files, but one file that sticks out the most is the main.exe file. This is our program. Since it is a console application, we can execute it directly from the command line or PowerShell, and I modified it a little bit so that it now asks us for a student name before it inserts it into the list. Let's try it out. We'll go back to PowerShell and then enter the dist\main subfolder. Let's list it again to make sure that all the files are there. And then I can just type main.exe. And right now, it's asking me for student name. So, again, let's go back to good old james. And then it prints James-HS. Remember, this HS means high school. Now this all works, but we still have a mess of all these files in the dist directory, and they're all required in order for our app to work. We can solve this problem by passing the --onefile flag to the pyinstaller command and have it package all into one .exe file. Let's do that. First, let me delete the dist folder and the build folder created earlier by the pyinstaller, as well as this main.spec file. Let me go back to the command line. Let's navigate away back to our root Pluralsight course folder. Make sure that we have all of our files right there. And now I'm going to run pyinstaller again but pass it the --onefile flag and again type main.py. Once again after a lot of warnings, the process is complete. So let's go back to Windows File Explorer. Again we have a dist directory, but this time we don't have a main directory, and we only have one file, main.exe. Now you will notice that the file size is unusually large, it's 5 MB for a simple console application. That's simply because the Python interpreter along with a lot of required Python packages have to be packaged with this executable file. From my personal experience, I have noticed that even if I'm building a complex desktop application in Python, the file size does not change a whole lot. I encourage you to read the pyinstaller documentation because it supports a lot of nice features such as adding an icon to the application, changing the name, requiring elevated privileges in order for the program to run, and so on. Generally, though, when you're creating an exe file, you don't normally distribute your application just by sending that single exe file. You normally want the user to install your application. Creating a setup wizard is also very easy, and we'll take a look at that in the next video.
Creating a Setup File
Distributing your app just as an exe file can sometimes be considered weird. Normally you will want to create a setup wizard so that the user can install the application in the Windows Program Files folder and so that it is available from the Add and Remove Programs in Windows as well. Also, you may want to include additional files with your application, such as Help file, ReadMe, some images, or whatever. In this video, we're going to create a setup wizard for our console application. Now this video is not strictly related to Python. You can create a setup wizard using this tool for any program you want. It does not matter if it is written in Python or not. That tool I'm talking about is called Inno Setup, so let's go get it now. So Inno Setup is a Windows only application. And to download it, simply search for it on Google. And click this link that has jrsoftware.org in it. And then download and install Inno Setup. When you run Inno Setup, you're presented with this screen. There are two ways you can created a setup wizard file for your application from Inno Setup. The first way is manually creating a Pasqual-based setup script based on which Inno Setup will create a setup wizard for us, while the second way is to use the Inno Setup's built-in setup creation or script, which will ask you for your program file, additional files, licenses, etc. We will use the second way. So I'm going to click on Create a new script file using the Script Wizard and hit OK. The Script Wizard is pretty self-explanatory. On the first page, we specify the basic details of our app, name, version, publisher, and the website. So let's just put PyStudentManager. And the application version could be 1.0. The publisher, let's call it PythonBo, and we can change the website to pythonbo.com. Then we select where we want our application to be installed, the Program Files folder or the custom folder that we specify here. We'll want it to be in Program Files folder. And we can also override the application folder name, but we don't want to do that right now. Then we get to the kind of a main part of creating a setup, selecting the application main executable file. I'm going to use our main.exe file that we created in the last video, so I'm going to browse to it. That's in the desktop folder. And, remember, it's in the dist. And here's our main.exe. In here, we can also add additional files and folders. I'm just going to add a LICENSE.txt file that I created earlier, and that's in the same folder as our main.exe. But, obviously, you can add whatever files or folders you want in here. On the next page, we have a lot of shortcut-related options. And now we get to the point where we can load the text files for the license, information before the installation, and information after the installation. The last two include text that is going to show up in the setup wizard before the installation starts and after the installation finishes respectively. For the license, Inno Setup would automatically generate the Agree or Decline buttons. I am actually going to load the GPL license text that I prepared, and that's the same as our LICENSE.txt file that I used earlier. Then we can select which languages we want to include. And on the following page, we give Inno Setup instructions on creating the actual setup.exe file including the name of the generated file, output folder, setup icon, and even the setup password. So we can set the output folder the same as our main.exe file, that's the dist directory. And the output-based file name, we'll call it pystudentmanager-setup. Normally it's not a good idea to just name your setup file setup.exe because the user may not know what application it is the setup for. Let's hit Next. And we're presented with the last page. So once we click Finish, we see that Inno Setup has automatically created a script for us. We can customize this script to our liking. It is fairly straightforward. Or we can just hit Yes in this dialog to compile the new script right now and create our setup file. Let's just hit No on this dialog for now. And you see that Inno Setup is processing right here. Let's open our Pluralsight course, go to dist, and we see that we have our pystudentmanager-setup file created. I'll minimize this for now. If we made any custom changes to our script here, we can click on Build and then Compile, which is going to regenerate the setup file. We can also run the setup wizard directly from Inno Setup using the Run and Run options. But in this case, I'm going to run the setup wizard directly from the generated file that we have, and that's the pystudentmanager-setup. And we see that we are immediately presented with the GPL license that I added. And we have the I accept the agreement and the I do not accept the agreement that Inno Setup added automatically. Let's hit Next. It's telling us that it's going to install in the Program Files (x86) by default. We have an option to add a desktop shortcut. Then we have the standard issue ready-to-install dialog. And once I click Install, we also have the option to launch PyStudentManager at the end. We don't want to do that because it's a console application. But notice that if I minimize all these windows, we have the PyStudentManager app icon on the desktop or shortcut to our program with a default icon that pyinstaller provided to us in the last video.
Course Conclusion
Congratulations! You're done with the course. I really hope you enjoyed it. I had a lot of fun creating it, and I really hope that I helped you understand the basics of Python. I am sure that by now you agree that Python, although different, is a fairly simple programming language but very powerful at the same time. Remember, there are a lot of uses for Python. You can do web development, scientific computations, artificial intelligence, desktop applications, you name it, and you can do it with Python. So what should you do now? I always say, first, go outside and celebrate. But, otherwise, the choice is yours. Maybe explore some awesome Python libraries like Django or TensorFlow or PyQT. It's tough to decide. I am always available on Pluralsight, so please feel free to leave comments and questions in the discussion part of the course. I'll be happy to answer them. In the meantime, you can follow me on Twitter, @pythonbo, or visit my website, which is also pythonbo.com. Good luck, and have fun!
Course author
Bo Milanovich
Bo is a senior software developer from Serbia who is deeply involved with Python. In the past years, Bo has taught several courses online and offline at various universities.
Course info
LevelBeginner
Rating
(843)
My rating
Duration2h 59m
Released27 Apr 2017
Share course