[MUSIC PLAYING] BRIAN YU: All right, welcome back, everyone, to Web Programming with Python and JavaScript. And today, we take a look at one of the two main languages we're going to be looking at in this course. In particular, we're going to be looking at Python. Python is a very powerful language that makes it very easy to build applications quickly because there are a lot of features that are built into the language that just make it convenient for quick and productive development. So one of the goals of today is to introduce you to the Python programming language if you haven't seen it before. And even if you have seen it before, to give you a taste for what the language has to offer, exploring some of the more advanced features in some of the techniques we can use using Python to be able to develop applications all the more effectively. So we begin with our very first Python program, just a program that says, "hello, world." We're going to be writing it in a text file. And the program just looks like a single line, just like this. And if you've used other programming languages before, like C, or Java, or other languages, this probably looks pretty familiar syntax-wise. But just to break it down, we have a function called Print built into the Python programming language for us. And like many other programming languages, functions in Python take their arguments inside of parentheses. So inside of these parentheses are the argument, or the input, to the Print function, which in this case is just the words, "hello, world" followed by an exclamation point. So here's how we can actually take this program and run it. I'm going to go into my text editor and create a new file that I'll call hello.py. dot-py or dot-py is the conventional extension for Python programs. So I create a file called hello.pi inside of which will just be the Python code that we just saw a moment ago. We'll call the Print function. And as an argument, or the input, to the Print function, I'll say, "hello, world" exclamation point. Now in order to run this program, we're going to use a program in our terminal that also just so happens to be called Python. Python is what you might call an interpreted language, meaning we're going to run a program called Python, which is an interpreter that is going to read our .py file line by line, executing each line and interpreting what it is that it means in a way that the computer can actually understand. So we'll run Python followed by the name of the program that we'd like to interpret, in this case hello.py. And when we run this program, we see that the words "hello, world" are printed to the terminal. And that's it. That's the end of the program. And that's the very first program that we've written using the Python programming language. So now already we've seen a couple of features of Python. The ability to interpret Python-- there's no need to compile it into a binary first in order to run a Python program. We've seen functions. And we've also seen strings, just text that we can provide in quotation marks that we can provide as input to other functions or manipulate in other ways. And we'll see some examples of a string manipulation a little bit later. Like many other programming languages, Python also supports variables. And in order to assign a new value to a variable, the syntax looks a little something like this. If I have a line like a equals 28, what that's going to mean is take the value 28 and assign it, store it inside of this variable called a. Now, unlike other languages like C or Java which you might be familiar with, where you have to specify the type of every variable you create-- you have to say, like, int a to mean a is an integer. Python doesn't require you to tell you what the types of each of these variables actually are. So we can just say a equals 28 and Python knows that because this number is an int, that it's going to represent the variable a as an int, that it knows, it's able to infer, what the types of any these values happen to be. So all the values do indeed have types. You just don't explicitly need to state them. So for example, this here, the number 28, is a type int. It's an integer. A number like 1.5 has a decimal in it. It's a floating point number. So that, in Python, is what we might call a float type. Any type of text, something like the word "hello" wrapped in either double quotation marks or single quotation marks-- Python supports both-- is what we would call the str type, short for string. We also have a type for Boolean values, things that can be either true or false. In Python, those are represented using a capital T, true, and a capital F, false. Those are of type bool. And also, we have a special type in Python called the none type, which only has one possible value, this capital N, none. And none as a value we'll use whenever we want to represent the lack of a value somewhere. So if we have a function that is not returning anything, it is really returning none, effectively. And so you might imagine that none can be useful if ever you want a variable to represent the absence of something, for example. So lots of different possible types, and there are more types than just this. But here's a sampling of the possible variables and types that might exist inside of this language. So now let's try and actually use a variable in order to do something a little bit more interesting inside of our program. And we'll write a program that's able to take input from the user in order to say hello to them, for example. So I'll create a new file. We'll call it name.py. And to start, I'd like to prompt the user for input. I'd like to prompt the user to, for example, type in their name. So how might we do that? Well, just as there is a Print function that is built into Python that just prints out whatever the argument happens to be, Python also has a built in function called Input that prompts the user for input and asks them just type in some input. So let's provide some input and ask the user to type in their name, for example. And then we can save the result, the output of that function inside of a variable. And in this case, I'll save it inside of a variable that in this case also just happens to be called Name. Now we can run the program. I can run the program by going into my terminal and typing Python name.py. I'll press Return. And we'll see the program prompts me to type in my name. I see name colon space, which is that string I provided as the argument to the input function. And this now prompts me to type in my name, so I will. And after that, nothing seems to happen so far. So now I'd like to do something with that input. I typed in my name. I'd like to say, like, hello, for example to myself. So I'll go back into this program. And now what I can do is I can say print hello comma. And then I can say plus name. This plus operator in Python does a number of different things. If I have two numbers, it'll add those two numbers together. But with two strings, plus can actually concatenate, or combine, two strings together. So I can combine hello comma space with whatever the value of name happens to be. So now I'll rerun this program. Python name.py. Type in my name. And now we see "hello, Brian" as the output of the program. So this is one way that you can manipulate strings in Python. Another way that's quite popular in later versions of Python 3 is a method known as using f strings, short for formatted strings. And in order to use f strings in Python, it's going to be a similar but slightly different syntax. Instead of just having a string in double quotation marks, we'll put the letter f before the string. And inside of the string, I can now say hello comma-- and then if in a formatted string, if I want to plug in the value of a variable, I can do so by specifying it in curly braces. So what I'll say here is, inside of curly braces, name. And so what's going on here is I am telling this formatted string to substitute right here the value of a variable. So I prompted the user for input to type in their name. We took their name, saved it inside of this variable called name. And now here in this print statement on line two, I'm printing out a formatted string that is hello comma. And then in curly braces here, I'm saying plug in the value of the variable name. And so that is going to have the effect of taking whatever name was provided as input and printing it out. And this is a slightly more efficient way of being able to quickly create strings by plugging in values into those strings. So now I'll see the exact same behavior if I run Python name.py and prompt it to type in my name. I'm like, Brian. And then I see the result, "hello, Brian" for example. And so those are a couple of ways that we can deal with strings, manipulating strings, and combining strings using this technique. So in addition to variables, Python also supports all of the same other features that are core to many procedural programming languages, such as conditions, for example. So let's take a look at an example now of seeing whether a number is positive, or negative, or zero, for example. So I'll create a new file that I'll call conditions.py. And inside of conditions.py, I'll first prompt the user to type in some input. I'll say input number to mean type in a number. And we'll save that input inside of a variable that I'm just going to call n. And now I can ask questions. I can say something like if n is greater than zero. Then print n is positive. And so what's going on here is I have a Python condition. And the way a Python condition works is it begins with this keyword, a keyword like if, followed by a Boolean expression, some expression that's going to evaluate to true, or false, or something kind of like true or false. We can be a little bit loose about that, as we may see later. And then a colon means, all right, here is the beginning of the body of the if statement. And in Python, the way we know that we're inside the body of an if statement or inside the body of any other block of code is via indentation. So in some languages, like C, or in languages like HTML, which we saw a couple of lectures ago, the indentation isn't strictly required by the computer to be able to parse and understand what's inside the program. In Python, it's different. The indentation is required because the indentation is how the program knows what code is inside of the if statement and what code is outside of the if statement. So we have if n is greater than zero colon. And then everything indented underneath the if, is all of the body of the if statement. It is the lines of code that will execute if this condition, this Boolean expression and greater than zero, happens to be true. So if end is greater than zero, will print out n is positive. And then we can add an additional condition. I can say something like-- well, I could say something like else print n is not positive. But I can be a little bit more specific than that. Here is a sort of two branches. One if n is greater than 0, and one else case to handle all of the other possible scenarios. But really, what I'd like to do is perform a second check. In other languages, this might be called an else-if. Like, if this condition is not true but this other condition is true. Python abbreviated this to just elif-- E-L-I-F, just short for else-if. So I can say elif n is less than zero, then let's go ahead and print out n is negative, and else print n is zero, So the idea here now is that if n is greater than zero, we perform some task elif-- in other words, if it's not greater than zero-- then we check to see if it is less than zero. In which case, we print out that n as negative. Else, if neither of those two conditions are true, it's not positive and it's not negative, the only remaining possibility is that n is zero. So we can print out that n zero. And so we might like for this program to work. But watch what happens if I now try and run conditions.py. Even though logically in our heads and looking at it now, it probably seems pretty logical, if I run Python conditions.py and type in a number-- I'll type in the number five, for example, just see what happens. All right, something weird just happened. And this is our very first Python exception, an error that happens because something didn't quite go right inside of our Python program. And over time, you'll begin to learn how to parse this exception, and understand what it means, and where to begin to debug. But learning how to read these exceptions and figure out how to deal with them is definitely a very valuable skill on your way to becoming a Python developer. And so let's see if we can figure out what this exception to saying. Oftentimes, I start by looking at the bottom. I see that there is a type error. That is the type of the exception that has happened. There are a lot of exceptions that can go wrong in Python, things that we can do that cause errors. In this case, it's a type error, which generally means that there's some mismatch of types, that Python expected something to be of one type but it turned out to be a different type. So let's try and understand what this might be. It says greater than sine, not supported between instances of stir, short for string, and int. So what does that mean? Well, I guess it means that the greater than symbol that checks if one thing is greater than another doesn't work if you're comparing a string to an integer. And that's probably pretty reasonable. It doesn't really make sense to say a string is greater than or less than an integer. When we're talking about greater than or less than, usually we're talking about numbers. So they should both be integers, for example. So why do we think that greater than is comparing a string and an integer? Well, now we can look a little bit further up at the trace-back, which will show which parts of the code are really causing this problem. And in this case, the trace-back is pretty short. It's just pointing me to a single line of a single file. It's saying in the file conditions.py on line three, here is the line that triggered the exception-- if n is greater than zero. So, what's the exception here? Well, zero is obviously an integer because that just is an integer. And so if greater than thinks that it's comparing a string with an integer, then n somehow must be a string. Even though I typed in the number five, it must still think n is a string. So why might that be? Let's take a look at the code again and see if we can figure out what's going on. Well, it seems that this input function doesn't care what you type in. It's always going to give you back a string. n somehow is ending up as a string, which is pretty reasonable because the input function has no idea whether I typed in a number, or whether I typed in a letter, or I typed in other characters altogether. So input doesn't know to give back its data in the form of an int, or in the form of a float, or in any other form. So by default, it's just going to return a string. What characters did the user type in as their input? So what I'd like to do now, in order to make this program work the way I want it to, is take this and convert it into an integer, or cast it into an integer, so to speak. And the way that I can do that is by using a function in Python called int, that takes anything and turns it into an integer. So here, I can say int-- and then as the argument to the int function, the input to the int function, I'm just going to include this whole expression, input number. So I'm going to ask the user to input a number. They type in some text. The input function gives me back a string. And that string is going to serve as the input to the int function, which then gets saved inside of this variable called n. So now that we know that n is indeed an integer, let's try and run this program again. I'll go back into the terminal, run Python, conditions.py, I'm asked to type in a number. I type in a number like five. And all right, that still doesn't seem to have worked. And it didn't work because I didn't save the file. So I'll go ahead and save the file, try it again, type in a number. And now we see that indeed, n is positive. We get no more exception. We were able to run the code successfully and see that the value of n is positive. And I could try this again to test the other conditional branches. Type in negative one for example to see that n is negative. And otherwise if it isn't either positive or negative, then we know that n is zero. And so here was our first exposure to conditions in Python, the ability to have multiple different branches and do different code depending on some expression that we're going to evaluate to either be a true expression or a false expression. All right, so let's take a look at some of the other features that are going to be present inside the Python language. And one of the most powerful features of Python are its various different types of sequences, data types that store values in some sort of sequence or some collection of values altogether. So I go ahead and create a new file that we'll call sequences.py. And there are a number of different types of sequences that all obey similar properties. But one of the types of sequences is the type we've already seen, which is just a string, for example. So if I have a name, and the name is something like Harry, for instance, and this sequence allows me to access individual elements inside of the sequence. And in order to do so, it's much like an array in other languages, if you've experienced them before. But I can print out name square bracket zero. This square bracket notation takes a sequence, some ordered sequence of elements, and gets me access to one particular element inside of that sequence. And so if I have a string-like name and I say name square bracket zero, the effect of that is going to be take this long sequence and get me the zero-th element. In many programming languages and in programming more generally, we often start counting things at zero. So the very first item in the sequence is item zero, the second item is item one. So it's easy to get slight off by one errors there. But just know that item zero of the name should be the first character in the name. And I can see that for sure, if I run Python-- I'll save this file, run Python sequences.py. And what I get is just the first character of Harry's name, which in this case is the letter H. If I instead asked to print out character one, which would be the second character in the name, if we run the program, now I get the letter a. And this type of indexing works for many different types of sequences, not just a string, which so happens to be a sequence of characters, but other types as well. Python, for example, has a type for lists of data. So if I have a sequence of any type of data that I want to store, I can store that information inside of a list in Python. So maybe instead of storing one name, I have multiple names that I want to store. So I want to store names like Harry, and Ron, and Hermione, for example. So now I have three names all stored in the sequence inside of a Python list. And I can-- you know, I can print out all of the names, for example, just to print out all of the names to see what the value of the variable names is equal to. And we'll see that when I do that, I get a printout of the contents of that list. Harry, Ron, Hermione, in that particular order. But you could also, much as you could index into a string, index into a list to say get me just the very first item inside of this names list, which in this case, when I run the program, is going to just be Harry. So there are a number of different sequence types that you can use in order to represent data. Another one just so happens to be called a tuple. And a tuple is often used if you have a couple of values that aren't going to change but you need to store a pair of values like two values together, or three values together, or something like it. You might imagine that if were writing a program to deal with graphing in two dimensions, for example, you might want to represent a point as an x value and a y value. And you could create two variables for it. I could say, you know, let me do, say, a coordinate x is going to be equal to 10.0. And coordinate y is equal to 20.0. But now I'm creating two variables for what's really one unit that just so happens to have two parts. And so to represent this, we can use a tuple in Python, and just say something like coordinate equals 10.0 comma 20.0. So whereas in lists we use square brackets to denote where the list begins and where the list ends, in a tuple, we just use parentheses to say we're grouping a number of values together, we're grouping one value, 10.0, with a second value, 20.0. And now we can pass around these two values as a single unit just by referencing them using the single name, which in this case is coordinate. So there are a number of different types of these various different sequences. And some of those sequences we'll take a look at are these data structures here. So list, for example, is a sequence of mutable values, which we took a look at. And mutable, just meaning we can change the elements in the list. If I have a list, I can add something to the end of the list, I can delete something from the list, I can modify the values inside the list. A tuple, on the other hand, is a sequence of immutable values those values can't change you can't add another element to the existing tuple. You'd have to create a new tuple in order to do so. And there are other data structures that exist as well. A couple that we'll take a look at in a moment include sets, which are a collection of unique values. So if you're familiar with sets from the world of mathematics, it's a very similar idea, that whereas a list in a tuple keeps things in a particular order, a set does not keep things in any particular order. It's just a collection. And in particular, all of the values need to be unique. In a list or in a tuple, you might have the same value appearing multiple times. And in a set, every value appears exactly once. And there are some advantages to sets, some ways that you can make your programs more efficient by using sets if you know that you just need a collection, if you don't care about the order, if something is only going to show up exactly once at most, then you can use a set to potentially make your programs a little more efficient and a little more elegantly designed. And finally, one other data structure that's quite powerful and that's going to come up a number of times during this course is a dictionary. In Python, shortened to just a dict, which is the collection of what we're going to call key-value pairs. And the way I like to think of this as with an actual physical dictionary that you might find in the library that maps words to their definitions. In a physical dictionary, you open up the dictionary and you look up a word and you get the definition. And a dict in Python is going to be very similar. It's going to be a data structure where I can look something up by one keyword or one value and get some other value as a result. We call the thing that I'm looking up the key. And we call what I get when I do the looking up the value. So we keep pairs of keys and values. In the case of an actual dictionary in the real world, the key is the word that we want to look up and the value is its definition. But we can use this more generally in Python anytime we want to map something to some other value such that we can very easily look up that value inside of this data structure. So we'll see examples of dictionaries as well. So let's now explore the first of these data structures, these lists, to explore what we can do by taking advantage of the features that are given to us by a Python list, for example. So we'll go ahead and create a new program that I'll call lists.py. And here, I'm just going to create a list of names. So names equals Harry, Ron, Hermione, and Ginny, for example. And as I start to write multiple lines of code, especially as my Python programs start getting longer, it can be a good idea to document what it is that I'm doing. So I can say, let me add a comment to this particular line of code just so I know what it is that I've done in this line of code. And in Python, there are a couple of different ways to create a comment. But the simplest way is just to use the pound sign or the hashtag. As soon as you include that, everything after that for the remainder of the line is a comment. The interpreter is going to ignore that comment. You can say whatever you want. It's more for you, the programmer and for someone who's reading your program to be able to look at the program, understand what it's saying, and figure out what they need to do about it. So I can just say, define a list of names, for example, just to make it clear to me what it is that I have done inside of this line of code. So I can print out that list of names, as we've done before. And we'll see that when I print out that list of names, what I get is-- oh, let me run list.py. What I get is this list-- Harry, Ron, Hermione, and Ginny. But I could also print out, as we've seen before, just the first of those names. Say, you know, print out just names square bracket zero, in which case I'm going to get just Harry, for example. But now, recall that a list is mutable. I can modify the elements that happen to exist inside of this list. So I could say names.apped a new name, something like Draco, for example. And so lists have a number of built in methods or functions which are functions that I can run on an existing list to access particular elements of the list or to modify the list in particular ways. And in the case of a list, the append method is a method or function that I can run that just adds a value to the end of an existing list. So I've added Draco to the list. And there are a number of other methods that I can use on lists, one of which is, for example, sorting a list. No need to write your own sorting algorithm in order to sort a sequence of objects. In Python, there is a built-in sort method that works on lists where I can just say names.sort. That will automatically sort everything in the list. And now if I print out all of those names-- go to print them out and get rid of this old print statement-- now we see that we get five names that are printed out because I had four elements originally in this list but then I added a fifth one. And notice now that they are actually in alphabetical order, starting with Draco, ending with Ron because I was able to sort the list by modifying the order in which those elements actually show up. And so list can definitely quite powerful anytime you need to store elements in order, a list is definitely a useful tool that Python gives to you. If you don't care about the order of the elements though, and if you know that all the elements are going to be unique, then you can use a set, which is another Python data structure that works in similar ways. The syntax is slightly different. So let's do an example with those. I'll create a new file, call it sets.py. And let me first create an empty set. And we can do that by just saying s equals-- s is going to be the variable that will store my set. And I'll say set and then parentheses. That will just create an empty set that just so happens to have nothing inside of it now we'll add some elements to the set so I can say s.add. Let's add the number one to the set. Let's add the number two. Let's add that number three. And let's add the number four. And then we can print out the set to see what happens to be inside the set right now. Now when I run this program, Python sets.py, we see that inside the set are four values, one, two, three, and four. They happen to be in order. But sets are not naturally ordered. They're not going to always keep track of what the order is going to be. But I can add-- for example, if I add three again to the set, now I've added three to the set twice. I added one, two, three, four, and then three again. When I print out the contents of the set, it still just contains the elements one, two, three, four. No element ever appears twice in the set, following with the mathematical definition of a set where no element ever appears more than once inside of a set. You can also remove elements from sets as well. So if I wanted to remove the number two from the set for example, I could say s.remove2 and then print out s to say print out whatever happens to be inside of that set now. And now when I rerun this program, I only get one, three, and four because I removed two from the set. So sets allow you to add to them, remove from them. And also, all sequences, whether they be strings, or lists, or sets, allow you to get how many elements are in the set by taking advantage of a function built into Python called len. So len we'll give you the length of a sequence, so the number of items inside of a list, or the number of characters inside of a string, or the number of elements inside of a set. And so, if I wanted to print out how many elements are in the set, I might do something like this. In a formatted string, say the set has some number of elements. And how do I know how many elements? Well, again, inside of these curly braces, I can include any expression in Python that I would like to substitute into this string. So how many elements are in the set? I can get that by calculating len of s. So what I've done here is I've said with len of s, I would like to calculate the length of the set, s, in other words, how many elements are actually inside of that set. And then using this curly brace notation, I'm saying, take that number and plug it into this string so we can see the set has some number of elements, for example. So now if I run this program, Python sets.py, I see that I get these three elements that happen to be inside of the set right now, which is one, and then three, and then four. And then it tells me that the set has three elements inside of it, which is the number of elements that are in the set right now. So now we've seen a number of different language features inside of Python. We've seen variables. We've seen conditions so that we can conditionally do things-- if something is true, if something else is true. And we've seen some of the data structures that are core to the way Python works-- lists, and sets, and tuples, and other data structures that can be helpful too. And now let's take a look at another feature of the Python programming language common to many programming languages, the idea of looping. If I want to be able to do something multiple times, I'll go ahead and create a new file called loops.py. And let's just create a simple loop. The simplest loop we could create in Python is just one that's going to count a bunch of numbers. So in order to do that, what I could say is something like this. For i in one, two, three, four, five-- or maybe I want to count zero, one, two, three, four, five, just to start counting at zero-- print i. And so here's the basic syntax for a Python loop. And here's what seems to be going on. Over here on the very first line, I have a Python list as denoted by those square brackets that contains six numbers-- zero, one, two, three, four, five. And now I have a for loop-- for i in this list. And the way Python interprets this is to say, go through this list one element to the time. And for each element, call that element i. You could've called it anything. But in this case, i is just a conventional choice for a number that keeps incrementing. And we're going to print out now the value of i for each iteration of this loop. So we try this out now and run Python loops.py. We see zero, one, two, three, four, five. Great, it printed out all of the numbers from zero to five one at a time. In practice though, if we wanted to count all the way up to five or print six numbers for example, this is fine for now. But if we wanted to print like 100 numbers or 1,000 numbers, this is going to start to get tedious. So Python has a built-in function called range where I can say for i in range six to achieve exactly the same thing. Range six means get me a range of six numbers. So if we start at zero, it's going to go from zero all the way up to five. And then we can print out each one of the elements inside of that sequence. So if I rerun in Python loops.py, we get zero, one, two, three, four, five. So loops enable us to loop over any type of sequence. So if the sequence is a list, I can say something like, if I have a list of names like Harry, and Ron, and Hermione, and this is my list of names, I can have a loop that says that for each name in my list of names, let's print out that name, for example. So we have a list. The list is called Names. We're looping over it one element at a time and printing it out. Now if I run the program, I see three names printed one on each line. And you can do this for other sequences as well. Maybe I have just a single name that is called Harry. And now I can have a line that says, you know, for every character in that name, print the character. If the name is the sequence, is a sequence of individual characters because it's a string, then when I loop over to that string, I'll be looping over each individual character in that string. So I can run the program and see one on each line, each of the letters that just so happens to be inside of Harry's name. So now we've seen conditions. We've seen loops. And we've seen a number of different data structures. We've seen lists, and sets, as well as tuples. The last important data structure that we're going to be taking a look at are Python dictionaries which, as you'll recall, are some mapping of keys to values. If I want to be able to look something up, I can use a Python dictionary just as a data structure to be able to store these sorts of values. So I'll create a new file called dictionaries.py. And maybe I want to create a dictionary that is going to keep track of, say, what house each of the students at Hogwarts happen to be in. So I might have a dictionary called Houses. And the way we specify a dictionary is by specifying a key colon of value when we're defining a dictionary for the first time. So I might say Harry colon Gryffindor and then Draco colon Slytherin, for example. And so what this line of code is doing is it is creating a new dictionary. This dictionary is called Houses. And inside this dictionary, I have two keys, two things that I can look up. I can look up Harry or I can look up Draco. And when I look up those keys, I get the value that follows their colon. So after Harry, if I look up Harry, I get Gryffindor If I look up Draco, I get Slytherin, for example. So now if I wanted to print out what house Harry is in, I can print out houses square brackets Harry. So I can here, say, I would like to print out-- take the Houses dictionary. And the square bracket notation is how I look something up inside of a dictionary. It's similar to how we use square brackets to look up a specific element inside of a list to say, like, get me element zero or element one. In this case, we're using a Python dictionary to say, take the Houses dictionary and look up Harry's value, which hopefully should be Gryffindor. And we'll see that if we look up run Python dictionaries.py, we do get Gryffindor as the value of Harry's house. We can add things to this dictionary as well using the same syntax. In the same way that I use square brackets to access the value inside of a dictionary, if I want to change the value in a dictionary or add something new to it, I could say houses and Hermione, and say that Hermione is also in Gryffindor, for example. And so this line of code here says take the Houses dictionary and look up Hermione in the Houses dictionary. And when you do, that should be set equal to this value here, Gryffindor. So we took that value and we are going to assign it to Hermione inside of the dictionary, such that now if we wanted to, we could print out Hermione's house as well, run the program, and see that Hermione is also in Gryffindor. So anytime we want to be able to map some value to some other volume, whether we're mapping people to what house they happen to be in or we're mapping users to some information about those users inside of our web application, dictionaries are going to be very, very powerful tools for us to be able to do that. The next Python language feature we'll take a look at are functions in Python that are going to be some way for us to write our own functions that take an input and produce some output. We've already seen a number of different functions that already exist in Python. We've seen the input function that takes an input from the user. We've seen the print function that takes some text or some other information and prints it to the screen. But if we want to define our own functions, we can do so as well. So here I'll go ahead and write a new program called functions.py. And let's write a function that takes a number and squares it. So the square of 10 is 10 times 10, or 100. I would like a function that very easily takes a number and returns its square. The way I define a function in Python is using the def keyword. Def, short for define. And here I can say, let me define a function called square and then, in parentheses, what inputs it takes. In this case, square just takes a single input that I'm going to call x. But if there were multiple inputs, I could separate them with commas, like x, y, z for a function that took three inputs, for example. But in this case, there is just a single input, x. And the square function could have any logic in it indented underneath the square function. But ultimately, this function is fairly simple. All it's going to do is return x times x, x multiplied by itself. And now, if I want to print out a whole bunch of squares of numbers, I can do so. I can say for i in range, let's say, 10, let's print out that the square of i is square i. So let's try and parse out what's going on here. Line four says for i in range 10, do some loop 10 times, looping from zero all the way up to nine. And for each time we loop, we're going to print something out. We're going to print out the square of-- plug in the value of i here-- is-- plug in the value of calling our square function using i as input. So that is going to have the result of running this loop 10 times and printing out this line 10 different times, each with a different value of i. So I can run Python functions.py. And here's what I see. The square of zero is zero. Square of one is one two is four, so on and so forth, all the way up to the square root of nine is 81. So we've now written a function and been able to use it. But ideally, when we write functions, we'd like to not just be able to use them in the same file, but for others to be able to use them as well. And so how can we do that? Well, in order to do that, you can import functions from other Python modules or files, so to speak. So let me create a new file called squares.py, for example. So that instead of running this loop here, let's instead run this loop in squares.py, again, separating out different parts of my code. I have one file that defines the square function inside of functions.py and then another file called squares.py, where I'm actually calling the square function. Now if I try to run Python squares.py, you'll notice I'll run into an error. Here's another error you'll see quite frequently. It's a name error, another type of exception. Which here says the name square is not defined, meaning I'm trying to use a variable, or a function name, or something else that doesn't actually have a definition. I've never said what square is. And that's because by default, Python files don't know about each other. If I want to use a function that was defined inside of another file, I need to import it from that file. And I can do so like so. I can say, from functions import square. Functions was the name of this file, functions.py. And I'm saying from that Python module, I would like to import the square function as a function that I would like to use. Now I can run Python squares.py. And we get the output that we expect-- no more exception. I've now been able to import something from another module and access it this way. So this is one way to import, to literally say from functions import a square function, import a particular name that is defined inside of functions.py. Another way I could have done this is just to say import functions, just import that whole module. But then I would need to say-- instead of just square, I would need to say functions.square, to mean go inside the functions module, and get the square function, and run that function. And this would operate in exactly the same way. So, a couple of different options-- I either import the entire module, in which case I use this dot notation to, say, access a particular part of that module. Or I say from functions import square to just import the name square into this file entirely so that I can just use the word square whenever I want to. And this works not just for modules that we have written, but also Python comes with the number of built-in modules. If you want to write programs that interact with CSV files, which are a spreadsheet file format, I can import Python's built-in CSV module to get access to a whole bunch of CSV-related features. There are a whole bunch of math-related features you can get by importing the math module, so on and so forth. And there are additional Python modules and packages that you can install that other people have written. And then you just import them when the time comes. And next time, as we take a look at Django, this is one of the techniques that we're going to be looking at, is using functions that have been written by people that are not ourselves. So that now is modules and how we can use modules to be able to import functions in order to allow for certain behavior. And this is one way that we can program using the Python programming language. But another key technique that Python supports that are supported by a number of other programming languages as well is an idea of object-oriented programming, a special type of programming or programming paradigm, so to speak, which is a way of thinking about the way that we write programs. In an object-oriented programming, we think about the world in terms of objects where objects might store information, store some data inside of them, and also support the ability to perform types of operations, some sort of actions, or methods, or functions, as we might call them, that can operate on those objects. So now we're going to take a look at some of the object-oriented capacities that the Python programming language is going to give us the ability to have. So, Python comes with a number of built in types. It has types for lists. It has types for sets and so on and so forth. Let's imagine, though, that we want to create a new type in Python, some way of representing other types of data. For example, two-dimensional points, things we've talked about before-- something that has an x value and a y value. Now, as we've already discussed, you could do this using a tuple, just using one number comma another number. But we could create an entire class of objects to be able to represent this data structure as well. And so that's what we'll take a look at now, is how to create a class in Python. So I'll create a new file called classes.py. And all a class is, is you can think of a class as a template for a type of object. We are going to define a new class called Point. And then after we've defined what a point is, we will be able to create other points. We'll be able to create points to store x and y values, for example. And so what do we need in order to create a class? Well, we need some way to say that when I create a point, what should happen? And in Python, this is defined using what's called a magic method called underscore underscore init. And underscore underscore init is a method or function that is going to automatically be called every time that I try to create a new point. And this function takes a couple of arguments. All functions that operate on objects themselves, otherwise known as methods, are going to take the first argument called self. And this argument self represents the object in question. And this is going to be important because we don't just want a single variable called x to store the points x coordinate or a single variable called y to store the y coordinate because two different points might have different x and different y values. And we want to be able to store those separately. And we're going to store them inside of the object itself. So this variable self references the object that we are currently dealing with. And it might change depending on which point we happen to be interacting with at any given time. What other inputs does a point need? Well, a point also needs an x value and a y value. So when we create a point, we're going to provide to that point an x value and a y value. Now, what do we need to do in order to store all this data inside of the point? Well, recall that self is representing the point itself. And so if we want to store data inside of that point and allow that point to store its own x and y values, then we need to store that data inside of the self, so to speak. And in order to do that, we can use this dot notation to say self.x is equal to whatever this input x happens to be. And self.y is equal to whatever this argument y happens to be. And these values, x and y, they can be called anything. They could just be called like input one and input two, for example. And then you would just reflect them here. The important thing is that these two input values are being stored inside of the point itself in properties that we're going to call x and y. All right, so that was a little bit abstract. But now let's see how we could actually use this. If I want to create a new point called p, I can say p equals point. And then the self argument is going to be provided automatically. I don't need to worry about that. But I do need to provide input one and input two-- the x value and the y value. So I'll go ahead and provide an x value of two and a y value of eight, for example. So now I've created this point. And now that I have a point, I can print out information about the point. I can print out the x value of the point. And I can print out the y value of the point. Again, I'm using this dot notation to say, go into the point and access data that is stored inside of that point. Access its x value and access its y value. So now when I run this program, Python classes.py, what I get is two on the first line, that is the x value, and then eight on the second-- or eight on the second line. That is the y value. So what we have here is a function called init that creates a point by storing the two inputs inside of the object, inside of a property called x and a property called y, such that later I can create a point which calls this init function implicitly. And after we've created the point, I can access the data inside of it. I can say print out whatever p.x is equal to, print out whatever p.y is equal to as well. So that was a fairly simple example of creating a class, just creating a class for representing a point, an x and a y value. Let's look at a more interesting example. Let's imagine that we're trying to write a program for an airline where the airline needs to keep track of booking passengers on a flight and making sure that no flight gets overbooked. We don't want more passengers on the flight than there is capacity on that flight. So let's define a new class that we're going to call flight. And this time, the init method is just going to take a single argument other than the self, which is the capacity. Every flight needs some sort of capacity to know how many people can fit on the plane. And so I'll store that inside of a value called self.capacity equals capacity. And what other information do we need to store about a flight? Well, a flight has a capacity. And it also has all of the passengers on the flight. And so we could represent this in a number of ways. But we know that lists can be used in order to store a sequence of values. So we'll go ahead and just create a list that will store in self.passengers that is going to be equal to the empty list. So we start out with an empty list of passengers. So now if I want to create a flight, I can say flight equals and then Flight, that's the name of the class, and then provide a capacity. I can say capacity of three to mean three people can go in this flight, but no more than three. That is the capacity because that is the argument that is specified inside of this init function. And when I do so, I'm automatically going to get this empty list of passengers. So now let's think about what methods, or what functions, we might care about performing when it comes to a flight. So one reasonable function to add would be a function that says, all right, let's add a passenger to the flight if I want someone new to go on the flight. So how might it go about doing that? Well, let's define a new method, also known as a function, to this flight class called Add Passenger. This method can be called whatever we want. Because this is a method that's going to work on an individual object, we need some way of referencing that object itself. So we'll use the keyword self again. And when we add a passenger, we need to add a passenger by their name. So I need to specify their name as well, such that now here, I want to add that name to the passengers list. How do I get access to the passengers list? Well, I have access to the self, the object of itself. And I store the passengers inside of self, in self.passenger, an attribute of this object. And self.passenger is a list that initially starts out as an empty list. But if I want to add something to the end of the list, we've already seen that in order to do that, I can say self-passengers.append name. So that adds in someone new to the end of this passengers list. Now, what could potentially go wrong here? Well, every time we call this Add Passenger function, what's going to happen is we are going to append to the end of this passengers list this name. But we haven't taken into consideration the capacity of the flight. Ideally, our Add Passengers function shouldn't let someone be added to a flight if the flight is already at capacity. So there are a number of things we could do here. We could just check it inside of this function. But just for good measure, let's create a new function. Let's add a new function called Open Seats that is going to return the number of open seats that are on the plane. Other than Self, there are no other inputs that we need to calculate how many open seats there are. The only thing we need to know in order to calculate open seats is we need to know the capacity minus however many passengers there are. Remember, self.passengers is our list of all the passengers. And any time we have a sequence to get the length of that sequence, I can say len, or length of that sequence, to say get me the number of passengers that there are. So now we have this function called Open Seats, which will return capacity minus the number of passengers and tell us how many open seats there are. And now in this Add Passenger function, I can add some additional logic. I can say if not self.open_seats. So this is equivalent to me saying in this case, like, if self.open_seats equals equals zero, meaning there are no open seats, a more Pythonic way, so to speak, of expressing this idea is just saying if not self.open_seats. In other words, if there aren't any more open seats, then what should we do? We should return. And maybe you might imagine this Add Passenger function returns true if it was able to successfully add a passenger and false otherwise. So in this case, I can return false to say, you know what? There aren't enough open seats. Let me return false from the function to indicate that there was some sort of error. But otherwise, if there are open seats, we can add the passenger and return true to mean that everything was OK, we were able to add the passenger successfully. So now we have these three functions-- Init that creates a new flight, Add Passenger that adds a new passenger to that flight, and Open Seats, which tells us how many open seats there are. And now let's use those functions to actually add some passengers to this flight. Let me get a list of people. We'll say Harry, Ron, Hermione, and Ginny. And now let me loop over all of those people. For every person in that list of people, let's try to flight.add_passenger person. And we can save the result in a variable called success, for example. And then I can say if success, well, then let's print out that we added the person to flight successfully. But else, otherwise, let's print out no available seats for that person. So what's going on here? We have a list of people, four people. And for each of those people, we're going to try and add the passenger to the flight calling flight.add_passenger, calling this method, passing, as input, the person's name, and save the result true or false in this variable called Success. If success is true, we print out we've added them successfully. Otherwise, we print out there are no available seats for that person. So now we can try running this program. I'll run Python classes.py. And now we see we've added Harry, Ron, and Hermione to the flight successfully. But the flight had a capacity of three, which means there are no available seats for Ginny, which we get as the error message on the fourth line. But if you're really trying to optimize, you might notice you don't really need this variable. I could just take this entire expression, flight.add_passenger person, and put it in the condition itself. I can say, try and add a passenger. Add passenger will return true or false. And if it returns true, that means it was a success. And then I can print out that we've added the person to the flight successfully. So that is a brief look at object-oriented programming, this technique within Python and other programming languages to represent objects like this particular flight and then to manipulate those objects using methods like the Add Passenger method that takes a flight and adds people to it, at least as long as there is available capacity on that flight. It's one of the many powerful features of Python that we'll be definitely taking a look at later in the term and using as we go about building these web applications. Now, there are a couple of final examples that are just worth taking a look at just to give you some exposure to some of the other features that are available in Python. One thing that will be coming up soon is the idea of decorators. And just as we can take a value in Python like a number and modify the value, decorators are a way in Python of taking a function, and modifying that function, adding some additional behavior to that function. So I'll create a new file called decorators.py just to demonstrate what we can do with decorators. And the idea of a decorator is a decorator is going to be a function that takes a function of input and returns a modified version of that function as output. So unlike other programming languages where functions just exist on their own and they can't be passed in as input or output to other functions, in Python, a function is just a value like any other. You can pass it as input to another function. You can get it as the output of another function. And this is known as a functional programming paradigm, where functions are themselves values. So let's create a function that modifies another function by announcing that the function is about to run and that the function has completed run, just to demonstrate. So this Announce function will take, as input, a function f. And it's going to return a new function. And usually, this function wraps up this function f with some additional behavior, and for that reason, is often called a wrapper function. So we may call this wrapper to say that, all right, what is my wrapper function going to do? It's first going to print about to run the function just to announce that we're about to run the function. That's what I want my Announce decorator to do. Then let's actually run the function f. And then let's print done with the function. So what my Announce decorator is doing is it's taking the function f and it's creating a new function that just announces, via a print statement, before and after the function is done running. And then at the end, we'll return this new function, which is the wrapper function. So this right here is what we might call a decorator, a function that takes a function, modifies it by adding some additional capabilities to it, and then gives us back some output. So now here, I can define a function called Hello that just prints "hello, world," for example. And then to add a decorator, I use the at symbol. I can say at announce to say add the Announce decorator to this function. And then I'll just run the function Hello. And we'll see what happens. I'll run Python decorators.py. And I see about to run the function, then "hello, world," then done with the function. So again, why did that work? It's because our Hello function that just printed 'hello, world" is wrapped inside of this Announce decorator, where what the Announce decorator does, is it takes our Hello function of input and gets us a new function that first prints an alert warning that we're about to run the function, actually runs the function, and then prints another message. So, a bit of a simple example here. But there's a lot of power in decorators for being able to very quickly take a function and add capability to it. You might imagine in a web application, if you only want certain functions to be able to run, if a user is logged in, you can imagine writing a decorator that checks to make sure that a user is logged in, and then just using that decorator on all of the functions that you want to make sure only work when a user so happens to be logged in. So decorators are a very powerful tool that web application frameworks like Django can make use of just to make the web application development process a little bit easier as well. Let's take a look at a couple other techniques that exist within Python. One is how we might be able to more efficiently represent functions. So let's imagine that I now have-- I'm going to call this lambda.py for a reason you'll see in a moment. Let's imagine that I have a list of names or people, for example. And inside of this list of people, each person, instead of being just a string, is going to be a dictionary that has both a name like Harry and a house like Gryffindor. And let me add another name like Cho and a house like Ravenclaw, and then another name like Draco and a house like Slytherin. So here we have a list where each of the elements inside of that list is a dictionary, a mapping of keys and values. And that's totally OK. In Python, we have the ability to nest the data structures within one another. We can have lists inside of other lists, or lists inside of dictionaries, or in this case, dictionaries inside of a list. And in fact, this nesting of data structures is one of the reasons why it's very easy in Python to be able to represent structured data like a list of people where every person has various different properties. What I might like to do now is something like sort all of these people and then print them all out. So I might want to say people.sort, and then print all the people. But if I try to run this, I'll get an exception. I get an exception, type error less than not supported between dict and dict, which is sort of weird because I'm not using any less than symbol at all anywhere in a program. But in the trace-back, you'll see that the line of code that it's catching on is people.sort. Somehow, people.sort is causing a type error because it's trying to use less than to compare two dictionaries. And what this appears to mean is that Python doesn't know how to sort these dictionaries. It doesn't know, does Harry belong before or after Cho because it doesn't know how to compare these two elements. And so if I want to do something like this, then I need to tell the sort function how to sort these people. And so in order to do that, one way I could do this is by defining a function that tells the sort function how to do the sorting, what to look at when sorting. So if I want to sort by people's name, let me define a function that I'll just call f, that takes a person as input and returns that person's name by looking up the name field inside of the dictionary. And now I can sort people by their name by saying sort key equals f. What this means is sort all the people. And the way to sort them, the way you know how to compare them, is by running this function where this function takes a person and gives us back their name. And this will sort everyone by name. Now, if I run Python lambda.py, you will see that I first get Cho, then Draco, then Harry in alphabetical order by name, whereas if instead I had tried to sort people by their house by changing my function that I'm using to sort and then rerun this, now I see that its first Harry who is in Gryffindor, then Ravenclaw, then Slytherin, for example. So we get the houses in alphabetical order instead. But the reason I show this is because this function is so simple and is only used in one place. Python actually gives us an easier way to represent a very short, one-line function using something called a lambda expression. And this is a way of including the function just as a single value on a single line. I can say instead of defining a function called f, I can get rid of all of this and just say, sort by this key, a lambda, which is a function that takes a person and returns the person's name. So we say person as the input, colon person name as the output. This is a condensed way of saying the same thing we saw a moment ago, of defining a function, giving it a name, and then passing in the name here. This right here is a complete function that takes a person as input and returns their name. So Python lambda.py, that will actually sort the people by their name-- Cho, then Draco, then Harry. Whereas if I have left off this key altogether and then tried to sort, well then we get this type error. Because we can't compare these two dictionaries. So we've seen a lot of different exceptions now throughout Python. So the very last example we'll take a look at is an example of how to deal with these exceptions, like what to do when things might go wrong if we want our program to be able to handle those possible exceptional cases, situations where things might, in fact, go wrong. So let's try an example. I'll create a new file that I'm going to call exceptions.py. And what exceptions.py is going to do is it's going to get some input. It's going to say let's get an integer input called x. And let's get an integer input called y. And then it lets go ahead and print out the result of x divided by y. So result equals x divided by y. And then let's print out something like x divided by y equals result. And we can literally print out the values of x and y. So this is a simple program that's just performing some division. Get a value of x, get a value of y, divide the two, and print out the result. We can try running this by running Python exceptions.py if I would type in like five and then 10, five divided by 10 is 0.5-- exactly what I might expect. But what could go wrong now? You remember from math in division, what could go wrong is if I type in five and then zero, try and do five divided by zero, what's going to happen? Well, when I do that, I get an exception. I get a zero division error, which is an error that happens whenever you try to divide by zero. What I'd like to happen though in this case is not for my program to display kind of a messy error and a trace-back like this, but to handle the exception gracefully, so to speak. To be able to catch when the user does something wrong and report a nicer looking message instead. And so how might I go about doing that. Well, one thing I can do here is instead of just saying result equals x over y, I can say try to do this, try to set result equal to x divided by y, and then say except if a zero division error happens. Then let's do something else. Let's print error cannot divide by zero, and then exit the program. How do you exit the program? It turns out there's a module in Python called sys. And if I import the sys module, I can say sys.exit1 to mean exit the program with a status code of one, where a status code of one generally means something went wrong in this program. So now I'm trying to divide-- x divided by y-- except I have an exception handler. This is a try-except expression. I'm saying try to do this except if this exception happens, rather than have the program crash, just print out this error message, can't divide by zero, and then exit the program. So now let's try it-- Python exceptions.py. Again, five and 10 works totally normally, gets me a value of 0.5. But now if I try five and zero, press Return, I get an error. Cannot divide by zero-- no long exception that's going to look complicated to the user. It's no longer messy. I've been able to handle the exception gracefully. Now, one other exception that might come up is what if instead of x is five, I type in a word like "hello," something that's not a number. Now I get another type of exception, a value error, which is happening when I try and convert something to an int because Hello cannot be converted into a base 10 integer. You can't take text that is not a number and turn it into an integer. So instead, I'm getting this value error here. How can I deal with that? Well, I can deal with it in much the same way. When I'm getting this input x and y, I can say rather than just get the input, just try to get the input. Except if a value error happens, which is the area that we got a moment ago, this value error, then print error invalid input, and go ahead and sys.exit1. So now I've been able to handle that error as well. I can say Python exceptions.py. I can say Hello. And I just get error invalid input. I can divide by zero. I get error cannot divide by zero. But if I do type of valid x and a y value, then I get the result of dividing one number by the other. So exception handling is often a helpful tool for if you expect that some lines of code you might be running might run into some sort of problem, be they a value error, or a zero division error, or some other error altogether, to be able to handle those errors gracefully. And that's probably what you want if you're going about building a web application using Python, is the ability to say that if something goes wrong, we want to handle the error nicely, display a nice error message to the user telling them what was wrong instead of having the program entirely crash. So those are some of the key features now with this Python programming languagem this language that gives us the ability to define these functions, and loops, and conditions in very convenient ways, to create classes where we can begin to build objects that are able to perform various different types of tasks. And next time using Python, we'll be able to design web applications such that users are able to make requests to our web applications and get some sort of response back. So we will see you next time.