[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 of 9 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.