Python Basics: Hello World Program
Python Basics: Hello World Program
VS Code is a text editor. In addition to editing text, you can visually browse
files and run text-based commands at a terminal.
In the terminal, you can execute code [Link] to start coding.
In the text editor above, you can type print("hello, world"). This is a
famous canonical program that nearly all coders write during their learning
process.
In the terminal window, you can execute commands. To run this program, you
are going to need to move your cursor to the bottom of the screen, clicking in
the terminal window. You can now type a second command in the terminal
window. Next to the dollar sign, type python [Link] and press the enter key
on your keyboard.
Recall that computers really only understand zeros and ones. Therefore, when
you run python [Link], Python will interpret the text that you created
in [Link] and translate it into the zeros and ones that the computer can
understand.
The result of running the python [Link] program is hello, world.
Congrats! You just created your first program.
Functions
Functions are verbs or actions that the computer or computer language will
already know how to perform.
In your [Link] program, the print function knows how to print to the
terminal window.
The print function takes arguments. In this case, "hello, world" are the
arguments that the print function takes.
Bugs
Bugs are a natural part of coding. These are mistakes, problems for you to
solve! Don’t get discouraged! This is part of the process of becoming a great
programmer.
Imagine that in our [Link] program we accidentally typed print("hello,
world", forgetting the final ) required by the print function. If you make this
mistake, the interpreter will output an error in the terminal window!
Error messages can often inform you of your mistakes and provide clues on
how to fix them. However, there will be many times when the interpreter is not
this helpful.
Improving Your First Python Program
We can personalize your first Python program.
In your program, you can introduce your own variable in your program
by editing it to read
Notice that this equal = sign in the middle of name = input("What's your
name? ") has a special role in programming. This equal sign literally
assigns what is on the right to what is on the left. Therefore, the value
returned by input("What's your name? ") is assigned to name.
If you edit your code as follows, you will notice an unexpected result:
Comments are a way for programmers to track what they are doing in their
programs and even inform others about their intentions for a block of code. In
short, they are notes for yourself and others who will see your code!
You can add comments to your program to be able to see what it is that
your program is doing. You might edit your code as follows:
# Ask the user for their name
name = input("What's your name? ")
print("hello,")
print(name)
Comments can also serve as a to-do list for you.
Pseudocode
Using the title method, it would title case the user’s name:
# Ask the user for their name
name = input("What's your name? ")
# Remove whitespace from the str
name = [Link]()
# Capitalize the first letter of each word
name = [Link]()
# Print the output
print(f"hello, {name}")
By this point, you might be very tired of typing python repeatedly in the
terminal window. You can use the up-arrow key on your keyboard to recall the
most recent terminal commands you have entered.
# Ask the user for their name, remove whitespace from the str and
capitalize the first letter of each word
name = input("What's your name? ").strip().title()
# Print the output
print(f"hello, {name}")
You can learn more about strings in Python’s documentation on str
Integers or int
In Python, an integer is referred to as an int.
In the world of mathematics, we are familiar with +, -, *, /, and % operators.
That last operator % or modulo operator may not be very familiar to you.
You don’t have to use the text editor window to run Python code. Down in your
terminal, you can run python alone. You will be presented with >>> in the
terminal window. You can then run live, interactive code. You could type 1+1,
and it will run that calculation. This mode will not commonly be used during
this course.
Opening up VS Code again, we can type code [Link] in the terminal.
This will create a new file in which we will create our own calculator.
x = 1
y = 2
z = x + y
print(z)
z = x + y
print(z)
Running this program, we discover that the output is incorrect as 12. Why
might this be?
Prior, we have seen how the + sign concatenates two strings. Because
your input from your keyboard on your computer comes into the
interpreter as text, it is treated as a string. We, therefore, need to
convert this input from a string to an integer. We can do so as follows:
x = input("What's x? ")
y = input("What's y? ")
z = int(x) + int(y)
print(z)
The result is now correct. The use of int(x) is called “casting,” where a
value is temporarily changed from one type of variable (in this case, a
string) to another (here, an integer).
x = int(input("What's x? "))
y = int(input("What's y? "))
print(x + y)
This illustrates that you can run functions on functions. The inner
function is run first, and then the outer one is run. First,
the input function is run. Then, the int function.
x = float(input("What's x? "))
y = float(input("What's y? "))
print(x + y)
This change allows your user to enter 1.2 and 3.4 to present a total
of 4.6.
Let’s imagine, however, that you want to round the total to the nearest
integer. Looking at the Python documentation for round, you’ll see that
the available arguments are round(number[, ndigits]). Those square
brackets indicate that something optional can be specified by the
programmer. Therefore, you could do round(n) to round a digit to its
nearest integer. Alternatively, you could code as follows:
# Get the user's input
x = float(input("What's x? "))
y = float(input("What's y? "))
# Create a rounded result
z = round(x + y)
# Print the result
print(z)
Let’s imagine that we want to round this down. We could modify our
code as follows:
As we might expect, this will round the result to the nearest two
decimal points.
This cryptic f-string code displays the same as our prior rounding
strategy.
Let’s bring back our final code of [Link] by typing code [Link] into
the terminal window. Your starting code should look as follows:
# Ask the user for their name, remove whitespace from the str and
capitalize the first letter of each word
name = input("What's your name? ").strip().title()
# Print the output
print(f"hello, {name}")
We can better our code to create our own special function that says
“hello” for us!
Erasing all our code in our text editor, let’s start from scratch:
Attempting to run this code, your interpreter will throw an error. After
all, there is no defined function for hello.
Here, in the first lines, you are creating your hello function. This time,
however, you are telling the interpreter that this function takes a single
parameter: a variable called to. Therefore, when you
call hello(name) the computer passes name into the hello function as to.
This is how we pass values into functions. Very useful! Running python
[Link] in the terminal window, you’ll see that the output is much
closer to our ideal presented earlier in this lecture.
Test out your code yourself. Notice how the first hello will behave as
you might expect, and the second hello, which is not passed a value,
will, by default, output hello, world.
We don’t have to have our function at the start of our program. We can
move it down, but we need to tell the interpreter that we have
a main function and a separate hello function.
def main():
# Output using our own function
name = input("What's your name? ")
hello(name)
# Output without passing the expected arguments
hello()
# Create our own function
def hello(to="world"):
print("hello,", to)
This alone, however, will create an error of sorts. If we run python
[Link], nothing happens! The reason for this is that nothing in this
code is actually calling the main function and bringing our program to
life.
The following very small modification will call the main function and
restore our program to working order:
def main():
# Output using our own function
name = input("What's your name? ")
hello(name)
# Output without passing the expected arguments
hello()
# Create our own function
def hello(to="world"):
print("hello,", to)
main()
Returning Values
You can imagine many scenarios where you don’t just want a function to
perform an action but also to return a value back to the main function. For
example, rather than simply printing the calculation of x + y, you may want a
function to return the value of this calculation back to another part of your
program. This “passing back” of a value we call a return value.
True
True
True
False
False
False
start
x<y
"x is less than y"
x>y
"x is greater than y"
x == y
"x is equal to y"
stop
Notice how the use of elif allows the program to make fewer decisions.
First, the if statement is evaluated. If this statement is found to be true,
all the elif statements will not be run at all. However, if
the if statement is evaluated and found to be false, the first elif will be
evaluated. If this is true, it will not run the final evaluation.
True
False
True
False
True
False
start
x<y
"x is less than y"
x>y
"x is greater than y"
x == y
"x is equal to y"
stop
True
False
True
False
start
x<y
"x is less than y"
x>y
"x is greater than y"
"x is equal to y"
stop
or
or allows your program to decide between one or more alternatives. For
example, we could further edit our program as follows:
x = int(input("What's x? "))
y = int(input("What's y? "))
if x < y or x > y:
print("x is not equal to y")
else:
print("x is equal to y")
Notice that the result of our program is the same, but the complexity is
decreased. The efficiency of our code is increased.
At this point, our code is pretty great. However, could the design be
further improved? We could further edit our code as follows:
x = int(input("What's x? "))
y = int(input("What's y? "))
if x != y:
print("x is not equal to y")
else:
print("x is equal to y")
Notice how we removed the or entirely and simply asked, “Is x not
equal to y?” We ask one and only one question. Very efficient!
Notice that the == operator evaluates if what is on the left and right are
equal to one another. The use of double equal signs is very important. If
you use only one equal sign, an error will likely be thrown by the
interpreter.
True
False
start
x == y
"x is equal to y"
"x is not equal to y"
stop
and
Similar to or, and can be used within conditional statements.
Execute in the terminal window code [Link]. Start your new program
as follows:
score = int(input("Score: "))
if score >= 90 and score <= 100:
print("Grade: A")
elif score >=80 and score < 90:
print("Grade: B")
elif score >=70 and score < 80:
print("Grade: C")
elif score >=60 and score < 70:
print("Grade: D")
else:
print("Grade: F")
Notice that by executing python [Link], you will be able to input a
score and get a grade. However, notice how there is potential for bugs.
Typically, we do not want to ever trust our users to input the correct
information. We could improve our code as follows:
score = int(input("Score: "))
if 90 <= score <= 100:
print("Grade: A")
elif 80 <= score < 90:
print("Grade: B")
elif 70 <= score < 80:
print("Grade: C")
elif 60 <= score < 70:
print("Grade: D")
else:
print("Grade: F")
Notice how Python allows you to chain together the operators and
conditions in a way quite uncommon to other programming languages.
Notice how our users can type in any number 1 or greater to see if it is
even or odd.
Creating Our Own Parity Function
As discussed in Lecture 0, you will find it useful to create a function of
your own!
We can create our own function to check whether a number is even or
odd. Adjust your code as follows:
def main():
x = int(input("What's x? "))
if is_even(x):
print("Even")
else:
print("Odd")
def is_even(n):
if n % 2 == 0:
return True
else:
return False
main()
Notice that this return statement in our code is almost like a sentence in
English. This is a unique way of coding only seen in Python.
We can further revise our code and make it more and more readable:
def main():
x = int(input("What's x? "))
if is_even(x):
print("Even")
else:
print("Odd")
def is_even(n):
return n % 2 == 0
main()
Notice that the program will evaluate what is happening within the n %
2 == 0 as either True or False and simply return that to the main
function.
match
Similar to if, elif, and else statements, match statements can be used
to conditionally run code that matches certain values.
Consider the following program:
name = input("What's your name? ")
if name == "Harry":
print("Gryffindor")
elif name == "Hermione":
print("Gryffindor")
elif name == "Ron":
print("Gryffindor")
elif name == "Draco":
print("Slytherin")
else:
print("Who?")
Notice the first three conditional statements print the same response.
We can improve this code slightly with the use of the or keyword:
name = input("What's your name? ")
if name == "Harry" or name == "Hermione" or name == "Ron":
print("Gryffindor")
elif name == "Draco":
print("Slytherin")
else:
print("Who?")
Notice the use of the _ symbol in the last case. This will match with any
input, resulting in similar behavior as an else statement.
Notice, the use of the single vertical bar |. Much like the or keyword,
this allows us to check for multiple values in the same case statement.
Summing Up
You now have the power within Python to use conditional statements to ask
questions and have your program take action accordingly. In this lecture, we
discussed…
Conditionals;
if Statements;
Control flow, elif, and else;
or;
and;
Modulo;
Creating your own function;
Pythonic coding;
and match.
Loops
Running this code by typing python [Link], you’ll notice that the
program meows three times.
Notice how even though this code will execute print("meow") multiple
times, it will never stop! It will loop forever. while loops work by
repeatedly asking if the condition of the loop has been fulfilled. In this
case, the interpreter is asking, “does i not equal zero?” When you get
stuck in a loop that executes forever, you can press Ctrl+C on your
keyboard to break out of the loop.
To fix this loop that lasts forever, we can edit our code as follows
i = 3
while i != 0:
print("meow")
i = i - 1
Notice that now our code executes properly, reducing i by 1 for each
“iteration” through the loop. The term iteration has special significance
within coding. By iteration, we mean one cycle through the loop. The
first iteration is the “0th” iteration through the loop. The second is the
“1st” iteration. In programming, we count starting with 0, then 1, then
2.
Notice how changing the operator to i < 3 allows our code to function
as intended. We begin by counting with 0 and it iterates through our
loop three times, producing three meows. Also, notice how i += 1 is the
same as saying i = i + 1.
True
False
start
i=0
i<3
"meow"
i += 1
stop
Notice how clean this code is compared to your previous while loop
code. In this code, i begins with 0, meows, i is assigned 1, meows, and,
finally, i is assigned 2, meows, and then ends.
Notice how it will meow three times, but the program will
produce meowmeowmeow as the result. Consider: How could you create a
line break at the end of each meow?
Notice how this code produces three meows, each on a separate line.
By adding end="" and the \n we tell the interpreter to add a line break at
the end of each meow.
Improving with User Input
Perhaps we want to get input from our user. We can use loops as a way
of validating the input of the user.
A common paradigm within Python is to use a while loop to validate the
input of the user.
For example, let’s try prompting the user for a number greater than or
equal to 0:
while True:
n = int(input("What's n? "))
if n < 0:
continue
else:
break
Notice that we’ve introduced two new keywords in
Python, continue and break. continue explicitly tells Python to go to the
next iteration of a loop. break, on the other hand, tells Python to “break
out” of a loop early before it has finished all of its iterations. In this
case, we’ll continue to the next iteration of the loop when n is less than
0—ultimately reprompting the user with “What’s n?”. If, though, n is
greater than or equal to 0, we’ll break out of the loop and allow the rest
of our program to run.
It turns out that the continue keyword is redundant in this case. We can
improve our code as follows:
while True:
n = int(input("What's n? "))
if n > 0:
break
for _ in range(n):
print("meow")
Notice how this while loop will always run (forever) until n is greater
than 0. When n is greater than 0, the loop breaks.
Notice how not only did we change your code to operate in multiple
functions, but we also used a return statement to return the value
of n back to the main function.
More About Lists
Consider the world of Hogwarts from the famed Harry Potter universe.
In the terminal, type code [Link].
In the text editor, code as follows:
students = ["Hermione", "Harry", "Ron"]
print(students[0])
print(students[1])
print(students[2])
Notice that for each student in the students list, it will print the student
as intended. You might wonder why we did not use the _ designation as
discussed prior. We choose not to do this because student is explicitly
used in our code.
Notice how executing this code results in not only getting the position of
each student plus one using i + 1, but also prints the name of each
student. len allows you to dynamically see how long the list of the
students is regardless of how much it grows.
Notice that we can promise that we will always keep these lists in order.
The individual at the first position of students is associated with the
house at the first position of the houses list, and so on. However, this
can become quite cumbersome as our lists grow!
Notice how students[student] will go to each student’s key and find the
value of their house. Execute your code, and you’ll notice how the
output is a bit messy.
Now, you have access to a whole host of interesting data about these
students. Now, further modify your code as follows:
students = [
{"name": "Hermione", "house": "Gryffindor", "patronus": "Otter"},
{"name": "Harry", "house": "Gryffindor", "patronus": "Stag"},
{"name": "Ron", "house": "Gryffindor", "patronus": "Jack Russell
terrier"},
{"name": "Draco", "house": "Slytherin", "patronus": None},
]
for student in students:
print(student["name"], student["house"], student["patronus"], sep=",
")
Notice how the for loop will iterate through each of the dicts inside
the list called students.
Notice how our column can grow as much as we want without any hard
coding.
Now, let’s try to print a row horizontally. Modify your code as follows:
def main():
print_row(4)
def print_row(width):
print("?" * width)
main()
Notice how we now have code that can create left-to-right blocks.
Examining the slide below, notice how Mario has both rows and columns
of blocks.
Consider: How could we implement both rows and columns within our
code? Modify your code as follows:
def main():
print_square(3)
def print_square(size):
# For each row in square
for i in range(size):
# For each brick in row
for j in range(size):
# Print brick
print("#", end="")
# Print blank line
print()
main()
Notice that we have an outer loop that addresses each row in the
square. Then, we have an inner loop that prints a brick in each row.
Finally, we have a print statement that prints a blank line.
Loops
while
for
len
list
dict
Exceptions
This is still not the best way to implement this code. Notice that we are
trying to do two lines of code. For best practice, we should only try the
fewest lines of code possible that we are concerned could fail. Adjust
your code as follows:
try:
x = int(input("What's x?"))
except ValueError:
print("x is not an integer")
print(f"x is {x}")
Notice that while this accomplishes our goal of trying as few lines as
possible, we now face a new error! We face a NameError where x is not
defined. Look at this code and consider: Why is x not defined in some
cases?
Notice that if no exception occurs, it will then run the block of code
within else. Running python [Link] and supplying 50, you’ll notice
that the result will be printed. Trying again, this time supplying cat,
you’ll notice that the program now catches the error.
Considering improving our code, notice that we are being a bit rude to
our user. If our user does not cooperate, we currently simply end our
program. Consider how we can use a loop to prompt the user for x and if
they don’t prompt again!
while True:
try:
x = int(input("What's x?"))
except ValueError:
print("x is not an integer")
else:
break
print(f"x is {x}")
Notice that while True will loop forever. If the user succeeds in
supplying the correct input, we can break from the loop and then print
the output. Now, a user that inputs something incorrectly will be asked
for input again.
Creating a Function to Get an Integer
Surely, there are many times that we would want to get an integer from
our user. Modify your code as follows:
def main():
x = get_int()
print(f"x is {x}")
def get_int():
while True:
try:
x = int(input("What's x?"))
except ValueError:
print("x is not an integer")
else:
break
return x
main()
Even still, we can improve this program. Consider what else you could
do to improve this program. Modify your code as follows:
def main():
x = get_int()
print(f"x is {x}")
def get_int():
while True:
try:
x = int(input("What's x?"))
except ValueError:
print("x is not an integer")
else:
return x
main()
Notice that return will not only break you out of a loop, but it will also
return a value.
Notice this does the same thing as the previous iteration of our code,
simply with fewer lines.
pass
We can make it such that our code does not warn our user, but simply
re-asks them our prompting question by modifying our code as follows:
def main():
x = get_int()
print(f"x is {x}")
def get_int():
while True:
try:
return int(input("What's x?"))
except ValueError:
pass
main()
Notice that our code will still function but will not repeatedly inform the
user of their error. In some cases, you’ll want to be very clear to the
user what error is being produced. Other times, you might decide that
you simply want to ask them for input again.
Exceptions
Value Errors
Runtime Errors
try
else
pass
Libraries
Generally, libraries are bits of code written by you or others that you
can use in your program.
Python allows you to share functions or features with others as
“modules”.
If you copy and paste code from an old project, chances are you can
create such a module or library that you could bring into your new
project.
Random
random is a library that comes with Python that you could import into
your own project.
It’s easier as a coder to stand on the shoulders of prior coders.
So, how do you load a module into your own program? You can use the
word import in your program.
Inside the random module, there is a built-in function
called [Link](seq). random is the module you are importing.
Inside that module, there is the choice function. That function takes into
it a seq or sequence that is a list.
In your terminal window type code [Link]. In your text editor, code
as follows:
import random
coin = [Link](["heads", "tails"])
print(coin)
Notice that the list within choice has square braces, quotes, and a
comma. Since you have passed in two items, Python does the math and
gives a 50% chance for heads and tails. Running your code, you will
notice that this code, indeed, does function well!
We can improve our code. from allows us to be very specific about what
we’d like to import. Prior, our import line of code is bringing the entire
contents of the functions of random. However, what if we want to only
load a small part of a module? Modify your code as follows:
from random import choice
coin = choice(["heads", "tails"])
print(coin)
Notice that we now can import just the choice function of random. From
that point forward, we no longer need to code [Link]. We can
now only code choice alone. choice is loaded explicitly into our program.
This saves system resources and potentially can make our code run
faster!
Moving on, consider the function [Link](a, b). This function will
generate a random number between a and b. Modify your code as
follows:
import random
number = [Link](1, 10)
print(number)
Notice that [Link] will shuffle the cards in place. Unlike other
functions, it will not return a value. Instead, it will take the cards list and
shuffle them inside that list. Run your code a few times to see the code
functioning.
Notice that the program is going to look at what the user typed in the
command line. Currently, if you type python [Link] David into the
terminal window, you will see hello, my name is David. Notice
that [Link][1] is where David is being stored. Why is that? Well, in
prior lessons, you might remember that lists start at the 0th element.
What do you think is held currently in [Link][0]? If you
guessed [Link], you would be correct!
There is a small problem with our program as it stands. What if the user
does not type in the name at the command line? Try it yourself.
Type python [Link] into the terminal window. An error list index out
of range will be presented by the interpreter. The reason for this is that
there is nothing at [Link][1] because nothing was typed! Here’s how
we can protect our program from this type of error:
import sys
try:
print("hello, my name is", [Link][1])
except IndexError:
print("Too few arguments")
Notice that the user will now be prompted with a useful hint about how
to make the program work if they forget to type in a name. However,
could we be more defensive to ensure the user inputs the right values?
Notice how we are using a built-in function of sys called exit that allows
us to exit the program if an error was introduced by the user. We can
rest assured now that the program will never execute the final line of
code and trigger an error. Therefore, [Link] provides a way by which
users can introduce information from the command
line. [Link] provides a means by which the program can exit if an
error arises.
Notice that if you type python [Link] David Carter Rongxin into the
terminal window, the interpreter will output not just the intended output
of the names, but also hello, my name is [Link]. How then could we
ensure that the interpreter ignores the first element of the list
where [Link] is currently being stored?
slice can be employed in our code to start the list somewhere different!
Modify your code as follows:
import sys
if len([Link]) < 2:
[Link]("Too few arguments")
for arg in [Link][1:]:
print("hello, my name is", arg)
Notice that rather than starting the list at 0, we use square brackets to
tell the interpreter to start at 1 and go to the end using
the 1: argument. Running this code, you’ll notice that we can improve
our code using relatively simple syntax.
Packages
One of the reasons Python is so popular is that there are numerous
powerful third-party libraries that add functionality. We call these third-
party libraries, implemented as a folder, “packages”.
PyPI is a repository or directory of all available third-party packages
currently available.
cowsay is a well-known package that allows a cow to talk to the user.
Python has a package manager called pip that allows you to install
packages quickly onto your system.
In the terminal window, you can install the cowsay package by typing pip
install cowsay. After a bit of output, you can now go about using this
package in your code.
In your terminal window type code [Link]. In the text editor, code as
follows:
import cowsay
import sys
if len([Link]) == 2:
[Link]("hello, " + [Link][1])
Notice that the program first checks that the user inputted at least two
arguments at the command line. Then, the cow should speak to the
user. Type python [Link] David and you’ll see a cow saying “hello” to
David.
You now can see how you could install third-party packages.
You can learn more on PyPI’s entry for cowsay
You can find other third-party packages at PyPI
APIs
APIs or “application program interfaces” allow you to connect to the
code of others.
requests is a package that allows your program to behave as a web
browser would.
In your terminal, type pip install requests. Then, type code [Link].
It turns out that Apple iTunes has its own API that you can access in
your programs. In your internet browser, you can
visit [Link]
entity=song&limit=1&term=weezer and a text file will be downloaded.
David constructed this URL by reading Apple’s API documentation.
Notice how this query is looking for a song, with a limit of one result,
that relates to the term called weezer. Looking at this text file that is
downloaded, you might find the format to be similar to that we’ve
programmed previously in Python.
The format in the downloaded text file is called JSON, a text-based
format that is used to exchange text-based data between applications.
Literally, Apple is providing a JSON file that we could interpret in our
own Python program.
In the terminal window, type code [Link]. Code as follows:
import requests
import sys
if len([Link]) != 2:
[Link]()
response = [Link]("[Link]
entity=song&limit=1&term=" + [Link][1])
print([Link]())
It turns out that Python has a built-in JSON library that can help us
interpret the data received. Modify your code as follows:
import json
import requests
import sys
if len([Link]) != 2:
[Link]()
response = [Link]("[Link]
entity=song&limit=1&term=" + [Link][1])
print([Link]([Link](), indent=2))
How could we simply output the name of just that track name? Modify
your code as follows:
import json
import requests
import sys
if len([Link]) != 2:
[Link]()
response = [Link]("[Link]
entity=song&limit=50&term=" + [Link][1])
o = [Link]()
for result in o["results"]:
print(result["trackName"])
You can learn more about requests through the library’s documentation.
You can learn more about JSON in Python’s documentation of JSON.
Making Your Own Libraries
You have the ability as a Python programmer to create your own library!
Imagine situations where you may want to re-use bits of code time and
time again or even share them with others!
We have been writing lots of code to say “hello” so far in this course.
Let’s create a package to allow us to say “hello” and “goodbye”. In your
terminal window, type code [Link]. In the text editor, code as
follows:
def hello(name):
print(f"hello, {name}")
def goodbye(name):
print(f"goodbye, {name}")
Notice that this code in and of itself does not do anything for the user.
However, if a programmer were to import this package into their own
program, the abilities created by the functions above could be
implemented in their code.
Let’s see how we could implement code utilizing this package that we
created. In the terminal window, type code [Link]. In this new file in
your text editor, type the following:
import sys
from sayings import goodbye
if len([Link]) == 2:
goodbye([Link][1])
Libraries
Random
Statistics
Command-Line Arguments
Slice
Packages
APIs
Making Your Own Libraries
FILES
Notice that running this code has the desired output. The user can input
a name. The output is as expected.
Notice that the user will be prompted three times for input.
The append method is used to add the name to our names list.
Notice that this has the same result as the prior block of code.
o Now, let’s enable the ability to print the list of names as a sorted
list. Code as follows:
names = []
for _ in range(3):
[Link](input("What's your name?" ))
Notice that the open function opens a file called [Link] with writing
enabled, as signified by the w. The code above assigns that opened file
to a variable called file. The line [Link](name) writes the name to
the text file. The line after that closes the file.
Testing out your code by typing python [Link], you can input a name
and it saves to the text file. However, if you run your program multiple
times using different names, you will notice that this program will
entirely rewrite the [Link] file each time.
Ideally, we want to be able to append each of our names to the file.
Remove the existing text file by typing rm [Link] in the terminal
window. Then, modify your code as follows:
name = input("What's your name? ")
file = open("[Link]", "a")
[Link](name)
[Link]()
Notice that the only change to our code is that the w has been changed
to a for “append”. Rerunning this program multiple times, you will
notice that names will be added to the file. However, you will notice a
new problem!
Examining your text file after running your program multiple times,
you’ll notice that the names are running together. The names are being
appended without any gaps between each of the names. You can fix
this issue. Again, remove the existing text file by typing rm [Link] in
the terminal window. Then, modify your code as follows:
name = input("What's your name? ")
file = open("[Link]", "a")
[Link](f"{name}\n")
[Link]()
Notice that the line with [Link] has been modified to add a line
break at the end of each name.
This code is working quite well. However, there are ways to improve this
program. It so happens that it’s quite easy to forget to close the file.
You can learn more in Python’s documentation of open.
with
The keyword with allows you to automate the closing of a file.
Modify your code as follows:
name = input("What's your name? ")
with open("[Link]", "a") as file:
[Link](f"{name}\n")
Notice that readlines has a special ability to read all the lines of a file
and store them in a list called lines. Running your program, you will
notice that the output is quite ugly. There seem to be multiple line
breaks where there should be only one.
There are many approaches to fix this issue. However, here is a simple
way to fix this error in our code:
with open("[Link]", "r") as file:
lines = [Link]()
for line in lines:
print("hello,", [Link]())
Notice that rstrip has the effect of removing the extraneous line break
at the end of each line.
Still, this code could be simplified even further:
with open("[Link]", "r") as file:
for line in file:
print("hello,", [Link]())
Notice that running this code, it is correct. However, notice that we are
not sorting the names.
This code could be further improved to allow for the sorting of the
names:
names = []
with open("[Link]") as file:
for line in file:
[Link]([Link]())
for name in sorted(names):
print(f"hello, {name}")
Notice that names is a blank list where we can collect the names. Each
name is appended to the names list in memory. Then, each name in the
sorted list in memory is printed. Running your code, you will see that
the names are now properly sorted.
What if we wanted the ability to store more than just the names of
students? What if we wanted to store both the student’s name and their
house as well?
CSV
CSV stands for “comma separated values”.
In your terminal window, type code [Link]. Ensure your new CSV
file looks like the following:
Hermione,Gryffindor
Harry,Gryffindor
Ron,Gryffindor
Draco,Slytherin
Let’s create a new program by typing code [Link] and code as
follows:
with open("[Link]") as file:
for line in file:
row = [Link]().split(",")
print(f"{row[0]} is in {row[1]}")
Notice that rstrip removes the end of each line in our CSV
file. split tells the interpreter where to find the end of each of our
values in our CSV file. row[0] is the first element in each line of our CSV
file. row[1] is the second element in each line in our CSV file.
The above code is effective at dividing each line or “record” of our CSV
file. However, it’s a bit cryptic to look at if you are unfamiliar with this
type of syntax. Python has built-in ability that could further simplify this
code. Modify your code as follows:
with open("[Link]") as file:
for line in file:
name, house = [Link]().split(",")
print(f"{name} is in {house}")
Notice that the split function actually returns two values: The one
before the comma and the one after the comma. Accordingly, we can
rely upon that functionality to assign two variables at once instead of
one!
Imagine that we would again like to provide this list as sorted output?
You can modify your code as follows:
students = []
with open("[Link]") as file:
for line in file:
name, house = [Link]().split(",")
[Link](f"{name} is in {house}")
for student in sorted(students):
print(student)
Notice that this produces the desired outcome, minus the sorting of
students.
Notice that sorted needs to know how to get the key of each student.
Python allows for a parameter called key where we can define on what
“key” the list of students will be sorted. Therefore, the get_name function
simply returns the key of student["name"]. Running this program, you
will now see that the list is now sorted by name.
Still, our code can be further improved upon. It just so happens that if
you are only going to use a function like get_name once, you can simplify
your code in the manner presented below. Modify your code as follows:
students = []
with open("[Link]") as file:
for line in file:
name, house = [Link]().split(",")
[Link]({"name": name, "house": house})
for student in sorted(students, key=lambda student: student["name"]):
print(f"{student['name']} is in {student['house']}")
Now that we’re dealing with homes instead of houses, modify your code
as follows:
students = []
with open("[Link]") as file:
for line in file:
name, home = [Link]().split(",")
[Link]({"name": name, "home": home})
for student in sorted(students, key=lambda student: student["name"]):
print(f"{student['name']} is in {student['home']}")
Notice that running our program still does not work properly. Can you
guess why?
Notice how we are explicitly saying in our CSV file that anything reading
it should expect there to be a name value and a home value in each
line.
We can modify our code to use a part of the csv library called
a DictReader to treat our CSV file with even more flexibilty:
import csv
students = []
with open("[Link]") as file:
reader = [Link](file)
for row in reader:
[Link]({"name": row["name"], "home": row["home"]})
for student in sorted(students, key=lambda student: student["name"]):
print(f"{student['name']} is in {student['home']}")
Notice that we have replaced reader with DictReader, which returns one
dictionary at a time. Also, notice that the interpreter will directly access
the row dictionary, getting the name and home of each student. This is an
example of coding defensively. As long as the person designing the CSV
file has inputted the correct header information on the first line, we can
access that information using our program.
Up until this point, we have been reading CSV files. What if we want to
write to a CSV file?
To begin, let’s clean up our files a bit. First, delete the [Link] file
by typing rm [Link] in the terminal window. This command will
only work if you’re in the same folder as your [Link] file.
Then, in [Link], modify your code as follows:
import csv
name = input("What's your name? ")
home = input("Where's your home? ")
with open("[Link]", "a") as file:
writer = [Link](file, fieldnames=["name", "home"])
[Link]({"name": name, "home": home})
Note that there are many types of files that you can read from and write
to.
You can learn more in Python’s documentation of CSV.
Binary Files and PIL
One more type of file that we will discuss today is a binary file. A binary
file is simply a collection of ones and zeros. This type of file can store
anything including, music and image data.
There is a popular Python library called PIL that works well with image
files.
Animated GIFs are a popular type of image file that has many image
files within it that are played in sequence over and over again, creating
a simplistic animation or video effect.
Imagine that we have a series of costumes, as illustrated below.
Here is [Link].
Here is another one called [Link]. Notice how the leg positions
are slightly different.
Before proceeding, please make sure that you have downloaded the
source code files from the course website. It will not be possible for you
to code the following without having the two images above in your
possession and stored in your IDE.
In the terminal window type code [Link] and code as follows:
import sys
from PIL import Image
images = []
for arg in [Link][1:]:
image = [Link](arg)
[Link](image)
images[0].save(
"[Link]", save_all=True, append_images=[images[1]],
duration=200, loop=0
)
Notice that we import the Image functionality from PIL. Notice that the
first for loop simply loops through the images provided as command-
line arguments and stores theme into the list called images.
The 1: starts slicing argv at its second element. The last lines of code
saves the first image and also appends a second image to it as well,
creating an animated gif. Typing python [Link] [Link]
[Link] into the terminal. Now, type code [Link] into the
terminal window, and you can now see an animated GIF.
File I/O
open
with
CSV
PIL
REGULAR EXPRESSIONS
Notice that strip will remove whitespace at the beginning or end of the
input. Running this program, you will see that as long as an @ symbol is
inputted, the program will regard the input as valid.
You can imagine, however, that one could input @@ alone and the input
could be regarded as valid. We could regard an email address as having
at least one @ and a . somewhere within it. Modify your code as follows:
email = input("What's your email? ").strip()
if "@" in email and "." in email:
print("Valid")
else:
print("Invalid")
Notice that while this works as expected, our user could be adversarial,
typing simply @. would result in the program returning valid.
Notice how the strip method is used to determine if username exists and
if . is inside the domain variable. Running this program, a standard email
address typed in by you could be considered valid. Typing
in malan@harvard alone, you’ll find that the program regards this input
as invalid.
Notice this does not increase the functionality of our program at all. In
fact, it is somewhat a step back.
Notice that we don’t care what the username or domain is. What we
care about is the pattern. .+ is used to determine if anything is to the
left of the email address and if anything is to the right of the email
address. Running your code, typing in malan@, you’ll notice that the input
is regarded as invalid as we would hope.
Had we used a regular expression .*@.* in our code above, you can
visualize this as
follows:
Notice the depiction of the state machine of our regular expression. On
the left, the interpreter begins evaluating the statement from left to
right. Once we reach q1 or question 1, the interpreter reads time and
time again based on the expression handed to it. Then, the state is
changed looking now at q2 or the second question being validated.
Again, the arrow indicates how the expression will be evaluated time
and time again based upon our programming. Then, as depicted by the
double circle, the final state of state machine is reached.
Considering the regular expression we used in our code, .+@.+, you can
visualize it as
follows:
Notice how q1 is any character provided by the user, including ‘q2’ as 1
or more repetitions of characters. This is followed by the ‘@’ symbol.
Then, q3 looks for any character provided by the user, including q4 as 1
or more repetitions of characters.
The re and [Link] functions and ones like them look for patterns.
Continuing our improvement of this code, we could improve our code as
follows:
import re
email = input("What's your email? ").strip()
if [Link](".+@.+.edu", email):
print("Valid")
else:
print("Invalid")
Now that we’re using escape characters, it’s a good time to introduce
“raw strings”. In Python, raw strings are strings that don’t format
special characters—instead, each character is taken at face-value.
Imagine \n, for example. We’ve seen in an earlier lecture how, in a
regular string, these two characters become one: a special newline
character. In a raw string, however, \n is treated not as \n, the special
character, but as a single \ and a single n. Placing an r in front of a
string tells the Python interpreter to treat the string as a raw string,
similar to how placing an f in front of a string tells the Python
interpreter to treat the string as a format string:
import re
email = input("What's your email? ").strip()
if [Link](r"^.+@.+\.edu$", email):
print("Valid")
else:
print("Invalid")
You can imagine still how our users could create problems for us! For
example, you could type in a sentence such as My email address is
malan@[Link]. and this whole sentence would be considered valid.
We can be even more precise in our coding.
It just so happens we have more special symbols at our disposal in
validation:
^ matches the start of the string
$ matches the end of the string or just before the newline at the end
of the string
We can modify our code using our added vocabulary as follows:
import re
email = input("What's your email? ").strip()
if [Link](r"^.+@.+\.edu$", email):
print("Valid")
else:
print("Invalid")
Notice this has the effect of looking for this exact pattern matching to
the start and end of the expression being validated. Typing in a
sentence such as My email address is malan@[Link]. now is
regarded as invalid.
We propose we can do even better! Even though we are now looking for
the username at the start of the string, the @ symbol, and the domain
name at the end, we could type in as many @ symbols as we
wish! malan@@@[Link] is considered valid!
We can add to our vocabulary as follows:
[] set of characters
[^] complementing the set
Using these newfound abilities, we can modify our expression as
follows:
import re
email = input("What's your email? ").strip()
if [Link](r"^[^@]+@[^@]+\.edu$", email):
print("Valid")
else:
print("Invalid")
Notice that ^ means to match at the start of the string. All the way at
the end of our expression, $ means to match at the end of the
string. [^@]+ means any character except an @. Then, we have a
literal @. [^@]+\.edu means any character except an @ followed by an
expression ending in .edu. Typing in malan@@@[Link] is now
regarded as invalid.
We can still improve this regular expression further. It turns out there
are certain requirements for what an email address can be! Currently,
our validation expression is far too accommodating. We might only want
to allow for characters normally used in a sentence. We can modify our
code as follows:
import re
email = input("What's your email? ").strip()
if [Link](r"^[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\.edu$", email):
print("Valid")
else:
print("Invalid")
Adding even more symbols to our vocabulary, here are some more to
consider:
A|B either A or B
(...) a group
(?:...) non-capturing version
Case Sensitivity
To illustrate how you might address issues around case sensitivity,
where there is a difference between EDU and edu and the like, let’s
rewind our code to the following:
import re
email = input("What's your email? ").strip()
if [Link](r"^\w+@\w+\.edu$", email):
print("Valid")
else:
print("Invalid")
Recall that within the [Link] function, there is a parameter for flags.
Some built-in flag variables are:
[Link]
[Link]
[Link]
Consider how you might use these in your code.
Notice how the (\w+\.)? communicates to the interpreter that this new
expression can be there once or not at all. Hence,
both malan@[Link] and malan@[Link] are considered valid.
Interestingly enough, the edits we have done so far to our code do not
fully encompass all the checking that could be done to ensure a valid
email address. Indeed, here is the full expression that one would have
to type to ensure that a valid email is inputted:
^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-
zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
There are other functions within the re library you might find
useful. [Link] and [Link] are ones you might find exceedingly
useful.
You can learn more in Python’s documentation of re.
Cleaning Up User Input
You should never expect your users to always follow your hopes for
clean input. Indeed, users will often violate your intentions as a
programmer.
There are ways to clean up your data.
In the terminal window, type code [Link]. Then, in the text-editor,
code as follows:
name = input("What's your name? ").strip()
print(f"hello, {name}")
You might notice that typing in Malan,David with no space causes the
interpreter to throw an error. Since we now know some regular
expression syntax, let’s apply that to our code:
import re
name = input("What's your name? ").strip()
matches = [Link](r"^(.+), (.+)$", name)
if matches:
last, first = [Link]()
name = first + " " + last
print(f"hello, {name}")
Notice that [Link] can return a set of matches that are extracted
from the user’s input. If matches are returned by [Link]. Running
this program, typing in David Malan notice how the if condition is not
run and the name is returned. If you run the program by typing Malan,
David, the name is also returned properly.
It just so happens that we can request specific groups back
using [Link]. We can modify our code as follows:
import re
name = input("What's your name? ").strip()
matches = [Link](r"^(.+), (.+)$", name)
if matches:
name = [Link](2) + " " + [Link](1)
print(f"hello, {name}")
Recognize still that typing in Malan,David with no space will still break
our code. Therefore, we can make the following modification:
import re
name = input("What's your name? ").strip()
matches = [Link](r"^(.+), *(.+)$", name)
if matches:
name = [Link](2) + " " + [Link](1)
print(f"hello, {name}")
Notice the addition of the * in our validation statement. This code will
now accept and properly process Malan,David. Further, it will properly
handle ` David,Malan with many spaces in front of David`.
Notice how we combine two lines of our code. The walrus := operator
assigns a value from right to left and allows us to ask a boolean
question at the same time. Turn your head sideways and you’ll see why
this is called a walrus operator.
You can imagine how we would simply be able to get rid of the
beginning of the standard Twitter URL. We can attempt this as follows:
url = input("URL: ").strip()
username = [Link]("[Link] "")
print(f"Username: {username}")
Notice how the replace method allows us to find one item and replace it
with another. In this case, we are finding part of the URL and replacing
it with nothing. Typing in the full URL [Link]
the program effectively outputs the username. However, what are some
shortcomings of this current program?
Notice how pattern refers to the regular expression we are looking for.
Then, there is a repl string that we can replace the pattern with. Finally,
there is the string that we want to do the substitution on.
The protocol, subdomain, and the possibility that the user inputted any
part of the URL after the username are all reasons that this code is still
not ideal. We can further address these shortcomings as follows:
import re
url = input("URL: ").strip()
username = [Link](r"^(https?://)?(www\.)?twitter\.com/", "", url)
print(f"Username: {username}")
Notice how the ^ caret was added to the url. Notice also how the . could
be interpreted improperly by the interpreter. Therefore, we escape it
using a \ to make it \. For the purpose of tolerating both http and https,
we add a ? to the end of https?, making the s optional. Further, to
accommodate www we add (www\.)? to our code. Finally, just in case the
user decides to leave out the protocol altogether,
the http:// or https:// is made optional using (https?://).
Still, we are blindly expecting that what the user inputted a url that,
indeed, has a username.
Using our knowledge of [Link], we can further improve our code.
import re
url = input("URL: ").strip()
matches = [Link](r"^https?://(www\.)?twitter\.com/(.+)$", url,
[Link])
if matches:
print(f"Username:", [Link](2))
Notice how we are searching for the regular expression above in the
string provided by the user. In particular, we are capturing that which
appears at the end of the URL using (.+)$ regular expression.
Therefore, if the user fails to input a URL without a username, no input
will be presented.
Notice that the ?: tells the interpreter it does not have to capture what
is in that spot in our regular expression.
Notice that the [a-z0-9_]+ tells the interpreter to only expect a-z, 0-9,
and _ as part of the regular expression. The + indicates that we are
expecting one or more characters.
Regular Expressions
Case Sensitivity
Cleaning Up User Input
Extracting User Input
OBJECT-ORIENTED PROGRAMMING
Packing that tuple, such that we are able to return both items to a
variable called student, we can modify our code as follows.
def main():
student = get_student()
print(f"{student[0]} from {student[1]}")
def get_student():
name = input("Name: ")
house = input("House: ")
return (name, house)
if __name__ == "__main__":
main()
Notice that (name, house) explicitly tells anyone reading our code that
we are returning two values within one. Further, notice how we can
index into tuples using student[0] or student[1].
Notice that this code produces an error. Since tuples are immutable,
we’re not able to reassign the value of student[1].
Note that lists are mutable. That is, the order of house and name can be
switched by a programmer. You might decide to utilize this in some
cases where you want to provide more flexibility at the cost of the
security of your code. After all, if the order of those values is
changeable, programmers that work with you could make mistakes
down the road.
We can provide our special case with Padma in our dictionary version of
our code.
def main():
student = get_student()
if student["name"] == "Padma":
student["house"] = "Ravenclaw"
print(f"{student['name']} from {student['house']}")
def get_student():
name = input("Name: ")
house = input("House: ")
return {"name": name, "house": house}
if __name__ == "__main__":
main()
Any time you create a class and you utilize that blueprint to create
something, you create what is called an “object” or an “instance”. In the
case of our code, student is an object.
Further, we can lay some groundwork for the attributes that are
expected inside an object whose class is Student. We can modify our
code as follows:
class Student:
def __init__(self, name, house):
[Link] = name
[Link] = house
def main():
student = get_student()
print(f"{[Link]} from {[Link]}")
def get_student():
name = input("Name: ")
house = input("House: ")
student = Student(name, house)
return student
if __name__ == "__main__":
main()
Notice how we check now that a name is provided and a proper house is
designated. It turns out we can create our own exceptions that alerts
the programmer to a potential error created by the user called raise. In
the case above, we raise ValueError with a specific error message.
Notice how we define our own method charm. Unlike dictionaries, classes
can have built-in functions called methods. In this case, we define
our charm method where specific cases have specific results. Further,
notice that Python has the ability to utilize emojis directly in our code.
Before moving forward, let us remove our patronus code. Modify your
code as follows:
class Student:
def __init__(self, name, house):
if not name:
raise ValueError("Invalid name")
if house not in ["Gryffindor", "Hufflepuff", "Ravenclaw",
"Slytherin"]:
raise ValueError("Invalid house")
[Link] = name
[Link] = house
def __str__(self):
return f"{[Link]} from {[Link]}"
def main():
student = get_student()
[Link] = "Number Four, Privet Drive"
print(student)
def get_student():
name = input("Name: ")
house = input("House: ")
return Student(name, house)
if __name__ == "__main__":
main()
Notice how we’ve written @property above a function called house. Doing
so defines house as a property of our class. With house as a property, we
gain the ability to define how some attribute of our class, _house, should
be set and retrieved. Indeed, we can now define a function called a
“setter”, via @[Link], which will be called whenever the house
property is set—for example, with [Link] = "Gryffindor". Here,
we’ve made our setter validate values of house for us. Notice how we
raise a ValueError if the value of house is not any of the Harry Potter
houses, otherwise, we’ll use house to update the value of _house.
Why _house and not house? house is a property of our class, with
functions via which a user attempts to set our class attribute. _house is
that class attribute itself. The leading underscore, _, indicates to users
they need not (and indeed, shouldn’t!) modify this value
directly. _house should only be set through the house setter. Notice how
the house property simply returns that value of _house, our class
attribute that has presumably been validated using our house setter.
When a user calls [Link], they’re getting the value
of _house through our house “getter”.
In addition to the name of the house, we can protect the name of our
student as well. Modify your code as follows:
class Student:
def __init__(self, name, house):
[Link] = name
[Link] = house
def __str__(self):
return f"{[Link]} from {[Link]}"
# Getter for name
@property
def name(self):
return self._name
# Setter for name
@[Link]
def name(self, name):
if not name:
raise ValueError("Invalid name")
self._name = name
@property
def house(self):
return self._house
@[Link]
def house(self, house):
if house not in ["Gryffindor", "Hufflepuff", "Ravenclaw",
"Slytherin"]:
raise ValueError("Invalid house")
self._house = house
def main():
student = get_student()
print(student)
def get_student():
name = input("Name: ")
house = input("House: ")
return Student(name, house)
if __name__ == "__main__":
main()
Notice how, much like the previous code, we provide a getter and setter
for the name.
Notice how by executing this code, it will display that the class
of 50 is int.
Notice how executing this code will indicate this is of the class str.
Notice how executing this code will indicate this is of the class list.
We can also apply this to a list using the name of Python’s built-
in list class as follows:
print(type(list()))
Notice how executing this code will indicate this is of the class list.
Notice how executing this code will indicate this is of the class dict.
We can also apply this to a dict using the name of Python’s built
in dict class as follows:
print(type(dict()))
Notice how executing this code will indicate this is of the class dict.
Class Methods
Sometimes, we want to add functionality to a class itself, not to
instances of that class.
@classmethod is a function that we can use to add functionality to a class
as a whole.
Here’s an example of not using a class method. In your terminal
window, type code [Link] and code as follows:
import random
class Hat:
def __init__(self):
[Link] = ["Gryffindor", "Hufflepuff", "Ravenclaw",
"Slytherin"]
def sort(self, name):
print(name, "is in", [Link]([Link]))
hat = Hat()
[Link]("Harry")
Notice how when we pass the name of the student to the sorting hat, it
will tell us what house is assigned to the student. Notice that hat =
Hat() instantiates a hat. The sort functionality is always handled by
the instance of the class Hat. By executing [Link]("Harry"), we pass
the name of the student to the sort method of the particular instance
of Hat, which we’ve called hat.
Object-oriented programming
Classes
raise
Class Methods
Static Methods
Inheritance
Operator Overloading
EXTRA NOTES
It turns out we can use the built-in set features to eliminate duplicates.
In the text editor window, code as follows:
students = [
{"name": "Hermione", "house": "Gryffindor"},
{"name": "Harry", "house": "Gryffindor"},
{"name": "Ron", "house": "Gryffindor"},
{"name": "Draco", "house": "Slytherin"},
{"name": "Padma", "house": "Ravenclaw"},
]
houses = set()
for student in students:
[Link](student["house"])
for house in sorted(houses):
print(house)
Since no errors are presented by executing the code above, you’d think
all is well. However, it is not! In the text editor window, code as follows:
balance = 0
def main():
print("Balance:", balance)
deposit(100)
withdraw(50)
print("Balance:", balance)
def deposit(n):
balance += n
def withdraw(n):
balance -= n
if __name__ == "__main__":
main()
Notice how we now add the functionality to add and withdraw funds to
and from balance. However, executing this code, we are presented with
an error! We see an error called UnboundLocalError. You might be able to
guess that, at least in the way we’ve currently coded balance and
our deposit and withdraw functions, we can’t reassign it a new value
inside a function.
To interact with a global variable inside a function, the solution is to use
the global keyword. In the text editor window, code as follows:
balance = 0
def main():
print("Balance:", balance)
deposit(100)
withdraw(50)
print("Balance:", balance)
def deposit(n):
global balance
balance += n
def withdraw(n):
global balance
balance -= n
if __name__ == "__main__":
main()
Notice how the global keyword tells each function that balance does not
refer to a local variable: instead, it refers to the global variable we
originally placed at the top of our code. Now, our code functions!
Notice MEOWS is our constant in this case. Constants are typically denoted
by capital variable names and are placed at the top of our code. Though
this looks like a constant, in reality, Python actually has no mechanism
to prevent us from changing that value within our code! Instead, you’re
on the honor system: if a variable name is written in all caps, just don’t
change it!
def meow(n):
for _ in range(n):
print("meow")
Notice how running mypy now produces no errors because we cast our
input to an integer.
Notice how the meow function has only a side effect. Because we only
attempt to print “meow”, not return a value, an error is thrown when we
try to store the return value of meow in meows.
We can further use type hints to check for errors, this time annotating
the return values of functions. In the text editor window, code as
follows:
def meow(n: int) -> None:
for _ in range(n):
print("meow")
number: int = int(input("Number: "))
meows: str = meow(number)
print(meows)
Notice how the notation -> None tells mypy that there is no return value.
Notice how the three double quotes designate what the function does.
You can use docstrings to standardize how you document the features
of a function. In the text editor window, code as follows:
def meow(n):
"""
Meow n times.
:param n: Number of times to meow
:type n: int
:raise TypeError: If n is not an int
:return: A string of n meows, one per line
:rtype: str
"""
return "meow\n" * n
number = int(input("Number: "))
meows = meow(number)
print(meows, end="")
Let’s assume that this program will be getting much more complicated.
How could we check all the arguments that could be inserted by the
user? We might give up if we have more than a few command-line
arguments!
Luckily, argparse is a library that handles all the parsing of complicated
strings of command-line arguments. In the text editor window, code as
follows:
import argparse
parser = [Link]()
parser.add_argument("-n")
args = parser.parse_args()
for _ in range(int(args.n)):
print("meow")
We can also program more cleanly, such that our user can get some
information about the proper usage of our code when they fail to use
the program correctly. In the text editor window, code as follows:
import argparse
parser = [Link](description="Meow like a cat")
parser.add_argument("-n", help="number of times to meow")
args = parser.parse_args()
for _ in range(int(args.n)):
print("meow")
We can further improve this program. In the text editor window, code as
follows:
import argparse
parser = [Link](description="Meow like a cat")
parser.add_argument("-n", default=1, help="number of times to meow",
type=int)
args = parser.parse_args()
for _ in range(args.n):
print("meow")
Notice how not only is help documentation included, but you can
provide a default value when no arguments are provided by the user.
Notice how this program tries to get a user’s first name by naively
splitting on a single space.
It turns out there are other ways to unpack variables. You can write
more powerful and elegant code by understanding how to unpack
variables in seemingly more advanced ways. In the text editor window,
code as follows:
def total(galleons, sickles, knuts):
return (galleons * 17 + sickles) * 29 + knuts
print(total(100, 50, 25), "Knuts")
What if we wanted to store our coins in a list? In the text editor window,
code as follows:
def total(galleons, sickles, knuts):
return (galleons * 17 + sickles) * 29 + knuts
coins = [100, 50, 25]
print(total(coins[0], coins[1], coins[2]), "Knuts")
Notice how a list called coins is created. We can pass each value in by
indexing using 0, 1, and so on.
Notice how a * unpacks the sequence of the list of coins and passes in
each of its individual elements to total.
Suppose that we could pass in the names of the currency in any order?
In the text editor window, code as follows:
def total(galleons, sickles, knuts):
return (galleons * 17 + sickles) * 29 + knuts
print(total(galleons=100, sickles=50, knuts=25), "Knuts")
When you start talking about “names” and “values,” dictionaries might
start coming to mind! You can implement this as a dictionary. In the
text editor window, code as follows:
def total(galleons, sickles, knuts):
return (galleons * 17 + sickles) * 29 + knuts
coins = {"galleons": 100, "sickles": 50, "knuts": 25}
print(total(coins["galleons"], coins["sickles"], coins["knuts"]),
"Knuts")
We can even pass in named arguments. In the text editor window, code
as follows:
def f(*args, **kwargs):
print("Named:", kwargs)
f(galleons=100, sickles=50, knuts=25)
Notice how the named values are provided in the form of a dictionary.
Notice how map takes two arguments. First, it takes a function we want
applied to every element of a list. Second, it takes that list itself, to
which we’ll apply the aforementioned function. Hence, all words
in words will be handed to the [Link] function and returned
to uppercased.
Notice how this code doesn’t (yet!) use any comprehensions. Instead, it
follows the same paradigms we have seen before.
print(" " * i)
Notice how this program will count the number of sheep you ask of it.
print(" " * i)
if __name__ == "__main__":
main()
We have been getting into the habit of abstracting away parts of our
code.
We can call a sheep function by modifying our code as follows:
def main():
n = int(input("What's n? "))
for i in range(n):
print(sheep(i))
def sheep(n):
We can provide the sheep function more abilities. In the text editor
window, code as follows:
def main():
n = int(input("What's n? "))
for s in sheep(n):
print(s)
def sheep(n):
flock = []
for i in range(n):
[Link](" " * i)
return flock
if __name__ == "__main__":
main()
Executing our code, you might try different numbers of sheep such
as 10, 1000, and 10000. What if you asked for 1000000 sheep, your
program might completely hang or crash. Because you have attempted
to generate a massive list of sheep, your computer may be struggling to
complete the computation.
The yield generator can solve this problem by returning a small bit of
the results at a time. In the text editor window, code as follows:
def main():
n = int(input("What's n? "))
for s in sheep(n):
print(s)
def sheep(n):
for i in range(n):
Notice how yield provides only one value at a time while the for loop
keeps working.
Notice how running this program provides you with a spirited send-off.
Our great hope is that you will use what you learned in this course to
address real problems in the world, making our globe a better place.
This was CS50!
F-strings in Python allow embedded expressions within string literals, prefixed with 'f', for more readable formatting. For example, 'print(f"hello, {name}")' is more concise than using '%', 'format', or concatenation methods, providing a clear, direct way to format strings .
Anticipating user input errors prevents program crashes and allows for meaningful error messages. In a class definition, exceptions can be raised when input doesn't meet specific criteria, like a missing 'name' or invalid 'house', using 'raise ValueError("Error message")'. This ensures users are prompted to correct their input .
Parameters in functions are arguments that control and influence the function's operation. In Python's 'print' function, parameters like 'end' determine how outputs are formatted. By default, 'end' is '\n', adding a newline character, but it can be changed to another string, affecting output flow .
The assignment operator '=' in Python assigns the value or result on its right to the variable name on its left. For example, in the code 'name = input("What's your name? ")', the input function prompts the user for input, and whatever the user types is assigned to the variable 'name' .
Classes in Python encapsulate functionality by bundling data and methods that operate on that data. For instance, in a 'Student' class, attributes like 'name' and 'house' can be defined with methods controlling how these attributes are managed, such as initializing and checking them for validity through '__init__' and custom methods .
Using a comma in a print statement in Python, such as 'print("hello,", name)', allows for printing multiple arguments separated by a space automatically. Concatenation, like 'print("hello, " + name)', must be explicit and doesn't automatically add a space between the arguments unless included in the string. Both methods print results on the same line if 'end' parameter defaults or specified as '' .
The '__str__' method in a Python class defines a user-friendly string representation for objects of the class when printed. In the 'Student' class, '__str__' returns a formatted string like 'f"{self.name} from {self.house}"', enhancing readability and presentation when objects are output or logged .
Using Python's 're' module, a regular expression pattern can validate email addresses. A pattern like '^\w+@(\w+\.)?\w+\.edu$' verifies that an address has a proper format ending with '.edu'. Extending to other domains requires adjusting the regex to include other TLDs .
The 'strip' method removes any leading and trailing whitespace from a string, ensuring clean input. For example, 'name = input("What's your name?").strip()' removes extra spaces around the user's input, avoiding errors or issues when processing their name .
Case sensitivity in email validation can be addressed by using the 're.IGNORECASE' flag within the 're.search' function in Python. For instance, 're.search(r"^\w+@\w+\.edu$", email, re.IGNORECASE)' will treat email inputs like 'MALAN@HARVARD.EDU' and 'malan@harvard.edu' as valid by ignoring case discrepancies .