Python 3.8: Hands-On Learning Guide
Python 3.8: Hands-On Learning Guide
8
◊◊◊
with examples and hands-on exercises
WEBUCATOR
Copyright © 2020 by Webucator. All rights reserved.
No part of this book may be reproduced or used in any manner
without written permission of the copyright owner.
Version: PYT-[Link]
The Authors
Nat Dunn
In this book, we cover material that you will be able to immediately put
into practice.
How to Read this Book
The mechanic, who wishes to do his work well, must first sharpen his
tools.
– Confucius
Visual Studio Code has many freely available extensions for different
programming languages. Python developers should install Microsoft’s
Python extension. As shown in the screenshot that follow:
1. Click the Extensions icon (below the bug) on the left of the
Explorer panel.
2. Search for “Python”.
3. If it doesn’t show that the extension is already installed, click the
Install button.
You can customize the Visual Studio Code color theme by selecting
File > Preferences > Color Theme. The default is a dark theme. We
use a light theme for our screenshots.
Conclusion
Your computer should now be all set for viewing and editing the files in
this book.
LESSON 2
Python Basics
Topics Covered
The pythons had entered into Mankind. No man knew at what moment
he might be Possessed!
– Plague of Pythons, Frederik Pohl
The terminal will open at the root of your Visual Studio Code
workspace. If you’re working in the workspace we had you set up,
that should be in a Webucator\Python directory (the words folder
and directory are used interchangeably). The prompt on Windows
will read something like:
PS C:\Webucator\Python>
natdunn@NatsMac:Python %
Your prompt will show that you are in the Demos directory.
Depending on your environment, it may also show one or more
directories above the Demos directory. To get the full path to your
current location, run pwd for present working directory:
pwd
The “.” at the beginning of the path represents the current (or
present) directory. So, .\Demos refers to the Demos directory within
the current directory. A Mac won’t include the current directory in
the path. When you press Tab, it will just fill out the rest of the
folder name.
cd Demos
Directory: C:\Webucator\Python\python-basics
Mac Terminal
NatsMac:python-basics natdunn$ ls
Demos Exercises Solutions data
Running Python
Python runs on Microsoft Windows, Mac OS X, Linux, and other Unix-
like systems. The first thing to do is to make sure you have a recent
version of Python installed:
print('Hello, world!')
You can tell that you are at the Python prompt by the three right-angle
brackets:
Code Explanation
Another way to run a file from within Visual Studio Code is to right-
click on the open file and select Run Python File at Terminal:
If you don’t see this option, then you don’t have the Python extension
installed.
Exercise 1: Hello, world!
5 to 10 minutes.
Code Explanation
Literals
When a value is hard coded into an instruction, as "Hello" is in
print("Hello"), that value is called a literal because the Python
interpreter should not try to interpret it but should take it literally. If we
left out the quotation marks (i.e., print(Hello)), Python would output
an error because it would not know how to interpret the name Hello.
Either single quotes or double quotes can be used to create string
literals. Just make sure that the open and close quotation marks
match.
Literals representing numbers (e.g., 42 and 3.14) are note enclosed in
quotation marks.
Python Comments
In the previous demo, you may have noticed this line of code:
# Say Hello to the World
Multi-line Comments
There is no official syntax for creating multi-line comments; however,
Guido van Rossum, the creator of Python, tweeted this tip5 as a
workaround:
# This is a
# very very helpful and informative
# comment about my code!
Data Types
In Python programming, objects have different data types. The data
type determines both what an object can do and what can be done to
it. For example, an object of the data type integer can be subtracted
from another integer, but it cannot be subtracted from a string of
characters.
In the following list, we show the basic data types used in Python.
Abbreviations are in parentheses.
10 to 15 minutes.
In this exercise, you will use the built-in type() function to explore
different data types.
Solution
>>> type(3)
<class 'int'>
>>> type(3.1)
<class 'float'>
>>> type('3')
<class 'str'>
>>> type('pizza')
<class 'str'>
>>> type(True)
<class 'bool'>
>>> type(('1', '2', '3'))
<class 'tuple'>
>>> type(['1', '2', '3'])
<class 'list'>
>>> type({'1', '2', '3'})
<class 'set'>
Don’t worry if you’re not familiar with all of the preceding data types.
We will cover them all.
Variables
Variables are used to hold data in memory. In Python, variables are
untyped, meaning you do not need to specify the data type when
creating the variable. You simply assign a value to a variable. Python
determines the type by the value assigned.
Variable Names
Variable names are case sensitive, meaning that age is different from
Age. By convention, variable names are written in all lowercase letters
and words in variable names are separated by underscores (e.g.,
home_address). Variable names must begin with a letter or an
underscore and may contain only letters, digits, and underscores.
Keywords
Variable Assignment
1. Variable name.
2. Assignment operator.
3. Value assigned. This could be any data type.
Here is the “Hello, world!” script again, but this time, instead of
outputting a literal, we assign a string to a variable and then output the
variable:
Demo 2: python-basics/Demos/hello_variables.py
Code Explanation
Run this Python file at the terminal. The output will be the same as it
was in the previous demo:
Hello, world!
Simultaneous Assignment
5 to 10 minutes.
In this exercise, you will write a simple Python script from scratch. The
script will create a variable called today that stores the day of the
week.
today = "Monday"
print(today)
Constants
In programming, a constant is like a variable in that it is an identifier
that holds a value, but, unlike variables, constants are not variable,
they are constant. Good name choices, right?
Python doesn’t really have constants, but as a convention, variables
that are meant to act like constants (i.e., their values are not meant to
be changed) are written in all capital letters. For example:
PI = 3.141592653589793
RED = "FF0000" # hexadecimal code for red
Deleting Variables
Although it’s rarely necessary to do so, variables can be deleted using
the del statement, like this:
>>> a = 1
>>> print(a)
1
>>> del a
>>> print(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
Notice that trying to print a results in an error, because after del a, the
a variable no longer exists.
def main():
print("I am part of the function.")
print("I am also part of the function.")
print("Hey, me too!")
print("Sad not to be part of the function. I've been outdented.")
main()
Code Explanation
Notice that the first line of output is the line that is not part of the
function. That is because the function does not run until it is called,
and it is called after the print() function that outputs “Sad not to be
part of the function. I’ve been outdented.”
The code is read by the Python interpreter from top to bottom, but the
function definition just defines the function; it does not invoke the
function (programmer speak for “call the function to execute”).
[Link]
Visit [Link] to see this demo in
pythontutor.com7, a web application for visualizing how Python
executes code.
1. Functions are created using the def keyword. The content that
follows the def keyword on the same line is called the function
signature.
2. The convention for naming functions is the same as that for
variables: use all lowercase letters and separate words with
underscores.
3. In the function definition, the function name is followed by a pair of
parentheses, which may contain parameters (more on that soon),
and a colon.
4. The contents of the function starts on the next line and must be
indented. Either spaces or tabs can be used for indenting, but
spaces are preferred.
5. The first line of code after the function definition that is not
indented is not part of the function and effectively marks the end of
the function definition.
6. Functions are invoked using the function name followed by the
parentheses (e.g., indent_demo()).
There is nothing magic about the name “main”. It is simply the name
used by convention for the function that starts the program or module
running.8
Grouping of Statements
A programming statement is a unit of code that does something.
The following code shows two statements:
greeting = "Hello!"
print(greeting)
print() Function
You have already seen how to output data using the built-in print()
function. Now, let’s see how to output a variable and a literal string
together. The print() function can take multiple arguments. By
default, it will output them all separated by single spaces. For
example, the following code will output "H e l l o !"
print('H', 'e', 'l', 'l', 'o', '!')
def main():
today = "Monday"
print("Today is", today, ".")
main()
Code Explanation
Notice the extra space before the period in the output of the last
demo:
Named Arguments
The following demo shows how the sep argument can be used to get
rid of the extra space we saw in the previous example:
Demo 5: python-basics/Demos/variable_and_string_output_fixed_spacing.py
def main():
today = "Monday"
print("Today is ", today, ".", sep="")
main()
Code Explanation
def main():
today = input("What day is it? ")
print("Wow! Today is ", today, "? Awesome!", sep="")
main()
Code Explanation
Enter the day and press Enter. It will output something like:
5 to 10 minutes.
def main():
your_name = input("What is your name? ")
print("Hello, ", your_name, "!", sep="")
main()
Code Explanation
with Blocks
with open("[Link]") as f:
content = [Link]()
print(content)
When Python reaches the end of the with block, it understands that
the file is no longer necessary and automatically closes it.
Writing to a File
In addition to the path to the file, the open() function takes a second
parameter: mode, which indicates whether the file is being opened for
reading ("r"), writing ("w"), or appending ("a"). The default value for
mode is "r", which is why we didn’t need to pass a value in when
opening the file for reading. If we want to write to a file, we do have to
pass in a value: "w".
When you run the code above, it will open [Link] if it exists and
overwrite the file with the text you write to it. If it doesn’t find a file
named [Link], it will create it.
Exercise 5: Working with Files
10 to 15 minutes.
In this exercise, you will open two files that contain lists of popular
boys and girls names from 1880,10 read their contents into two
variables, and then write the combined content of the two files into a
new file.
That will place one newline between the content in boys and the
content in girls.
5. Save your file.
6. Test your solution. When you run it, it should create the new file.
Look in the data folder for the [Link] file. Does it exist? If so,
open it up. Does it have a list of all the boys and girls names?
Conclusion
In this lesson, you have begun to work with Python. Among other
things, you have learned to use variables, to output data, to collect
user input, and to write simple Python functions and modules.
LESSON 3
Functions and Modules
Topics Covered
You have seen some of Python’s built-in functions. In this lesson, you
will learn to write your own.
Defining Functions
We discussed functions a little in the last lesson, but let’s quickly
review the syntax. Functions are defined in Python using the def
keyword. The syntax is as follows:
def function_name():
# content of function is indented
do_something()
# This is no longer part of the function
do_something_else()
def say_hello():
your_name = input("What is your name? ")
print("Hello, ", your_name, "!", sep="")
def main():
say_hello()
main()
Code Explanation
The code works in the same way, but the meat of the program has
been moved out of the main() function and into another function. This
is common. Usually, the main() function handles the flow of the
program, but the actual “work” is done by other functions in the
module.
def say_hello():
your_name = input("What is your name? ")
insert_separator()
print("Hello, ", your_name, "!", sep="")
def insert_separator():
print("===")
def recite_poem():
print("How about a Monty Python poem?")
insert_separator()
print("Much to his Mum and Dad's dismay")
print("Horace ate himself one day.")
print("He didn't stop to say his grace,")
print("He just sat down and ate his face.")
def say_goodbye():
print("Goodbye!")
def main():
say_hello()
insert_separator()
recite_poem()
insert_separator()
say_goodbye()
main()
Code Explanation
Variable Scope
Question: Why doesn’t the say_goodbye() function use the user’s
name (e.g., print("Goodbye,", your_name))?
Answer: It doesn’t know what your_name is.
Variables declared within a function are local to that function. Consider
the following code:
Demo 9: functions/Demos/local_var.py
def set_x():
x = 1
def get_x():
print(x)
def main():
set_x()
get_x()
main()
Code Explanation
x = 0
def set_x():
x = 1
print("from set_x():", x)
def get_x():
print("from get_x():", x)
def main():
set_x()
get_x()
main()
Code Explanation
x = 0
def set_x():
global x
x = 1
def get_x():
print(x)
def main():
set_x()
get_x()
main()
Code Explanation
Function Parameters
Consider the insert_separator() function in the
hello_you_expanded.py file that we saw earlier:
def insert_separator():
print("===")
def say_hello(name):
print('Hello, ', name, '!', sep='')
def insert_separator(s):
print(s, s, s, sep="")
def recite_poem():
print("How about a Monty Python poem?")
insert_separator("-")
print("Much to his Mum and Dad's dismay")
print("Horace ate himself one day.")
print("He didn't stop to say his grace,")
print("He just sat down and ate his face.")
def say_goodbye(name):
print('Goodbye, ', name, '!', sep='')
def main():
your_name = input('What is your name? ')
insert_separator("-")
say_hello(your_name)
insert_separator("=")
recite_poem()
insert_separator("=")
say_goodbye(your_name)
main()
Code Explanation
When calling a function, you can specify the parameter by name when
passing in an argument. When you do so, it’s called passing in a
keyword argument. For example, you can call the following divide()
function in several ways:
divide(10, 2)
divide(numerator=10, denominator=2)
divide(denominator=2, numerator=10)
divide(10, denominator=2)
As you can see, using keyword arguments allows you to pass in the
arguments in an arbitrary order. You can combine non-keyword
arguments with keyword arguments in a function call, but you must
pass in the non-keyword arguments first.
Later, we’ll see that a function can be written to require keyword
arguments.
Exercise 6: A Function with Parameters
15 to 25 minutes.
In this exercise, you will write a function for adding two numbers
together.
def add_3_and_6():
total = 3 + 6
print('3 + 6 = ', total)
def add_10_and_12():
total = 10 + 12
print('10 + 12 = ', total)
def main():
add_3_and_6()
add_10_and_12()
main()
Code Explanation
The first function adds 3 and 6. The second function adds 10 and 12.
These functions are only useful if you want to add those specific
numbers.
Solution: functions/Solutions/add_nums.py
def main():
add_nums(3, 6)
add_nums(10, 12)
main()
Code Explanation
The add_nums() function is flexible and reusable. It can add any two
numbers.
Default Values
Parameters that do not have default values require arguments to be
passed in. You can assign default values to parameters using the
following syntax:
def function_name(param=default):
do_something(param)
For example, the following code would make the “=” sign the default
separator:
def insert_separator(s="="):
print(s, s, s, sep="")
15 to 25 minutes.
In this exercise, you will write a function that can add two, three, four,
or five numbers together.
4. For now, it’s okay for the function to print out 0s for values not
passed in, like this:
1 + 2 + 0 + 0 + 0 = 3
1 + 2 + 3 + 4 + 5 = 15
11 + 12 + 13 + 14 + 0 = 50
101 + 201 + 301 + 0 + 0 = 603
Exercise Code: functions/Exercises/add_nums_with_defaults.py
def main():
add_nums(1, 2, 0, 0, 0)
add_nums(1, 2, 3, 4, 5)
add_nums(11, 12, 13, 14, 0)
add_nums(101, 201, 301, 0, 0)
main()
Solution: functions/Solutions/add_nums_with_defaults.py
def main():
add_nums(1, 2)
add_nums(1, 2, 3, 4, 5)
add_nums(11, 12, 13, 14)
add_nums(101, 201, 301)
main()
Code Explanation
Returning Values
Functions can return values. The add_nums() function we have been
working with does more than add the numbers passed in, it also prints
them out. You can imagine wanting to add numbers for some other
purpose than printing them out. Or you might want to print the results
out in a different way. We can change the add_nums() function so that
it just adds the numbers together and returns the sum. Then our
program can decide what to do with that sum. Take a look at the
following code:
Demo 13: functions/Demos/add_nums_with_return.py
def main():
result = add_nums(1, 2)
print(result)
result = add_nums(result, 3)
print(result)
result = add_nums(result, 4)
print(result)
result = add_nums(result, 5)
print(result)
result = add_nums(result, 6)
print(result)
main()
Code Explanation
The add_nums() function now returns the sum to the calling function via
the return statement. We assign the result to a local variable named
result. Then we print result and pass it back to add_nums() in
subsequent calls.
Note that once a function has returned a value, the function is finished
executing and control is transferred back to the code that invoked the
function.
Importing Modules
As we saw, part of the beauty of writing functions is that they can be
reused. Imagine you write a really awesome function. Or even better,
a module with a whole bunch of really awesome functions in it. You’d
want to make those functions available to other modules so you (and
other Python developers) could make use of them elsewhere.
Modules can import other modules using the import keyword as
shown in the following example:
Demo 14: functions/Demos/import_example.py
import add_nums_with_return
total = add_nums_with_return.add_nums(1, 2, 3, 4, 5)
print(total)
Code Explanation
Note that those are two underscores before and after name and
before and after main.
A short video explanation of this is available at
[Link]
For example:
Demo 15: functions/Demos/import_example2.py
total = add_nums(1, 2, 3, 4, 5)
print(total)
When you use this approach, it is not necessary to prefix the module
name when calling the function. However, it’s possible to have naming
conflicts, so be careful.
Another option, which is helpful for modules with long names, is to
create an alias for a module, so that you do not have to type its full
name:
import add_nums_with_return as anwr
total = anwr.add_nums(1, 2, 3, 4, 5)
This will output a list of paths, which are searched in order for the
imported module.
pyc Files
Files with a .pyc extension are compiled Python files. They are
automatically created in a __pycache__ folder the first time a file is
imported:
These files are created so that modules you import don’t have to be
compiled every time they run. You can just leave those files alone.
They will automatically be created/updated each time you import a
module that is new or has been changed.
Methods vs. Functions
You have already seen some built-in functions, like print() and
input(). You have also written some of your own, like
insert_separator() and divide(). In the upcoming lessons, you will
learn to use many more of Python’s built-in functions. You will also
learn about methods, which are similar to functions, except that they
are called on an object using the syntax object_name.method_name().
An example of a simple built-in function is len(), which returns the
length of the passed-in object:
>>> len('Webucator')
9
An example of a method of a string object is upper(), which returns
the string it is called upon in all uppercase letters:
>>> Webucator'.upper()
'WEBUCATOR'
Again, you will work with many functions and methods in upcoming
lessons.
Conclusion
In this lesson, you have learned to define functions with or without
parameters. You have also learned about variable scope and how to
import modules.
LESSON 4
Math
Topics Covered
I had been to school most all the time and could spell and read and
write just a little, and could say the multiplication table up to six times
seven is thirty-five, and I don't reckon I could ever get any further than
that if I was to live forever. I don't take no stock in mathematics,
anyway.
– Adventures of Huckleberry Finn, Mark Twain
>>> 5+2
7
>>> 5-2
3
>>> 5*2
10
>>> 5/2
2.5
>>> 5%2
1
>>> 5**2
25
>>> 5//2
2
>>> 5 % 2
1
>>> 11 % 3
2
>>> 22 % 4
2
>>> 22 % 3
1
>>> 10934 % 324
242
The floor division operator (//) is the same as regular division, but
rounded down:
>>> 5 // 2
2
>>> 11 // 3
3
>>> 22 // 4
5
>>> 22 // 3
7
>>> 10934 // 324
33
>>> -5 // 2 # rounded down, meaning towards negative infinity
-3
5 to 10 minutes.
In this exercise, you will write a small function called divide() that
takes a numerator and denominator and prints out a response that a
fifth grader would understand (e.g., “5 divided by 2 equals 2 with a
remainder of 1”).
def main():
divide(5, 2)
divide(6, 3)
divide(12, 5)
divide(1, 2)
main()
Solution: math/Solutions/floor_modulus.py
def main():
divide(5, 2)
divide(6, 3)
divide(12, 5)
divide(1, 2)
main()
Assignment Operators
The following table lists the assignment operators in Python.
Assignment Operators
Operator Description
= Basic assignment
a = 2
+= One step addition and assignment
a += 2 same as a = a + 2
-= One step subtraction and assignment
a -= 2 same as a = a - 2
*= One step multiplication and assignment
a *= 2 same as a = a * 2
/= One step division and assignment
a /= 2 same as a = a / 2
Here are the examples from the table above in a Python script:
>>> a = 5
>>> a
5
>>> a += 2
>>> a
7
>>> a -= 2
>>> a
5
>>> a *= 2
>>> a
10
>>> type(a)
<class 'int'>
>>> a /= 2
>>> a
5.0
>>> type(a) # Notice division returns a float
<class 'float'>
1. **
2. *, /, //, %
3. +, -
You can use parentheses to change the order of operations and give
an operation higher precedence. For example:
int(x)
>>> int(5)
5
>>> int('5')
5
>>> int(5.4)
5
>>> int(5.9)
5
>>> int(-5.9)
-5
>>> int('5.4')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '5.4'
float(x)
>>> float(5)
5.0
>>> float('5')
5.0
>>> float(5.4)
5.4
>>> float('5.4')
5.4
>>> float('-5.99')
-5.99
>>> float(-5.99)
-5.99
abs(x)
>>> abs(-5)
5
>>> abs(5)
5
>>> abs(-5.5)
5.5
>>> abs(5.5)
5.5
pow(base, exp)
is the same as
base**exp
For example:
>>> pow(4, 2)
16
>>> 4**2
16
>>> pow(4, 2, 3)
1
>>> 4**2 % 3
1
round(number[, ndigits])
sum(iter[, start])
The sum() function takes an iterable (e.g., a list) and adds up all of its
elements. We will cover this in the Iterables lesson.
The math Module
The math module is built in to Python and provides many useful
methods. We cover some of them here.15
To access any of these methods, you must first import math:
>>> import math
[Link](x)
>>> [Link](5.4)
6
>>> [Link](-5.4)
-5
>>>
[Link](x)
>>> [Link](5.6)
5
>>> [Link](-5.6)
-6
[Link](x)
[Link](x)
>>> [Link](-5)
5.0
>>> abs(-5)
5
[Link](x)
>>> [Link](3)
6
>>> [Link](5)
120
[Link](x, y)
>>> [Link](5, 2)
25.0
[Link](x)
>>> [Link](25)
5.0
The math module also contains two constants: [Link] and math.e, for
Pi and e as used in the natural logarithm.16
[Link]()
[Link](a, b)
[Link](b)
[Link](a, b)
[Link](a, b, step)
[Link](a, b)
Seeding
Notice that the random numbers generated depend on the seed. If you
run this same code locally, you should get the same random integers.
This can be useful for testing.
By default, [Link]() uses the current system time to ensure that
seed() is seeded with a different number every time it runs, so that the
random numbers generated will be different each time.
Exercise 9: How Many Pizzas Do We Need?
15 to 25 minutes.
In this exercise, you will write a program from scratch. Your program
should prompt the user to input the information required:
Using that information, your program must calculate how many pizzas
are needed to feed everyone. It should work like this:
import math
def main():
people = int(input("How many people are eating? "))
slices_per_person = float(input("How many slices per person? "))
slices = slices_per_person * people
main()
15 to 25 minutes.
In this exercise, you will write a dice-rolling program from scratch. The
program should include two functions: main() and roll_die(). The
roll_die() function should take one parameter: sides, and return a
random roll between 1 and sides
The main() function should call the roll_die() function three times
and keep a tally of the total. After each roll, it should print out the value
of the roll, the number of rolls, and the total. At the end, it should
output the average of the three rolls. The output will be similar to the
following:
You rolled a 3
Total after first roll: 3
You rolled a 5
Total after 2 rolls: 8
You rolled a 3
Total after 3 rolls: 11
Your average roll was 3.67
Thanks for playing.
Solution: math/Solutions/[Link]
import random
def roll_die(sides=6):
num_rolled = [Link](1, sides)
return num_rolled
def main():
sides = 6
total = 0
num_rolls = 1
roll = roll_die(sides)
print("You rolled a", roll)
total += roll
print("Total after first roll:", total)
num_rolls += 1
roll = roll_die(sides)
print("You rolled a", roll)
total += roll
print("Total after", num_rolls, "rolls:", total)
num_rolls += 1
roll = roll_die(sides)
print("You rolled a", roll)
total += roll
print("Total after", num_rolls, "rolls:", total)
main()
Conclusion
In this lesson, you have learned to do basic math in Python and to use
the math and random modules for extended math functionality.
LESSON 5
Python Strings
Topics Covered
String basics.
Special characters.
Multi-line strings.
Indexing and slicing strings.
Common string operators and methods.
Formatting strings.
Built-in string functions.
‘Yes,’ they say to one another, these so kind ladies, ‘he is a stupid old
fellow, he will see not what we do, he will never observe that his sock
heels go not in holes any more, he will think his buttons grow out new
when they fall, and believe that strings make theirselves.’
– Little Women, Louisa May Alcott
Escaping Characters
To create a string that contains a single quote (e.g., Where'd you get
the coconuts?), enclose the string in double quotes:
>>> phrase = "The soldier said, \"You've got two empty halves of a coconut.
>>> print(phrase)
The soldier said, "You've got two empty halves of a coconut."
# or
>>> phrase = 'The soldier said, "You\'ve got two empty halves of a coconut.
>>> print(phrase)
The soldier said, "You've got two empty halves of a coconut."
Notice that the printed output does not contain the backslashes.
Special Characters
Two other common special characters are the newline (\n) and
horizontal tab (\t):
Escape Sequences
Escape Sequence Meaning
\' Single quote
\" Double quote
\\ Backslash
\n Newline
\t Horizontal tab
Raw Strings
If you examine the variable directly without printing it, you see that
each backslash is escaped with another backslash:
>>> my_path
'C:\\news\\[Link]'
Note that backslashes will always escape single and double quotation
marks, even in a raw string, so you cannot end a raw string with a
single backslash:
Bad
my_path = r'C:\my\new\'
Good
my_path = r'C:\my\new\\'
Later, when we learn about regular expressions, you’ll see how raw
strings can come in handy.
Triple Quotes
Triple quotes are used to create multi-line strings. You generally use
three double quotes20 as shown in the following example:
Demo 16: strings/Demos/triple_quotes.py
print("""----------------
LUMBERJACK SONG
I'm a lumberjack
And I'm O.K.
I sleep all night
And I work all day.
----------------""")
Code Explanation
I'm a lumberjack
And I'm O.K.
I sleep all night
And I work all day.
----------------
In some cases when using triple quotes, you may want to break up
your code with a newline without having that newline show up in your
output. You can escape the actual newline with a backslash as shown
in the following demo:
Demo 17: strings/Demos/triple_quotes_newline_escaped.py
Code Explanation
Notice that the backslashes at the end of lines 1, 3, and 5 prevent line
breaks in the output.
Spacing in ebooks
Because ebooks do not have a set font size, it is impossible for
authors to control spacing and line breaks. So, the code above (and
other code samples throughout this book) may wrap in places
where it would not actually wrap when programming. We're sorry
about that! The best way to see how it will really look is to run these
code samples on your computer.
String Indexing
Indexing is the process of finding a specific element within a sequence
of elements through the element’s position. Remember that strings are
basically sequences of characters. We can use indexing to find a
specific character within the string.
If we consider the string from left to right, the first character (the left-
most) is at position 0. If we consider the string from right to left, the
first character (the right-most) is at position -1. The following table
illustrates this for the phrase “MONTY PYTHON”.
M O N T Y P Y T H O N
Left to Right: 0 1 2 3 4 5 6 7 8 9 10 11
Right to Left: -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
Code Explanation
10 to 20 minutes.
In this exercise, you will write a program that gets a specific character
from a phrase entered by the user.
1. Open strings/Exercises/[Link].
2. Modify the main() function so that it:
A. Prompts the user to enter a phrase.
B. Tells the user what phrase they entered (e.g., Your phrase is
'Hello, World!').
C. Prompts the user for a number.
D. Tells the user what character is at that position in the user’s
phrase (e.g., Character number 4 is o).
3. Here is the program completed by the user:
Choose a phrase: Hello, world!
Your phrase is 'Hello, world!'
Which character? [Enter number] 4
Character 4 is o
Challenge
As a Python programmer, you understand that the “o” in “Hello” is at
position 4, because Python starts counting with 0. However, regular
people will think that the character at position 4 is “l” and they will think
your program is wrong. Fix your program so that it responds as the
user expects. Also, to make it a little prettier, output the character in
single quotes.
The program should work like this:
def main():
phrase = input("Choose a phrase: ")
print("Your phrase is '", phrase, "'", sep="")
pos = int(input("Which character? [Enter number] "))
print("Character number", pos, "is", phrase[pos])
main()
Challenge Solution: strings/Solutions/indexing_challenge.py
def main():
phrase = input("Choose a phrase: ")
print("Your phrase is '", phrase, "'", sep="")
pos = int(input("Which character? [Enter number] "))-1
print("Character ", pos+1, " is '", phrase[pos], "'", sep="")
main()
Slicing Strings
Often, you will want to get a sequence of characters from a string (i.e.,
a substring). In Python, you do this by getting a slice of the string
using the following syntax:
substring = orig_string[first_pos:last_pos]
This returns a slice that starts with the character at first_pos and
includes all the characters up to but not including the character at
last_pos.
Code Explanation
10 to 20 minutes.
In this exercise, you will write a program that gets a substring (or slice)
from a phrase entered by the user.
1. Open strings/Exercises/[Link].
2. Modify the main() function so that it:
A. Prompts the user to enter a phrase.
B. Tells the user what phrase they entered (e.g., Your phrase is
'Hello, World!').
C. Prompts the user for a start number.
D. Prompts the user for a end number.
E. Tells the user the substring (within single quotes) that starts
with the start number and ends with the end number.
3. Here is the output of the program:
Choose a phrase: Hello, world!
Your phrase is 'Hello, world!'
Character to start with? [Enter number] 4
Character to end with? [Enter number] 9
Your substring is 'o, wor'
Challenge
As with the last exercise, make your program respond as users would
expect.
The new program should work like this:
Choose a phrase: Hello, world!
Your phrase is 'Hello, world!'
Character to start with? [Enter number] 4
Character to end with? [Enter number] 9
Your substring is 'lo, wo'
Solution: strings/Solutions/[Link]
def main():
phrase = input("Choose a phrase: ")
print("Your phrase is '", phrase, "'", sep="")
pos1 = int(input("Character to start with? [Enter number] "))
pos2 = int(input("Character to end with? [Enter number] ")) + 1
print("Your substring is '", phrase[pos1:pos2], "'", sep="")
main()
Challenge Solution: strings/Solutions/slicing_challenge.py
def main():
phrase = input("Choose a phrase: ")
print("Your phrase is '", phrase, "'", sep="")
pos1 = int(input("Character to start with? [Enter number] "))
pos2 = int(input("Character to end with? [Enter number] "))
print("Your substring is '", phrase[pos1:pos2], "'", sep="")
main()
Concatenation
Code Explanation
Repetition
one_knight_says = "nee"
many_knights_say = one_knight_says * 20
print(many_knights_say)
Code Explanation
neeneeneeneeneeneeneeneeneeneeneeneeneeneeneeneeneeneeneenee
5 to 10 minutes.
def say_hello(name):
print('Hello, ', name, '!', sep='')
def recite_poem():
print("How about a Monty Python poem?")
insert_separator("-", 20)
print("Much to his Mum and Dad's dismay")
print("Horace ate himself one day.")
print("He didn't stop to say his grace,")
print("He just sat down and ate his face.")
def say_goodbye(name):
print('Goodbye, ', name, '!', sep='')
def main():
your_name = input('What is your name? ')
insert_separator("-", 20)
say_hello(your_name)
insert_separator()
recite_poem()
insert_separator()
say_goodbye(your_name)
main()
Notice the output is “abbbc”. In other words, “b” will be repeated three
times before it is concatenated with “a” and “c”.
We can force the concatenation to take place first by using
parentheses:
Code Explanation
Notice that the id of name changes when we modify the string in the
variable.
Because strings are immutable, methods that operate on strings (i.e.,
string methods) cannot modify the string in place. Instead, they return
a value. Sometimes that value is a modified version of the string, but it
is important to understand that the original string is unchanged.
Consider, for example, the upper() method, which returns a string in
all uppercase letters:
If you want to change the original variable, you must assign the
returned value back to the variable:
>>> name = 'Nat'
>>> name = [Link]()
>>> name
'NAT'
>>> 'hELLo'.capitalize()
'Hello'
>>> 'hELLo'.lower()
'hello'
>>> 'hELLo'.upper()
'HELLO'
>>> 'hello world'.title()
'Hello World'
>>> 'hELLo'.swapcase()
'HellO'
This means that the count parameter, which indicates the maximum
number of replacements to be made, is optional.
The syntax allows for nesting optional parameters within optional
parameters. For example:
[Link](sub[, start[, end]])
[Link]()
1. All three will return True if the string contains only Arabic digits
(i.e., 0 through 9):
'42'.isdigit() # True
'42'.isdecimal() # True
'42'.isnumeric() # True
So, which should you use? It doesn’t make much difference really,
but isdigit() is the most popular, perhaps because the name most
closely matches the intention of the function.
If you’re really curious about it, run strings/Demos/[Link], which
will create a [Link] file in the same folder showing tabular
results, like this (but with much more data):
Both of these methods start looking at start index and end looking at
end index if start and end are specified.
>>> 'hello'.startswith('h')
True
>>> 'hello'.endswith('o')
True
is… Methods
All the is… methods shown in the table above return False for empty
strings, meaning strings with zero length.
All of these methods start looking at start index and end looking at
end index if start and end are specified.
Returns the number of times sub is found. Start looking at start index
and end looking at end index.
>>>"Hello World!".count('l')
3
String Formatting
Python includes powerful options for formatting strings.
The curly braces are used to indicate a replacement field, which takes
position arguments specified by index (as in the preceding example)
or by name (as in the following example):
>>> '{movie} is an {adjective} movie!'.format(movie='Monty Python',
... adjective='awesome')
'Monty Python is an awesome movie!'
When the field names are omitted, the first replacement field is at
index 0, the second at index 1, and so on.
These examples really just show another form of concatenation and
could be rewritten like this:
Format Specification
Because the field name is often left out, it is commonly written like
this:
{:format_spec}
[[fill]align][sign][width][,][.precision][type]
That looks a little scary, so let’s break it down from right to left.
Type
[[fill]align][sign][width][,][.precision][type]
# And with the field name and type specifier gone, we can
# remove the colon separator.
sentence = 'On a scale of {} to {}, I give {} a {}.'
sentence = [Link](1, 5, 'Monty Python', 6)
print(sentence)
Code Explanation
The final line of code has the advantage of being brief, but the
disadvantage of being obscure. As a rule, we prefer clarity over
brevity. We can make it clearer using field names:
>>> 'On a scale of {low} to {high}, {movie} is a {rating}.'.format(
'On a scale of 1 to 5, Monty Python is a 6.'
Floats
You will typically format floats as fixed point numbers using f as the
one-letter specifier, which has a default precision of 6. If neither type
nor precision is specified, floats will be as precise as they need to be
to accurately represent the value.
Fixed point type specified:
No type specified:
>>> import math
>>> 'pi equals {}'.format([Link])
'pi equals 3.141592653589793'
Precision
[[fill]align][sign][width][,][.precision][type]
[[fill]align][sign][width][,][.precision][type]
[[fill]align][sign][width][,][.precision][type]
In all three cases, the width of the formatted string is set to 5. Notice
the padding on the first two examples: after the string and before the
number.
In the final example, we format the number 123, but the format type
has been specified as fixed point (f) with a precision of 2. So, the
resulting string ('123.00') is six characters long – longer than the
specified width, so it just returns the full string without padding.
Sign
[[fill]align][sign][width][,][.precision][type]
Alignment
[[fill]align][sign][width][,][.precision][type]
You can change the default alignment by preceding the width (and
sign if there is one) with one of the following options:
Alignment
Options Meaning
< Left aligned (default for strings).
> Right aligned (default for numbers).
= Padding added between sign and digits. Only valid for
numbers.
^ Centered.
Some examples:
>>> '{:>5}'.format('abc')
' abc'
>>> '{:<5}'.format(123)
'123 '
>>> '{:^5}'.format(123)
' 123 '
>>> '{:=+5}'.format(123)
'+ 123'
Fill
[[fill]align][sign][width][,][.precision][type]
By default, spaces are used for padding, but this can be changed by
inserting a fill character before the alignment option, like this (note the
period after the colon):
>>> '{:.^10.2f}'.format([Link])
'...3.14...'
Percentage Type
>>> questions = 25
>>> correct = 18
>>> grade = correct / questions
>>> grade
0.72
>>> '{:.2f}'.format(grade)
'0.72'
>>> '{:.2%}'.format(grade)
'72.00%'
>>> '{:.0%}'.format(grade)
'72%'
location = "ponds"
items = "swords"
beings = "masses"
adjective = "farcical"
# EXAMPLE 2: Concatenation
quote = ("Listen, strange women lyin' in {} " +
"distributin' {} is no basis for a system of " +
"government. Supreme executive power derives from " +
"a mandate from the {}, not from some {} " +
"aquatic ceremony.").format(location, items,
beings, adjective)
print(quote)
print(quote)
Code Explanation
Remember that the backwards slashes at the end of each line escape
the newline character.
Also, notice that the arguments passed to format() are broken across
lines and vertically aligned to make it clear that they are related.
Run the file to see the resulting strings.
10 to 20 minutes.
In this exercise, you will practice formatting strings. Here are two
options for practicing:
Option 1
Option 2
import math
user_name = input("What is your name? ")
# Concatenation:
greeting = "Hello, " + user_name + "!"
# f-string:
greeting = f"Hello, {user_name}!"
print(greeting)
Code Explanation
The curly braces within the f-string contain the variable name and
optionally a format specification. The string literal is prepended with an
f.
len(string)
25 to 40 minutes.
In this exercise, you will write a program that repeatedly prompts the
user for a Company name, Revenue, and Expenses and then outputs
all the information as tab-delimited text. Here is the program after it
has run:
In this exercise, you can use the format() method or f-strings or any
combination of the two.
1. Open strings/Exercises/tab_delimited_text.py.
2. Modify the addheaders() function so that it creates a header row
and appends it to _output. The four headers should each take up
10 spaces, be aligned to the center, and be separated by tabs, like
this:
' Company \t Revenue \t Expenses \t Profit \n'
_output = ""
def addheaders():
# Write your code here
pass
def addrow():
# Write your code here
# The rest of the function prompts the user to add another row
# or quit. On quitting, it prints _output. Leave it as is.
def main():
# Call addheaders() and addrow()
addheaders()
addrow()
main()
Solution: strings/Solutions/tab_delimited_text.py
_output = ""
def add_headers():
global _output
c_header = "{:^10}".format("Company")
r_header = "{:^10}".format("Revenue")
e_header = "{:^10}".format("Expenses")
p_header = "{:^10}".format("Profit")
_output += "{}\t{}\t{}\t{}\n".format(c_header, r_header,
e_header, p_header)
def add_row():
global _output
c = input("Company: ")
r = float(input("Revenue: "))
e = float(input("Expenses: "))
p = r - e # profit
c_str = "{:<10}".format(c)
r_str = "${:>10,.2f}".format(r)
e_str = "${:>10,.2f}".format(e)
p_str = "${:>10,.2f}".format(p)
_output += new_row
-------Lines Omitted-------
Solution: strings/Solutions/tab_delimited_text_f_string.py
_output = ""
def add_headers():
global _output
c_header = f"{'Company':^10}"
r_header = f"{'Revenue':^10}"
e_header = f"{'Expenses':^10}"
p_header = f"{'Profit':^10}"
_output += f"{c_header}\t{r_header}\t{e_header}\t{p_header}\n"
def add_row():
global _output
c = input("Company: ")
r = float(input("Revenue: "))
e = float(input("Expenses: "))
p = r - e # profit
c_str = f"{c:<10}"
r_str = f"${r:>10,.2f}"
e_str = f"${e:>10,.2f}"
p_str = f"${p:>10,.2f}"
new_row = f"{c_str}\t{r_str}\t{e_str}\t{p_str}\n"
_output += new_row
-------Lines Omitted-------
Conclusion
In this lesson, you have learned to manipulate and format strings.
LESSON 6
Iterables: Sequences, Dictionaries, and Sets
Topics Covered
I did not like this iteration of one idea--this strange recurrence of one
image, and I grew nervous as bedtime approached and the hour of the
vision drew near.
– Jane Eyre, Charlotte Bronte
Iterables are objects that can return their members one at a time. The
iterables we will cover in this lesson are lists, tuples, ranges,
dictionaries, and sets.
Definitions
Here are some quick definitions to provide an overview of the different
types of objects we will be covering in this lesson. Don’t worry if the
meanings aren’t entirely clear now. They will be when you finish the
lesson.
1. Lists
2. Tuples
3. Ranges
Lists
Python’s lists are similar to arrays in other languages. Lists are
created using square brackets, like this:
List Methods
Some of the most common list methods are shown in the following list:
The following code illustrates how these methods are used. Try this
out at the Python shell:
>>> colors = ["red", "blue", "green", "orange"]
>>> colors
['red', 'blue', 'green', 'orange']
>>> [Link]("purple") # Append purple to colors
>>> colors
['red', 'blue', 'green', 'orange', 'purple']
>>> [Link]("green") # Remove green from colors
>>> colors
['red', 'blue', 'orange', 'purple']
>>> [Link](2, "yellow") # Insert yellow in position 2
>>> colors
['red', 'blue', 'yellow', 'orange', 'purple']
>>> [Link]("orange") # Get position of orange
3
>>> [Link]() # Sort colors in place
>>> colors
['blue', 'orange', 'purple', 'red', 'yellow']
>>> [Link]() # Reverse order of colors
>>> colors
['yellow', 'red', 'purple', 'orange', 'blue']
>>> [Link]() # Remove and return last element
'blue'
>>> [Link](1) # Remove and return element at position 1
'red'
>>> colors # Notice blue and red have been removed
['yellow', 'purple', 'orange']
>>> colors_copy = [Link]() # Create a copy of colors
>>> colors_copy
['yellow', 'purple', 'orange']
>>> [Link](colors_copy) # Append colors_copy to colors
>>> colors
['yellow', 'purple', 'orange', 'yellow', 'purple', 'orange']
>>> colors_copy.clear() # Delete all elements from colors_copy
>>> colors_copy # Notice colors_copy is now empty
[]
>>> del colors_copy # Delete colors_copy
>>> colors_copy # It's gone
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'colors_copy' is not defined
Copying a List
You can see that sorting colors_copy has no effect on the colors list.
They are two distinct objects. Compare that to the following, which
continues from the code above:
Do you see what’s going on here? When you assign one variable to
another, instead of making a copy, it just creates a pointer to that
object. If you modify one object, it affects both. The copy() method
creates a brand new identical object, so modifying the new object has
no effect on the original.
[Link](seq)
[Link](seq)
import random
def remove_random(the_list):
pass # replace this with your code
def main():
colors = ['red', 'blue', 'green', 'orange']
# Your code here
main()
Solution: iterables/Solutions/remove_random.py
import random
def remove_random(the_list):
x = [Link](the_list)
the_list.remove(x)
return x
def main():
colors = ['red', 'blue', 'green', 'orange']
removed_color = remove_random(colors)
print(f'The removed color was {removed_color}.')
print(f'The remaining colors are {colors}.')
main()
Tuples
Tuples are like lists, but they are immutable: once created, they
cannot be changed.
Get ready for a lie: Tuples are created using parentheses, like this:
But just because you can doesn’t mean you should. It’s a better idea
to get used to including the parentheses, because sometimes you do
need them. To illustrate, take a look at the following code:
Demo 27: iterables/Demos/[Link]
def show_type(obj):
print(type(obj))
Code Explanation
The takeaway here is: Always use parentheses when creating tuples.
Remember Constants
The MAGENTA variable above makes a good constant. The values
represent the amounts of red, green, and blue in the color magenta.
While lists are used for holding collections of like data, tuples are
meant for holding heterogeneous collections of data. In tuples, the
position of the element is meaningful. To illustrate, let’s consider our
MAGENTA constant again:
The values in the tuple correspond to RGB (red, green, blue) color
values. Magenta is created by mixing full red (255) with full blue (255)
and no green (0).
Other use cases for tuples include:
Turtle Graphics
For a little fun, open and run the iterables/Demos/[Link] file. It
uses turtle, a built-in graphics library, to draw shapes from tuples
of coordinates.
Empty Tuple
t_empty = ()
t_single = ("a",)
If you do not include the comma, you just get a string as illustrated in
the following code:
>>> t1 = ("a",)
>>> type(t1)
<class 'tuple'>
>>> t2 = ("a")
>>> type(t2)
<class 'str'>
Ranges
A range is an immutable sequence of numbers often used in for
loops, which will be covered in the Flow Control lesson. Ranges are
created using range(), which can take one, two, or three arguments:
range(stop)
range(start, stop)
range(start, stop, step)
Note that the stop number is not included in the range. You should
read it as "from start up to but not including stop."
15 to 20 minutes.
import random
def main():
pass # replace this with your code
main()
Solution: iterables/Solutions/[Link]
import random
def main():
roshambo = ["Rock", "Paper", "Scissors"]
computer_choice = [Link](roshambo)
print("Computer:", computer_choice)
print("User:", user_choice)
main()
Slicing
Slicing is the process of getting a slice or segment of a sequence as a
new sequence. The syntax is as follows:
sub_sequence = orig_sequence[first_pos:last_pos]
This returns a slice that starts with the element at first_pos and
includes all the elements up to but not including the element at
last_pos.
The following code shows how to slice a list, but the same can be
done with any sequence type. Try this out in the Python shell:
>>> fruit = ["apple", "orange", "banana", "pear", "lemon", "watermelon"]
>>> fruit[0:5]
['apple', 'orange', 'banana', 'pear', 'lemon']
>>> fruit[1:4]
['orange', 'banana', 'pear']
>>> fruit[4:]
['lemon', 'watermelon']
>>> fruit[-3:]
['pear', 'lemon', 'watermelon']
>>> fruit[:3]
['apple', 'orange', 'banana']
>>> fruit[-4:-1]
['banana', 'pear', 'lemon']
>>> fruit[:]
['apple', 'orange', 'banana', 'pear', 'lemon', 'watermelon']
10 to 20 minutes.
import math
def split_list(orig_list):
pass # replace this with your code
def main():
colors = ["red", "blue", "green", "orange", "purple"]
colors_split = split_list(colors)
print(colors_split[0])
print(colors_split[1])
main()
Solution: iterables/Solutions/[Link]
import math
def split_list(orig_list):
list_len = len(orig_list)
mid_pos = [Link](list_len/2)
list1 = orig_list[:mid_pos]
list2 = orig_list[mid_pos:]
return [list1, list2]
-------Lines Omitted-------
Note that, for strings, uppercase letters are “smaller” than lowercase
letters:
>>> min("NatDunn")
'D'
sum(iter[, start])
The sum() function takes an iterable and adds up all of its elements
and then adds the result to start (if it is passed in). For example:
Note, the join() method will error if any of the elements in the
sequence is not a string.
Splitting Strings into Lists
The split() method of a string splits the string into substrings. By
default it splits on whitespace. For example:
>>> sentence = 'We are no longer the Knights Who Say "Ni!"'
>>> list_of_words = [Link]()
>>> list_of_words
['We', 'are', 'no', 'longer', 'the', 'Knights', 'Who', 'Say', '"Ni!"']
Notice the extra space before “ banana”, “ pear”, and “ melon”. To get
rid of that space, you can specify a multi-character separator, like this:
Notice pear and melon are part of the same string element. That’s
because the string stopped splitting after two splits.
Alabama AL
Alaska AK
Arizona AZ
Arkansas AR
California CA
-------Lines Omitted-------
Code Explanation
The file is a listing of states in the United States. Each line lists the
name of the state and its abbreviation, separated by a newline.
The following code can be used to read this file into a list:
Demo 29: iterables/Demos/[Link]
def main():
with open('../data/[Link]') as f:
states = [Link]().splitlines()
main()
Code Explanation
This will read the states from the file into a list. Run the file:
PS …\iterables\Demos> python [Link]
The file contains 50 states.
As you will soon see, having the data in a list makes it much easier to
work with.
Unpacking Sequences
We learned earlier about simultaneous assignment, which allows you
to assign values to multiple variables at once, like this:
You can use the same concept to unpack a sequence into multiple
variables, like this:
>>> about_me = ("Nat", "Dunn", "Webucator")
>>> first_name, last_name, company = about_me
>>> first_name
'Nat'
>>> last_name
'Dunn'
>>> company
'Webucator'
Dictionaries
Dictionaries are mappings that use arbitrary keys to map to values.
They are like associative arrays in other programming languages.
Dictionaries are created with curly braces and comma-delimited key-
value pairs, like this:
>>> dict = {
'key1': 'value 1',
'key2': 'value 2',
'key3': 'value 3'
}
>>> dict['key2'] = 'new value 2' # assign new value to existing key
>>> dict['key4'] = 'value 4' # assign value to new key
>>> print(dict['key1']) # print value of key
value 1
grades = {
"English": 97,
"Math": 93,
"Global Studies": 85,
"Art": 74,
"Music": 86
}
The code samples for the methods that follow assume this dictionary:
grades = {
"English": 97,
"Math": 93,
"Art": 74,
"Music": 86
}
[Link](key[, default])
>>> [Link]('English')
97
>>> [Link]('French') # returns None
>>> [Link]('French', 0)
0
[Link](key[, default])
>>> [Link]('English')
97
>>> grades # Notice 'English' has been removed
{'Math': 93, 'Art': 74, 'Music': 86}
>>> [Link]('English')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'English'
>>> [Link]('English', 'Not found')
'Not found'
[Link]()
>>> [Link]()
('Music', 86)
>>> grades # Notice 'Music' has been removed
{'Math': 93, 'Art': 74}
[Link]()
>>> [Link]()
>>> grades # Dictionary is now empty
{}
update()
All three of the following statements would modify the value of the
"Math" key and add a new "Gym" key:
setdefault()
The setdefault(key, default) method works like this:
If key does not exist in the dictionary, key is added with a value
of default.
If key exists in the dictionary, the value for key is left unchanged.
grades = {
"English": 97,
"Math": 93,
"Art": 74,
"Music": 86
}
Code Explanation
Notice that the value of the Art key did not change. setdefault()
really means add_key_unless_key_already_exists().
The grade for any one of the keys in the setdefault() calls will only be
0 if the dictionary returned by get_data() doesn’t include that key.
[Link]()
[Link]()
[Link]()
As you can see, dict_keys and dict_values look like simple lists and
dict_items looks like a list of tuples. But while they look like lists, they
differ in two important ways:
grades = {
"English": 97,
"Math": 93,
"Global Studies": 85,
"Art": 74,
"Music": 86
}
gradepoints = [Link]()
print("Grade points:", gradepoints)
grades["Art"] = 87
print("Grade points:", gradepoints)
Code Explanation
The output:
Grade points: dict_values([97, 93, 85, 74, 86])
Grade points: dict_values([97, 93, 85, 87, 86])
Notice that the Art grade changes in the output (from 74 to 87). There
was no need to reassign [Link]() to gradepoints as
gradepoints provides a dynamic view into the grades dictionary.
>>> grades = {
"English": 97,
"Math": 93,
"Art": 75
}
>>> list([Link]())
['English', 'Math', 'Art']
>>> list([Link]())
[97, 93, 75]
>>> list([Link]())
[('English', 97), ('Math', 93), ('Art', 75)]
grades = {
"English": 97,
"Math": 93,
"Global Studies": 85,
"Art": 74,
"Music": 86
}
15 to 25 minutes.
Challenge
After printing the average, ask the user to change the grade in one
subject and then get the new average and print it out. Hint: you will
have to prompt the user twice, once for the subject and once for the
grade.
Solution: iterables/Solutions/[Link]
def main():
grades = {}
grades["English"] = int(input("English grade: "))
grades["Math"] = int(input("Math grade: "))
grades["Global Studies"] = int(input("Global Studies grade: "))
grades["Art"] = int(input("Art grade: "))
grades["Music"] = int(input("Music grade: "))
gradepoints = [Link]()
average = sum(gradepoints)/len(gradepoints)
main()
Challenge Solution: iterables/Solutions/[Link]
def avg(gradepoints):
average = sum(gradepoints)/len(gradepoints)
return average
def main():
grades = {}
grades["English"] = int(input("English grade: "))
grades["Math"] = int(input("Math grade: "))
grades["Global Studies"] = int(input("Global Studies grade: "))
grades["Art"] = int(input("Art grade: "))
grades["Music"] = int(input("Music grade: "))
gradepoints = [Link]()
average = avg(gradepoints)
main()
Code Explanation
Note that, as our program now requires calculating the average more
than one time, we have moved that functionality into a new avg()
function.
Sets
Sets are mutable unordered collections of distinct immutable objects.
So, while the set itself can be modified, it cannot be populated with
objects that can be modified. You can also think of sets as dictionaries
in which the keys have no values. In fact, they can be created with
curly braces, just like dictionaries:
>>> classes = {"English", "Math", "Global Studies",
"Art", "Music"}
>>> type(classes)
<class 'set'>
Sets are less commonly used than the other iterables we’ve looked at
in this lesson, but one great use for sets is to remove duplicates from
a list as shown in the following example:
>>> veggies = ["tomato", "spinach", "pepper", "pea", "tomato", "pea"
>>> v_set = set(veggies) # converts to set and remove duplicates
>>> v_set
{'pepper', 'tomato', 'spinach', 'pea'}
>>> veggies = list(v_set) # converts back to list
>>> veggies
['pepper', 'tomato', 'spinach', 'pea']
def remove_dups(the_list):
return list(set(the_list))
Using *args
This works fine if you know that there will be between two and five
numbers passed into add_nums(), but you can use *args to allow the
function to accept an arbitrary number of numbers:
Demo 34: iterables/Demos/add_nums.py
def main():
add_nums(1, 2)
add_nums(1, 2, 3, 4, 5)
add_nums(11, 12, 13, 14)
add_nums(101, 201, 301)
main()
Code Explanation
Using **kwargs
But, you see, the Land of Oz has never been civilized, for we are cut
off from all the rest of the world. Therefore we still have witches and
wizards amongst us.
– The Wonderful Wizard of Oz, L. Frank Baum
20 to 30 minutes.
Mac / Linux
echo $PATH
You should see the path of the Scripts (Windows) or bin (Mac)
directory prepended to the list of paths:
(.venv) PS …\virtual-environments\Demos> $Env:PATH
C:\Webucator\Python\virtual-environments\Demos\.venv\Scripts;
Because it is at the beginning of $PATH, the Scripts or bin directory
will be scanned first to resolve references to executable files. Once
you deactivate the virtual environment, the Scripts and bin
directory will be removed from $PATH.
6. To deactivate (exit) the virtual environment, run:
(.venv) PS …\virtual-environments\Demos> deactivate
pip list
Run pip list at the prompt in your standard environment. You should
see something like this (your packages may be different):
PS …\virtual-environments\Demos\> pip list
Package Version
----------------- -------
astroid 2.3.3
colorama 0.4.3
isort 4.3.21
lxml 4.5.0
mccabe 0.6.1
pip 20.0.2
pylint 2.4.4
setuptools 45.2.0
six 1.13.0
wrapt 1.11.2
In the next exercise, you will learn how to write these requirements to
a [Link] file and then use that file to set up a new virtual
environment. That way, when you share project code with other
developers, you can include the [Link] file so they can
easily set up a virtual environment for the project on their computer.
Exercise 21: Working with a Virtual Environment
15 to 25 minutes.
Instructions
Mac / Linux
source .venv/bin/activate
4. Run pip freeze. This will output a list of the packages installed in
the virtual environment. At this point, it won’t return anything as we
haven’t installed any packages yet.
5. Install the playsound package:
(.venv) PS …\virtual-environments\Exercises> pip install playsound
6. Now, when you run pip freeze, you will see the playsound
package and its version:
(.venv) PS …\virtual-environments\Exercises> pip freeze
playsound==1.2.2
pip freeze outputs the installed packages in the format pip needs
to install them. Write the output to a file in the Exercises directory
by running:
(.venv) PS …\virtual-environments\Exercises> pip freeze > requirements.
7. Open the new [Link] file in Visual Studio Code. You will
see that it contains just one line showing the playsound package
and its version.
8. In the Exercises directory, we have included some sound files and
a [Link] file that makes use of playsound to play the sounds. At
the terminal, run:
(.venv) PS …\virtual-environments\Exercises> python [Link]
11. Delete the virtual environment by deleting the .venv folder at the
command line, in VS Code’s Explorer, or using the file system.
12. Now, recreate the virtual environment and activate it. Run:
(.venv) PS …\virtual-environments\Exercises> python -m venv .venv
if conditions in Python.
Loops in Python.
Generator functions.
List comprehensions.
But the path began nowhere and ended nowhere, and it remained
mystery, as the man who made it and the reason he made it remained
mystery.
– The Call of the Wild, Jack London
Notice that the do_this_1() and do_this_2() function calls are both
indented indicating that they are part of the if block. They will only run
if some_conditions evaluates to True. The do_this_after() function is
not indented. That indicates that the if block has ended and it will run
regardless of the value of some_conditions.
elif (for else if) conditions are only evaluated when the if
condition is False. They are evaluated in order up until one
evaluates to True, at which point, that elif block is executed
and the rest of the elif conditions are skipped. An if statement
can have zero or more elif conditions.
The else block is only executed if the if condition and all the
elif conditions evaluate to False. An if statement can have zer
or one else conditions.
if some_conditions:
do_this_1()
do_this_2()
elif other_conditions:
do_this_3()
else:
do_this_4()
do_this_5()
do_this_after()
>>> bool(None)
False
>>> bool(0)
False
>>> bool(0.0)
False
>>> bool('')
False
>>> bool([])
False
not in
def main():
age = int(input('How old are you? '))
main()
Code Explanation
You can see the different results by running this file and entering
different ages at the prompt:
PS …\flow-control\Demos> python [Link]
How old are you? 17
You cannot vote or drink.
PS …\flow-control\Demos> python [Link]
How old are you? 19
You can vote, but can't drink.
PS …\flow-control\Demos> python [Link]
How old are you? 21
You can vote and drink.
Compound Conditions
More complex if statements often require that several conditions be
checked. The following table shows and and or operators for checking
multiple conditions and the not operator for negating a boolean value
(i.e., turning True to False or vice versa).
Logical Operators
Operator Name Example
and AND if a and b:
or OR if a or b:
not NOT if not a:
def main():
age = int(input('How old are you? '))
main()
Code Explanation
If the user enters “Y” or “y”, is_citizen will be True. Otherwise, it will
be False.
Two identical lists are assigned to the a and b variables, but, because
they are assigned separately, they are two separate objects. So, while
a == b is True, a is b is False.
The all() function returns True if all the elements of the iterable
evaluate to True.
The any() function returns True if any of the elements of the
iterable evaluate to True.
Ternary Operator
Many programming languages, including Python, have a ternary
conditional operator, which is most often used for conditional variable
assignment. To illustrate, consider this code:
if season == 'summer':
pant_color = 'white'
else:
pant_color = 'black'
That’s very clear, but it takes four lines of code to assign a value to
pant_color.
Here is how we could use this syntax to assign our pants’ color:
pant_color = 'white' if season == 'summer' else 'black'
It’s still pretty clear, but much shorter. Note that the expression could
be any type of expression, including a function call, that returns a
value that evaluates to True or False. For example, if you had an
is_summer() function, you could do this:
For example:
age = 15
if 12 < age < 20:
print("You are a teenager.")
This same syntax works with other types of objects (e.g., strings and
dates) as well.
Loops in Python
As the name implies, loops are used to loop (or iterate) over code
blocks. The following section shows examples of the two different
types of loops in Python: while loops and for loops.
You can find the loop examples used in this section in flow-
control/Demos/[Link]
while Loops
num=0
while num < 6:
print(num)
num += 1
You can combine while loops with user input to continually prompt the
user until he or she enters an acceptable value. The following
example illustrates this:
Demo 37: flow-control/Demos/while_input.py
def is_valid_age(s):
return [Link]() and 1 <= int(s) <= 113
def main():
age = input('How old are you? ')
while not is_valid_age(age):
age = input('Please enter a real age as a number: ')
age = int(age)
if age >= 21:
print('You can vote and drink.')
elif age >= 18:
print('You can vote, but can\'t drink.')
else:
print('You cannot vote or drink.')
main()
Code Explanation
Run the file at the terminal and try entering some non-integer values:
PS …\flow-control\Demos> python while_input.py
How old are you? twenty
Please enter a real age as a number: I'm 20
Please enter a real age as a number: 20!
Please enter a real age as a number: 20
You can vote, but can't drink.
import random
def is_valid_num(s):
# Make sure the number is a digit between 1 and 100.
return [Link]() and 1 <= int(s) <= 100
def main():
# Get a random number between 1 and 100
number = [Link](1, 100)
# Set num_guesses to 0.
# We'll increment it by 1 after each guess
num_guesses = 0
main()
for Loops
A for loop is used to loop through an iterator (e.g., a range, list, tuple,
dictionary, etc.). The syntax is as follows:
for item in sequence_name:
do_something(item)
0
1
2
3
4
5
Remember that a range goes up to but does not include the stop
value.
Skipping Steps
for num in range(1, 11, 2): # 3rd argument is the step
print(num)
1
3
5
7
9
Stepping Backwards
nums = [0, 1, 2, 3, 4, 5]
for num in nums:
print(num)
nums = (0, 1, 2, 3, 4, 5)
for num in nums:
print(num)
0
1
2
3
4
5
Both of the loops above will return the courses (the keys):
English
Math
Art
Music
97
93
74
86
This loop will return the courses (the keys) and grades (the values):
English: 97
Math: 93
Art: 74
Music: 86
The first item in each of the tuples above is the course and the second
item is the grade.
Another way of writing the loop shown above would be:
for item in [Link]():
course = item[0]
grade = item[1]
print(f'{course}: {grade}')
But, the original way, which unpacks the tuples into the course and
grade variables with each iteration, is more pythonic.
In this exercise, you will use loops to write all_true() and any_true()
functions that behave the same way as the built-in all() and any()
functions.
def all_true(iterable):
# write function
pass
def any_true(iterable):
# write function
pass
def main():
a = all_true([1, 0, 1, 1, 1])
b = all_true([1, 1, 1, 1, 1])
c = any_true([0, 0, 0, 1, 1])
d = any_true([0, 0, 0, 0, 0])
main()
Solution: flow-control/Solutions/all_and_any.py
def all_true(iterable):
# Return False if any item is False
for item in iterable:
if not item:
return False
# If we made it through the loop without returning False,
# then all items are True
return True
def any_true(iterable):
# Return True if any item is True
for item in iterable:
if item:
return True
# If we made it through the loop without returning True,
# then all items are False
return False
def main():
a = all_true([1, 0, 1, 1, 1])
b = all_true([1, 1, 1, 1, 1])
c = any_true([0, 0, 0, 1, 1])
d = any_true([0, 0, 0, 0, 0])
main()
11
12
13
14
15
This will skip to the next iteration when num is divisible by 5, resulting
in:
1
2
3
4
6
7
8
9
11
import random
def is_valid_num(s):
return [Link]() and 1 <= int(s) <= 100
def main():
number = [Link](1, 100)
guessed_number = False
guess = input("Guess a number between 1 and 100: ")
num_guesses = 0
num_guesses += 1
guess = int(guess)
main()
Alabama AL
Alaska AK
Arizona AZ
Arkansas AR
California CA
-------Lines Omitted-------
Code Explanation
Again, this file is a listing of states in the United States. Each line lists
the name of the state and its abbreviation, separated by a tab.
Let’s see how we can use the data in this file to write a program that
allows the user to enter a state abbreviation and get the state name in
return:
Demo 41: flow-control/Demos/get_state.py
def get_state(abbreviation):
# Read the data from the file into a list
with open('../data/[Link]') as f:
states = [Link]().splitlines()
def main():
# Loop until break statement
while True:
abbr = input('State abbreviation (q to quit): ').upper()
Code Explanation
45 to 120 minutes.
import random
def get_word():
"""Returns random word."""
words = ['Charlie', 'Woodstock', 'Snoopy', 'Lucy', 'Linus',
'Schroeder', 'Patty', 'Sally', 'Marcie']
return [Link](words).upper()
return status
def main():
word = get_word() # The random word
n = len(word) # The number of letters in the random word
guesses = [] # The list of guesses made so far
guessed = False
print('The word contains {} letters.'.format(n))
main()
2. The program selects a secret word and prints: “The word contains
n letters.” For example:
The word contains 6 letters.
Challenge
Create your own list of words in a separate file in the flow-control/data
folder (or use the Harry Potter spells in the [Link] file that’s already
there) and use the words from that file in the get_word() function.
Solution: flow-control/Solutions/guess_word.py
-------Lines Omitted-------
def check(word, guesses):
"""Creates and returns string representation of word
displaying asterisks for letters not yet guessed."""
status = '' # Current status of guess
last_guess = guesses[-1]
matches = 0 # Number of occurrences of last_guess in word
if letter == last_guess:
matches += 1
if matches > 1:
print('The word has {} "{}"s.'.format(matches, last_guess))
elif matches == 1:
print('The word has one "{}".'.format(last_guess))
else:
print('Sorry. The word has no "{}"s.'.format(last_guess))
return status
def main():
word = get_word() # The random word
n = len(word) # The number of letters in the random word
guesses = [] # The list of guesses made so far
guessed = False
print('The word contains {} letters.'.format(n))
main()
Challenge Solution: flow-control/Solutions/guess_word_challenge.py
import random
def get_word():
"""Returns random word."""
with open('../data/[Link]') as f:
words = [Link]().splitlines()
return [Link](words).upper()
-------Lines Omitted-------
If you were not able to get your program to work, study the solutions
and then go back and try to fix your code. If you’re not able to fix it
after studying it once, try again. That iterative process of attacking a
problem is excellent practice for real-world development.
In Python, for and while loops have an optional else clause, which is
executed after the loop has successfully completed iterating (i.e.,
without a break). The following demo shows how it works:
Demo 42: flow-control/Demos/loop_else.py
def main():
print('Example 1: for loop')
num = int(input('Enter a number: '))
for i in range(5):
if i == num:
break
print(i)
else:
print(f'Completed iterating without reaching {num}.')
main()
Code Explanation
Second Run
PS …\flow-control\Demos> python loop_else.py
Example 1: for loop
Enter a number: 7
0
1
2
3
4
Completed iterating without reaching 7.
The first run through, we entered numbers lower than 5, so the loops
were broken out of before completing. The second run through, we
entered numbers higher than 5, so the loops completed all iterations,
which led to the else clause also being executed.
So, when would you use this? You might use it to check user-entered
text for black-listed words. The following code shows a common way
to do this in other programming languages:
sentence = input('Input a sentence: ')
found_bad_word = False
for word in [Link]():
if word in BLACK_LIST:
found_bad_word = True
break
if found_bad_word:
print('You used a naughty word.')
else:
print('Sentence passes cleanliness test.')
The code in the following file shows how you can accomplish the
same thing in a cleaner way using the else clause:
Demo 43: flow-control/Demos/loop_else_use_case.py
BLACK_LIST = [
'shitake',
'sugar',
'fudge',
'gosh',
'darn',
'dang',
'heck'
]
def main():
sentence = input('Input a sentence: ')
main()
Code Explanation
Here are the results of running this file, first with a “clean” sentence
and then with a “dirty” one:
PS …\flow-control\Demos> python loop_else_use_case.py
Input a sentence: I like cream in my coffee.
Sentence passes cleanliness test.
PS …\flow-control\Demos> python loop_else_use_case.py
Input a sentence: I like sugar in my coffee.
You used a naughty word.
While using the else clause is not functionally better than the other
method shown, it is cleaner, and it saves us from having to create and
check the found_bad_word flag.
10 to 20 minutes.
In this exercise, you will rewrite a function to use the else clause of a
loop.
2. Modify the main() function so that it uses the for loop’s else
clause.
3. Run the script again. It should work in exactly the same way.
Exercise Code: flow-control/Exercises/[Link]
def is_state(state):
with open('../data/[Link]') as f:
states = [Link]().splitlines()
state_abbreviations = []
for state_row in states:
state_abbreviation = state_row.split('\t')[1]
state_abbreviations.append(state_abbreviation)
def main():
print('Name as many state abbreviations do you know?')
print('Separate them with spaces:')
states = input('').split()
bad_state = False
for state in states:
state = [Link]()
if not is_state(state):
print(f'{state} is not a state.')
bad_state = True
break
if not bad_state:
print(f'You named {len(states)} states.')
main()
Solution: flow-control/Solutions/[Link]
-------Lines Omitted-------
def main():
print('Name as many state abbreviations do you know?')
print('Separate them with spaces:')
states = input('').split()
for state in states:
state = [Link]()
if not is_state(state):
print(f'{state} is not a state.')
break
else:
print(f'You named {len(states)} states.')
main()
i = 1
for item in ['a', 'b', 'c']:
print(i, item, sep='. ')
i += 1
Code Explanation
1. a
2. b
3. c
The more Pythonic way of accomplishing the same thing is to use the
enumerate() function, like this:
Demo 45: flow-control/Demos/with_enum.py
def main():
with open('../data/[Link]') as f:
states = [Link]().splitlines()
main()
Generators
Generators are special iterators that can only be iterated through one
time. Generators are created with special generator functions. Before
looking at one, let’s first consider how a standard iterator (e.g., a list)
works:
Demo 47: flow-control/Demos/list_loop.py
import random
Code Explanation
import random
Code Explanation
Generator
def get_rand_nums(low, high, num):
for number in range(num):
yield [Link](low, high)
The two functions work in essentially the same way, but the second
function is much cleaner as there is no need for the local numbers
variable. Rather than creating a full list and then returning it, the
generator function yields each result one by one as it works its way
through the for loop. The only functional difference, as the example
illustrates, is that you can only iterate through the generator one time.
However, if you want to iterate through multiple times, you can simply
call the generator function again as shown in the next example:
Demo 49: flow-control/Demos/generator_loop2.py
import random
Code Explanation
Use generators whenever you want to be able to get the next item in a
sequence without creating the whole sequence ahead of time. Some
examples:
Infinite Sequences
Demo 50: flow-control/Demos/odd_numbers.py
def odd_numbers():
i = -1
while True:
i += 2
yield i
def main():
for i in odd_numbers():
print(i)
if input('Enter for next or q to quit: ') == 'q':
print('Goodbye!')
break
main()
Code Explanation
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
def main():
for i in fibonacci():
print(i)
if input('Enter for next or q to quit: ') == 'q':
print('Goodbye!')
break
main()
Code Explanation
In Bingo, the caller keeps picking out numbers until someone wins. It’s
impossible to know ahead of time how many numbers must be drawn.
Demo 52: flow-control/Demos/[Link]
import random
def bingo():
balls = {
'B': list(range(1, 16)),
'I': list(range(16, 31)),
'N': list(range(31, 46)),
'G': list(range(46, 61)),
'O': list(range(61, 76))
}
while balls:
letter = [Link](list([Link]()))
number = [Link](balls[letter])
balls[letter].remove(number) # Remove number from letter list
if not balls[letter]: # Letter has no more numbers
print('Removing', letter)
del balls[letter] # Delete letter from dictionary
yield (letter, number)
def main():
for ball in bingo():
print(ball)
if input('Enter for next ball or q to quit') == 'q':
print('Goodbye!')
break
else:
print('All out of balls.')
main()
Code Explanation
This generator function keeps spitting out Bingo balls until it runs out
of balls.
Here's a Bingo card, in case you want to try your luck:
import random
def roshambo(weapons):
while True:
yield [Link](weapons)
if your_weapon == python_weapon:
print('Tie: You both chose', your_weapon)
elif ((your_weapon == 'Scissors' and python_weapon == 'Paper')
or (your_weapon == 'Paper' and python_weapon == 'Rock')
or (your_weapon == 'Rock' and python_weapon == 'Scissors')):
print('You win:', your_weapon, 'beats', python_weapon)
else:
print('You lose:', python_weapon, 'beats', your_weapon)
print('--------')
def make_choice():
choice = input("""Choose your weapon:
1: Rock
2: Paper
3: Scissors
q: Quit
""")
return choice
def main():
weapons = ['Rock', 'Paper', 'Scissors']
python_weapons = roshambo(weapons)
choice = make_choice()
while choice in ['1', '2', '3']:
play(weapons, choice, python_weapons)
choice = make_choice()
print('Goodbye!')
main()
Code Explanation
List Comprehensions
List comprehensions are used to create new lists from existing
sequences by taking a subset of that sequence and/or modifying its
members. To understand the purpose, let’s first look at creating a new
list from a list using a for loop:
squares = []
for i in range(10):
[Link](i*i)
This same thing can be done much more tersely with a list
comprehension. The syntax is:
new_list = [modify(item) for item in items]
def initials(name):
fullname = [Link](' ')
inits = (fullname[0][0], fullname[1][0])
return inits
def main():
names = ['Graham Chapman', 'John Cleese', 'Eric Idle',
'Terry Gilliam', 'Terry Jones', 'Michael Palin']
inits = [initials(name) for name in names]
for i in inits:
print(f'{i[0]}.{i[1]}.')
main()
Code Explanation
G.C.
J.C.
E.I.
T.G.
T.J.
M.P.
def main():
add_nums(1, 2)
add_nums(1, 2, 3, 4, 5)
add_nums(11, 12, 13, 14)
add_nums(101, 201, 301)
main()
def main():
add_nums(1, 2)
add_nums(1, 2, 3, 4, 5)
add_nums(11, 12, 13, 14)
add_nums(101, 201, 301)
main()
Code Explanation
This uses a list comprehension to convert the list of numbers into a list
of strings and then joins that list on ', ' in one step. The output will be:
The sum of 2 and 1 is 3.
The sum of 2, 3, 4, 5 and 1 is 15.
The sum of 12, 13, 14 and 11 is 50.
The sum of 201, 301 and 101 is 603.
Assume that you want to get all the three-letter words from a list of
words. First, let’s do it without a list comprehension:
three_letter_words = []
for word in words:
if len(word) == 3:
three_letter_words.append(word)
def main():
words = ['Woodstock', 'Gary', 'Tucker', 'Gopher', 'Spike', 'Ed',
'Faline', 'Willy', 'Rex', 'Rhino', 'Roo', 'Littlefoot',
'Bagheera', 'Remy', 'Pongo', 'Kaa', 'Rudolph', 'Banzai',
'Courage', 'Nemo', 'Nala', 'Alvin', 'Sebastian', 'Iago']
three_letter_words = [w for w in words if len(w) == 3]
print(three_letter_words)
main()
Code Explanation
This code combines an if condition within a for loop into a single line.
It will return:
['Rex', 'Roo', 'Kaa']
def main():
with open('../data/[Link]') as f:
lines = [Link]().splitlines()
states_with_spaces = []
main()
Code Explanation
This reads in the content of the [Link] file and then splits it into a
list of lines. It then loops through that list to create a new list of just the
states that have spaces in their names. It will output:
1. New Hampshire
2. New Jersey
3. New Mexico
4. New York
5. North Carolina
6. North Dakota
7. Rhode Island
8. South Carolina
9. South Dakota
10. West Virginia
You could replace the for loop with a couple of list comprehensions,
like this:
Demo 59: flow-control/Demos/states_with_spaces2.py
-------Lines Omitted-------
states = [[Link]('\t')[0] for line in lines]
states_with_spaces = [state for state in states if ' ' in state]
-------Lines Omitted-------
Code Explanation
1. The first list comprehension gets all the state names from the lines
in the file.
2. The second list comprehension then uses that list to get just the
state names that have spaces in them.
-------Lines Omitted-------
states_with_spaces = [
[Link]('\t')[0] # Get state
for line in lines # For each line
if ' ' in [Link]('\t')[0] # Where there is a space in state
]
-------Lines Omitted-------
Code Explanation
While that’s a bit harder to read, the code is more efficient, because it
doesn’t have to loop through lines and then loop again through
states. It just does all the work the first time through the loop.
Conclusion
In this lesson, you have learned to write if-elif-else conditions and
to loop through sequences. You also learned about the enumerate()
function, generators, and list comprehensions.
LESSON 9
Exception Handling
Topics Covered
By this time, you’ve probably run into some exceptions (errors) in your
Python scripts. In this lesson, we will learn how to anticipate and
handle exceptions gracefully.
Exception Basics
You may remember from school that you cannot divide by zero. Two
or three people can share a pizza. One person can eat a whole pizza
alone. But zero people cannot eat a pizza. The pizza will never get
eaten. In Python, if you try to divide by zero, you will get a
ZeroDivisionError exception:
>>> 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
In some cases, you may be fine with allowing the Python interpreter to
report these exceptions as it finds them. But in other cases, you will
want to anticipate and catch these exceptions and handle them in
some special way. You do this in Python with try/except blocks.
Here’s a very simple example:
Demo 61: exception-handling/Demos/[Link]
try:
1/0
except:
print('You cannot divide by zero!')
You can have multiple except clauses with each clause specifying the
type of exception to catch:
Demo 62: exception-handling/Demos/[Link]
def divide():
try:
numerator = int(input('Enter a numerator: '))
denominator = int(input('Enter a denominator: '))
result = numerator / denominator
print(numerator, 'over', denominator, 'equals', result)
except ValueError:
print('Integers only please. Try again.')
divide()
except ZeroDivisionError:
print('You cannot divide by zero! Try again.')
divide()
except KeyboardInterrupt:
print('Quitter!')
except:
print('I have no idea what went wrong!')
def main():
divide()
main()
Code Explanation
1. If the user enters anything other than an integer for the numerator
or denominator, the int() function will fail and a ValueError
exception will be thrown.
2. If the user enters 0 for the denominator, a ZeroDivisionError
exception will be thrown.
3. If the user quits the program by pressing CTRL+C, a
KeyboardInterrupt exception will be thrown.
4. The last except clause, which is optional, serves as a wildcard to
catch any unspecified exception types.
Run the program and try entering different values to see how it works.
PS …\exception-handling\Demos> python [Link]
Integers only please. Try again.
Enter a numerator: 10
Enter a denominator: 0
You cannot divide by zero! Try again.
Enter a numerator: 10
Enter a denominator: 2
10 over 2 equals 5.0
PS …\exception-handling\Demos> python [Link]
Enter a numerator: 5
Enter a denominator: Quitter!
try:
1/0
except:
print('Something really bad just happened! Oh no, oh no, oh no!')
raise
Code Explanation
This first prints the friendly error (if you want it to be friendly) and then
outputs the interpreter’s traceback:
Something really bad just happened! Oh no, oh no, oh no!
Traceback (most recent call last):
File ".\[Link]", line 2, in <module>
1/0
ZeroDivisionError: division by zero
try:
1/0
except Exception as e:
print(type(e)) # prints exception type
print(e) # prints friendly message
Code Explanation
Note that e is an arbitrary variable name. You can name the variable
whatever you like.
15 to 25 minutes.
try:
# code that creates exception
except Exception as e:
print(type(e))
print(e, '\n')
# ZeroDivisionError
try:
1/0
except Exception as e:
print(type(e))
print(e, '\n')
# ValueError
try:
int('a')
except Exception as e:
print(type(e))
print(e, '\n')
# NameError
try:
print(foo)
except Exception as e:
print(type(e))
print(e, '\n')
# FileNotFoundError
try:
open('[Link]', 'r')
except Exception as e:
print(type(e))
print(e, '\n')
# ImportError
try:
import non_existing_module
except Exception as e:
print(type(e))
print(e, '\n')
# TypeError
try:
nums = [1, 2] # Lists are iterables, not iterators
next(nums)
except Exception as e:
print(type(e))
print(e, '\n')
# AttributeError
try:
greeting = 'Hello'
[Link]() # strings don't have a print() method
except Exception as e:
print(type(e))
print(e, '\n')
# StopIteration
try:
nums = [1, 2]
iter_nums = iter(nums)
print(next(iter_nums))
print(next(iter_nums))
print(next(iter_nums))
except Exception as e:
print(type(e))
print(e, '\n')
# KeyError
try:
grades = {'English':97, 'Math':93, 'Art':74, 'Music':86}
print(grades['Science'])
except Exception as e:
print(type(e))
print(e, '\n')
Code Explanation
<class 'ValueError'>
invalid literal for int() with base 10: 'a'
<class 'NameError'>
name 'foo' is not defined
<class 'FileNotFoundError'>
[Errno 2] No such file or directory: '[Link]'
<class 'ModuleNotFoundError'>
No module named 'non_existing_module'
<class 'TypeError'>
'list' object is not an iterator
<class 'AttributeError'>
'str' object has no attribute 'print'
1
2
<class 'StopIteration'>
<class 'KeyError'>
'Science'
def divide():
try:
numerator = int(input('Enter a numerator: '))
denominator = int(input('Enter a denominator: '))
result = numerator / denominator
except ValueError:
print('Integers only please. Try again.')
divide()
except ZeroDivisionError:
print('You cannot divide by zero! Try again.')
divide()
except KeyboardInterrupt:
print('Quitter!')
raise
except:
print('I have no idea what went wrong!')
else:
print(numerator, 'over', denominator, 'equals', result)
def main():
divide()
main()
Code Explanation
As we’re fairly confident that the final print() function call will not
raise a ValueError or a ZeroDivisionError, it makes sense to include it
within the else clause.
The crux of this code is that the resource should only be closed (and
always be closed) if it was successfully opened, whether or not some
exception occurred after it was opened. If the resource fails to open,
then we should not try to close it, so [Link]() cannot go in a
hypothetical finally block in the outer try. Note that this is an
advanced scenario, so don’t worry too much if you don’t fully
understand it. Generally, you won’t have to use the finally block in
exception handling.
Using Exceptions for Flow Control
In Python, it’s not uncommon to use exception handling as a method
of flow control. For example, rather than using a condition to check to
make sure everything is in order before proceeding, your code can
simply try to proceed and then respond appropriately if an exception is
raised. Compare the following square_num() function, which uses
exception handling, with the cube_num() function, which uses an if
condition to accomplish the same thing:
Demo 66: exception-handling/Demos/try_else.py
def square_num():
try:
num = int(input('Input Integer: '))
except ValueError:
print('That is not an integer.')
else:
print(num, 'squared is', num**2)
def cube_num():
num = input('Input Number: ')
if [Link]():
print(num, 'cubed is', int(num)**3)
else:
print('That is not an integer.')
Code Explanation
The difference between using if/else and try/except for flow control is
the difference between asking for permission and asking for
forgiveness.
10 to 20 minutes.
def main():
total = 0
while True:
num = input('Enter a number (q to quit): ')
if [Link]() == 'q':
print('Goodbye!')
break
if not [Link]():
print('Integers only please. Try again.')
else:
total += int(num)
print('The current total is:', total)
main()
Solution: exception-handling/Solutions/running_sum.py
def main():
total = 0
while True:
try:
num = input('Enter a number: ')
if [Link]() == 'q':
print('Goodbye!')
break
num = int(num) # This might cause an error
except ValueError:
print('Integers only please. Try again.')
else:
total += num
print('The current total is:', total)
main()
You can use this new dictionary to look up values in either direction.
But what if you pass bidict() a dictionary that has two keys with the
same values, like this:
It won’t error, but it won’t return the mapping you want, because the
bye key can only have one value:
The value of the bye key is 'adios', but there is no key with the value
of 'ciao'.
Consider the loop in the bidict() function. Each time it assigns a
value to a key. But if that key already exists, it overwrites the existing
value.
One way to handle this is to raise an exception, like this:
def bidict(d):
d2 = [Link]()
for k,v in [Link]():
if v in [Link]():
raise KeyError('Cannot create bidirectional dict ' +
'with duplicate keys.')
d2[v] = k
return d2
Now, when a programmer tries to pass a dict that won’t work with the
function, it will raise an exception. Here is the full code:
Demo 67: exception-handling/Demos/bidict_with_exception.py
def bidict(d):
d2 = [Link]()
for k, v in [Link]():
if v in [Link]():
raise KeyError('Cannot create bidirectional dict ' +
'with duplicate keys.')
d2[v] = k
return d2
Code Explanation
Run this and you will see that the call creating translator works, but
the call creating translator2 does not:
hi
hola
Traceback (most recent call last):
File ".\bidict_with_exception.py", line 14, in <module>
translator2 = bidict({'hola':'hi', 'adios':'bye', 'ciao':'bye'})
File ".\bidict_with_exception.py", line 5, in bidict
raise KeyError('Cannot create bidirectional dict ' +
KeyError: 'Cannot create bidirectional dict with duplicate keys.'
Conclusion
In this lesson, you have learned to handle Python exceptions. For
further study, see [Link]
LESSON 10
Python Dates and Times
Topics Covered
It was the best of times, it was the worst of times,… it was the epoch
of belief, it was the epoch of incredulity…
– A Tale of Two Cities, Charles Dickens
The Epoch
The most useful built-in modules for working with dates and times in
Python are time and datetime.
Clocks
1. [Link]()
2. time.perf_counter()
3. time.process_time()
4. [Link]()
Absolute Time
import time
seconds_since_epoch = [Link]()
minutes_since_epoch = seconds_since_epoch / 60
hours_since_epoch = minutes_since_epoch / 60
days_since_epoch = hours_since_epoch / 24
years_since_epoch = days_since_epoch / 365.25
print("""s: {:,}
m: {:,}
h: {:,}
d: {:,}
y: {:,}""".format(seconds_since_epoch,
minutes_since_epoch,
hours_since_epoch,
days_since_epoch,
years_since_epoch, sep='\n'))
Code Explanation
The preceding code will render the following (on March 06, 2020 at
08:28:21):
s: 1,583,501,301.986899
m: 26,391,688.366448317
h: 439,861.4727741386
d: 18,327.56136558911
y: 50.17812831099003
Relative Time
The other clocks return relative times from an indeterminate start time.
They are useful in determining differences between moments of time.
The most useful of these will be time.perf_counter() as it provides
the most precise results. It is often used to measure how quickly a
piece of code runs.
To illustrate how you would use this, assume that you wanted to
create a list of random integers between 1 and 100 and that you
needed the list as a string.
The most straightforward way of doing this is to create a loop and
concatenate a new random number onto your string with each
iteration.
But string concatenation is much less efficient than appending to a list,
so it’s actually faster to create a list, append a random number with
each iteration, and then when the loop is complete join the list into a
string.
And for a slightly faster solution, use a list comprehension. Here’s the
code:
Demo 69: date-time/Demos/compare_times.py
import time
import random
# Concatenating strings
start_time = time.perf_counter()
numbers = ''
for i in range(iterations):
num = [Link](1, 100)
numbers += ',' + str(num)
end_time = time.perf_counter()
td1 = end_time - start_time
# Appending to a list
start_time = time.perf_counter()
numbers = []
for i in range(iterations):
num = [Link](1, 100)
[Link](str(num))
numbers = ', '.join(numbers)
end_time = time.perf_counter()
td2 = end_time - start_time
And here are the results using different values for integers:
time.struct_time(tm_year=1970,
tm_mon=1, # 1 = January
tm_mday=1,
tm_hour=0,
tm_min=0,
tm_sec=0,
tm_wday=3, # 3 = Thursday (0 = Monday)
tm_yday=1, # 1 = First day of year
tm_isdst=0) # 0 = No daylight savings time
[Link]([secs])
Yesterday as struct_time
>>> day_in_seconds = 60*60*24
>>> yesterday = [Link]([Link]() - day_in_seconds)
>>> yesterday
time.struct_time(tm_year=2020, tm_mon=3, tm_mday=5,
tm_hour=20, tm_min=12, tm_sec=2,
tm_wday=3, tm_yday=65, tm_isdst=0)
Tomorrow as struct_time
>>> day_in_seconds = 60*60*24
>>> tomorrow = [Link]([Link]() + day_in_seconds)
>>> tomorrow
time.struct_time(tm_year=2020, tm_mon=3, tm_mday=7,
tm_hour=20, tm_min=12, tm_sec=5,
tm_wday=5, tm_yday=67, tm_isdst=0)
[Link]([secs])
>>> lt = [Link]()
>>> lt
time.struct_time(tm_year=2020, tm_mon=3, tm_mday=6,
tm_hour=14, tm_min=33, tm_sec=32,
tm_wday=4, tm_yday=66, tm_isdst=0)
Times as Strings
When comparing or doing any sort of calculations with dates and
times, it is necessary to treat them as objects or numbers; however,
when outputting dates in reports, it is more useful to see their human-
readable string representations.
Methods that create times as strings include :
[Link]([t])
Formatting Directives
import time
epoch = [Link](0)
print([Link]('%c', epoch)) # 01/01/70 00:00:00
print([Link]('%x', epoch)) # 01/01/70
print([Link]('%X', epoch)) # 00:00:00
print([Link]('%A, %B %d, %Y, %I:%M %p', epoch))
# Thursday, January 01, 1970, 12:00 AM
Code Explanation
import time
print('Blast off!')
Code Explanation
This will output the following, printing a new line every half second:
10
9
8
7
6
5
4
3
2
1
Blast off!
import time
def start_clock():
print("Starting Clock")
try:
while True:
localtime = [Link]()
result = [Link]("%I:%M:%S %p", localtime)
print(result)
[Link](1)
except KeyboardInterrupt:
print("Stopping Clock")
start_clock()
Code Explanation
This will output something like the following, printing a new time every
second until the user presses Ctrl+C:
Starting Clock
10:45:02 AM
10:45:03 AM
10:45:04 AM
10:45:05 AM
10:45:06 AM
Stopping Clock
[Link] Objects
[Link] Attributes
year
month
day
[Link] Methods
>>> i_day.ctime()
'Thu Jul 4 00:00:00 1776'
[Link] Objects
hour – defaults to 0.
minute – defaults to 0.
second – defaults to 0.
microsecond – defaults to 0.
tzinfo – defaults to None, which makes the datetime object
“naive,” meaning that it is unaware of timezones. It is common
to set tzinfo to UTC time like this
tzinfo=[Link].
[Link] Attributes
hour
minute
second
microsecond
[Link] Objects
A [Link] object is a combination of a [Link] object
and a [Link] object. There are a number of datetime methods
for creating [Link] objects:
[Link] Attributes
year
month
day
hour
minute
second
microsecond
[Link] Methods
>>> moon_landing.ctime()
'Mon Jul 21 02:56:15 1969'
15 to 30 minutes.
def is_summer(the_date=[Link]()):
# Get the year of the passed-in [Link] object
year = the_date.year
def main():
# Use ternary operator to assign pant color
pant_color = 'white' if is_summer() else 'black'
print(f'You should wear {pant_color} pants.')
main()
[Link] Objects
A [Link] object expresses a duration – the time between
two date, time, or datetime objects.
[Link] objects can be…
days
seconds
microseconds
[Link].total_seconds() Method
To determine the time delta between two dates, simply subtract one
from the other:
45 to 90 minutes.
In this exercise, you will create a small report on departure times from
July, 1980. All the data is in a text file (date-time/data/departure-
[Link]). Some of the data is shown below:
Exercise Code: date-time/data/[Link]
*Scheduled Actual
07/01/1980 2:40 AM 07/01/1980 4:00 AM
07/01/1980 3:00 AM 07/01/1980 3:00 AM
07/01/1980 4:40 AM 07/01/1980 4:40 AM
07/01/1980 5:30 AM 07/01/1980 5:30 AM
07/01/1980 6:00 AM 07/01/1980 6:01 AM
-------Lines Omitted-------
07/25/1980 7:00 PM 07/25/1980 7:09 PM
07/25/1980 7:01 PM 07/25/1980 7:01 PM
07/25/1980 7:15 PM
07/25/1980 7:53 PM 07/25/1980 7:53 PM
07/25/1980 8:00 PM 07/25/1980 8:00 PM
-------Lines Omitted-------
07/31/1980 9:10 PM 07/31/1980 9:10 PM
07/31/1980 10:05 PM 07/31/1980 10:05 PM
07/31/1980 10:45 PM 07/31/1980 10:45 PM
07/31/1980 11:15 PM 07/31/1980 11:15 PM
Things to note:
def get_departures():
departures = []
with open('../data/[Link]') as f:
for line in [Link]().splitlines():
departure = get_departure(line)
if departure:
[Link](departure)
return departures
def get_departure(line):
"""Return a tuple containing two datetime objects."""
def left_ontime(departure):
planned = departure[0]
actual = departure[1]
if not actual:
return False
return actual == planned
def left_late(departure):
pass
def left_next_day(departure):
pass
def did_not_run(departure):
pass
def main():
departures = get_departures()
ontime_departures = [d for d in departures if left_ontime(d)]
early_departures = [d for d in departures if left_early(d)]
late_departures = [d for d in departures if left_late(d)]
next_day_departures = [d for d in departures if left_next_day(d)]
cancelled_trips = [d for d in departures if did_not_run(d)]
main()
-------Lines Omitted-------
def get_departure(line):
"""Return a tuple containing two datetime objects."""
if line[0] == '*':
return None
departure = [Link]('\t')
planned = departure[0]
actual = departure[1]
if actual:
date_actual = [Link](actual, '%m/%d/%Y %I:%M %p')
else: # Date doesn't exist
date_actual = None
def left_ontime(departure):
"""Return True if left ontime. False, otherwise."""
planned = departure[0]
actual = departure[1]
if not actual:
return False
return actual == planned
def left_early(departure):
"""Return True if left early. False, otherwise."""
planned = departure[0]
actual = departure[1]
if not actual:
return False
if actual < planned:
print('Early:', departure)
return actual < planned
def left_late(departure):
"""Return True if left late. False, otherwise."""
planned = departure[0]
actual = departure[1]
if not actual:
return False
return actual > planned
def left_next_day(departure):
"""Return True if departed next day. False, otherwise."""
planned = departure[0]
actual = departure[1]
if not actual:
return False
return [Link] > [Link]
def did_not_run(departure):
"""Return True if did not depart. False, otherwise."""
return not departure[1]
-------Lines Omitted-------
Code Explanation
If you were not able to get your program to work, study the solution
and then go back and try to fix your code. If you’re not able to fix it
after studying it once, try again. That iterative process of attacking a
problem is excellent practice for real-world development.
Conclusion
In this lesson, you have learned to work with the time and datetime
modules.
LESSON 11
File Processing
Topics Covered
Reading files.
Creating and writing to files.
Working with directories.
Working with the os and [Link] modules.
He made haste to sit down in his easy chair and opened the book. He
tried to read, but he could not revive the very vivid interest he had felt
before in Egyptian hieroglyphics.
– Anna Karenina, Leo Tolstoy
Python allows you to access and modify files and directories on the
operating system. Among other things, you can:
1. Open new or existing files and store them in file object variables.
2. Read file contents, all at once or line by line.
3. Append to file contents.
4. Overwrite file contents.
5. List directory contents.
6. Rename files and directories.
Opening Files
The built-in open() function will attempt to open a file at a given path
and return a corresponding file object, which can be read and, if
opened with the right permissions, written to. This is the most basic
syntax for opening a file:
open(path_to_file, file_mode)
path_to_file can either be a relative or an absolute path. File modes
are described in the following list:
Reading Files
Once you have a file object, you can read the file contents using any
of the following methods:
Closing Files
You should always be sure to close files when you are done with
them. This can be done with the [Link]() method like this:
f = open('my_files/zen_of_python.txt')
# Do stuff with file
[Link]()
Using this structure, we don’t have to explicitly close the file with
[Link](). File objects have a special built-in __exit__() method that
closes the file and is always called at the end of a with block even if
code within the with block raises an exception. Often, you will read a
file into a variable within a with block and then use the variable
throughout the script, as shown in the following demo:
Demo 73: file-processing/Demos/[Link]
with open('my_files/zen_of_python.txt') as f:
poem = [Link]()
# The file is now closed, but we saved its content in the poem variable
for i, line in enumerate([Link](), 1):
print(f"{i}. {line}")
Code Explanation
15 to 25 minutes.
In this exercise, you will create a tool for searching through a file. This
is the starting code:
Exercise Code: file-processing/Exercises/word_search.py
def main():
# Open my_files/zen_of_python.txt and
# create a list from its contents
# Save the list as zop
main()
Challenge
Modify the code so that it prints all the lines in which the word is found,
like this:
def main():
with open('my_files/zen_of_python.txt') as f:
zop = [Link]()
main()
Challenge Solution: file-processing/Solutions/word_search_challenge.py
def main():
with open('my_files/zen_of_python.txt') as f:
zop = [Link]()
main()
Writing to Files
Files opened with a mode that permits writing can be written to using
the write() method as shown in the following file:
Demo 74: file-processing/Demos/simple_write.py
Code Explanation
5 to 10 minutes.
In this exercise, you will learn how modes affect reading and writing
files. This exercise starts out like the previous demo.
Code Explanation
By changing the mode to “a+”, the file is opened for appending and
reading. However, when a file is opened for appending, the cursor is
placed at the end of the file, and when you read from the file, it begins
where the cursor is, which is why print([Link]()) doesn’t output
anything.
Challenge Solution: file-processing/Solutions/simple_write_challenge.py
Code Explanation
Placing the cursor at the beginning of the file with [Link](0) makes it
possible to read the entire contents of the file.
30 to 45 minutes.
In this exercise, you will create a module that allows you to maintain a
list of items in a file.
def add_item(item):
"""Appends item (after stripping leading and trailing
whitespace) to [Link] followed by newline character
Keyword arguments:
item -- the item to append"""
pass
def remove_item(item):
"""Removes first instance of item from [Link]
If item is not found in [Link], alerts user.
Keyword arguments:
item -- the item to remove"""
pass
def delete_list():
"""Deletes the entire contents of the list by opening
[Link] for writing."""
pass
def print_list():
"""Prints list"""
pass
def show_instructions():
"""Prints instructions"""
print("""OPTIONS:
P
-- Print List
+abc
-- Add 'abc' to list
-abc
-- Remove 'abc' from list
--all
-- Delete entire list
Q
-- Quit\n""")
def main():
show_instructions()
while True:
choice = input('>> ')
if [Link]() == 'q':
print('Goodbye!')
break
if [Link]() == 'p':
print_list()
elif [Link]() == '--all':
delete_list()
elif choice and choice[0] == '+':
add_item(choice[1:])
elif choice and choice[0] == '-':
remove_item(choice[1:])
else:
print("I didn't understand.")
show_instructions()
if __name__ == '__main__':
main()
Challenge
1. The delete_list() function currently deletes the list without
warning. Give the user a chance to confirm before deleting the list.
2. In the print_list() function, use the enumerate() function to print
the items as a numbered list.
Solution: file-processing/Solutions/list_creator.py
def add_item(item):
"""Appends item (after stripping leading and trailing
whitespace) to [Link] followed by newline character
Keyword arguments:
item -- the item to append"""
with open('[Link]', 'a') as f:
[Link]([Link]() + '\n')
def remove_item(item):
"""Removes first instance of item from [Link]
If item is not found in [Link], alerts user.
Keyword arguments:
item -- the item to remove"""
item_found = False
with open('[Link]', 'r') as f:
items = [Link]().splitlines()
if item in items:
[Link](item)
item_found = True
else:
print('"' + item + '" not found in list.')
if item_found:
with open('[Link]', 'w') as f:
[Link]('\n'.join(items) + '\n')
def delete_list():
"""Deletes the entire contents of the list by opening
[Link] for writing."""
with open('[Link]', 'w') as f:
print('The list has been deleted.')
def print_list():
"""Prints list"""
with open('[Link]', 'r') as f:
print([Link]())
-------Lines Omitted-------
Challenge Solution: file-processing/Solutions/list_creator_challenge.py
-------Lines Omitted-------
def delete_list():
"""After confirming user really wants to delete list,
deletes the entire contents of the list by opening
[Link] for writing."""
confirm = input('Are you sure you want to delete the list? y/n ')
if [Link]() == 'y':
with open('[Link]', 'w') as f:
print('The list has been deleted.')
else:
print('OK. Whew! That was a close one.')
def print_list():
"""Prints list"""
with open('[Link]', 'r') as f:
for i, item in enumerate(f, 1):
print(i, '. ' + item, sep='', end='')
-------Lines Omitted-------
The os Module
The os module is a built-in Python module for interacting with the
operating system. In this section, we cover some of its most useful
methods.
>>> [Link]('..')
>>> [Link]()
'C:\\Webucator\\Python\\file-processing'
import os
dir_contents = [Link]("my_files")
for item in dir_contents:
print(item)
Code Explanation
This prints out a list of files and directories in the my_files directory:
import os
[Link]("my_files/a")
[Link]("my_files/a/b/c")
Code Explanation
import os
foo = 'my_files/[Link]'
bar = 'my_new_files/[Link]'
def foo2bar():
[Link](foo, bar)
print('Renamed', foo, 'to', bar)
def bar2foo():
[Link](bar, foo)
print('Renamed', bar, 'to', foo)
foo2bar()
Code Explanation
Walking a Directory
[Link](top, topdown=True, onerror=None, followlinks=False)
[Link]() Parameters
import os
def spaces2dashes():
for dirpath, dirnames, filenames in [Link]('my_files'):
for fname in filenames:
if ' ' in fname:
oldname = dirpath + '/' + fname
newname = dirpath + '/' + [Link](' ', '-')
print("Replacing", oldname, "with", newname)
[Link](oldname, newname)
def dashes2spaces():
for dirpath, dirnames, filenames in [Link]('my_files'):
for fname in filenames:
if '-' in fname:
oldname = dirpath + '/' + fname
newname = dirpath + '/' + [Link]('-', ' ')
print("Replacing", oldname, "with", newname)
[Link](oldname, newname)
spaces2dashes()
Code Explanation
>>> [Link]('.')
'C:\\Webucator\\Python\\file-processing\\Demos'
[Link](path)
>>> [Link]('my_files/[Link]')
'[Link]'
[Link](path)
>>> [Link]('my_files/[Link]')
'my_files'
[Link](path)
>>> [Link]('my_files/[Link]')
True
[Link](path)
>>> [Link]('my_files/[Link]')
26216
[Link](path)
>>> [Link]('my_files/[Link]')
False
[Link](path, start)
[Link](path, start) returns a relative path to path from
start, which defaults to the current directory.
>>> [Link](r'C:\Webucator\Python\file-processing\Demos\my_files\lo
'my_files\\[Link]'
>>> [Link]('my_files/[Link]')
True
>>> [Link]('my_files/[Link]')
False
[Link](path, *paths)
[Link](path)
>>> [Link]('my_files/[Link]')
('my_files', '[Link]')
[Link](path)
with open('my_files/zen_of_python.txt') as f:
poem = [Link]()
# The file is now closed, but we saved its content in the poem variable
for i, line in enumerate([Link](), 1):
print(f"{i}. {line}")
The issue is that Python is not looking for the file relative to where the
Python file is; it is looking for it relative to where the Python script is
being executed. One solution is to turn the relative path into an
absolute path. To do that, we need the special __file__ variable
(that’s two underscores on each side of “file”.
__file__ is a special variable that holds a relative path to the Python
script from the present working directory. To see how it works, run file-
processing/Demos/special_file_variable.py, which simply runs
print(__file__), from different directories:
PS …\file-processing\Demos> cd ..
PS …\Python\file-processing> python Demos/special_file_variable.py
Demos/special_file_variable.py
PS …\Python\file-processing> cd ..
PS …\Webucator\Python> python file-processing/Demos/special_file_variable.p
file-processing/Demos/special_file_variable.py
PS …\Webucator\Python> cd file-processing/Exercises
PS …\file-processing\Exercises> python ../Demos/special_file_variable.py
../Demos/special_file_variable.py
We can take the absolute path to the folder holding the Python script
and combine it with the parts of the relative path to the file we want to
open to create an absolute path, like this:
relative_path = "my_files/zen_of_python.txt"
# Join dir with path_parts to get an absolute path to the file to open.
new_path = [Link](folder, *path_parts)
The following script includes print() calls showing the values of the
different variables:
Demo 80: file-processing/Demos/relpath_to_abspath.py
import os
relative_path = "my_files/zen_of_python.txt"
print("__file__:", __file__)
# Join dir with path_parts to get an absolute path to the file to open.
new_path = [Link](folder, *path_parts)
print("new_path:", new_path)
Code Explanation
For reuse purpose, we can create a function that takes a relative path
to the file and returns an absolute path to the same file:
def file_path(relative_path):
folder = [Link]([Link](__file__))
path_parts = relative_path.split('/')
new_path = [Link](folder, *path_parts)
return new_path
import os
def file_path(relative_path):
folder = [Link]([Link](__file__))
path_parts = relative_path.split("/")
new_path = [Link](folder, *path_parts)
return new_path
path_to_file = file_path("my_files/zen_of_python.txt")
with open(path_to_file) as f:
poem = [Link]()
45 to 90 minutes.
An article in the Atlantic37 claims that parents give girls boys names,
but won’t give boys girls names. Journalists need data to make this
kind of claim. They rely on data analysts, many of whom are Python
programmers like you, to analyze the data.
In the file-processing/data directory, there are files that contain lists of
the most popular names in 1880 and 2018:
1. [Link]
2. [Link]
3. [Link]
4. [Link]
The data in these files is from [Link]
Using the data in these files, try to answer these questions:
1. What names that were exclusively boys names in 1880 became
exclusively girls names in 2018?
2. What names that were exclusively girls names in 1880 became
exclusively boys names in 2018?
Does what you found support the journalist’s claim?
Spend as much time as you like on this. It is an opportunity to practice
your new Python skills.
Solution: file-processing/Solutions/boys_and_girls.py
import os
def file_path(relative_path):
"""Returns absolute path from relative path"""
folder = [Link]([Link](__file__))
path_parts = relative_path.split("/")
new_path = [Link](folder, *path_parts)
return new_path
def file_to_list(path):
"""Returns content of file at path as list"""
with open(file_path(path)) as f:
lines = [Link]().splitlines()
return lines
if sort:
dup_list.sort()
return dup_list
boys_2018 = file_to_list(boys_2018_path)
girls_2018 = file_to_list(girls_2018_path)
boys_1880 = file_to_list(boys_1880_path)
girls_1880 = file_to_list(girls_1880_path)
list_to_file("../data/[Link]",
boys_names_2_girls_names)
list_to_file("../data/[Link]",
girls_names_2_boys_names)
main()
Code Explanation
There are different ways of going about this. We went through the
following process:
Conclusion
In this lesson, you have learned to work with files and directories on
the operating system.
LESSON 12
PEP8 and Pylint
Topics Covered
PEP8.
Pylint.
PEP838 is the official style guide for Python code. Pylint is software
that helps you follow the PEP8 guidelines and find potential problems
with your code. In this lesson, you will learn about both.
PEP8
Here we provide a summary of PEP8’s primary recommendations. We
do not cover everything. We recommend that you read PEP8 yourself
(see [Link] Be aware that you
may not understand everything as some of it deals with advanced
Python functionality that we have not covered.
Indentation
When you must wrap a line of code across lines to adhere to the
maximum line length, you should either:
Blank Lines
def do_that():
pass
def do_this_other_thing():
pass
# More Code
UTF-8 Encoding
Bad
from datetime import date, time
import math
Bad
import math, random
Quotes
Bad
if ( a == b ):
Good
all([a, b, c, d])
Bad
all( [a, b, c, d] )
2. Before a comma:
Good
all([a, b, c, d])
Bad
all([a , b , c, d])
Bad
def dups(list1, list2, sort = True):
Bad
greeting='Hello'
Good
a += 1
Bad
a+=1
Good
if (a == 1):
Bad
if (a==1):
Comments
pylint path_to_file
------------------------------------------------------------------
Your code has been rated at 9.30/10 (previous run: 8.25/10, +1.05)
For each line that Pylint deems problematic, it provides the script
name, the line number, the character number, the Pylint message
code, the Pylint message, and the abbreviated message, as shown in
the following image:
[Link]
We have included a [Link] script in the root folder of your class files.
This will run Pylint on all the Python files in a directory and its
subdirectories. Run the file from the directory it is in like this:
python [Link] path_to_folder output_file
For example:
python [Link] date-time/Exercises > [Link]
After running this, you will find a [Link] file in the root directory
containing a Pylint report on every Python file in the date-
time/Exercises directory. You can then make fixes and run it again to
generate a new report.
The pylintrc file, which is also in the root of your class files, has the
settings used by [Link]. We have added a few modifications to the
default settings:
Lambda functions.
Advanced list comprehensions.
The collections module.
Mapping and filtering.
Sorting sequences.
Unpacking sequences in function calls.
Modules and packages.
Packing the basket was not quite such pleasant work as unpacking
the basket.
It never is.
– The Wind in the Willows, Kenneth Grahame
In this lesson, you will learn about some Python functionality and
techniques that are commonly used but require a solid foundation in
Python to understand.
Lambda Functions
Lambda functions are anonymous functions that are generally used to
complete a small task, after which they are no longer needed. The
syntax for creating a lambda function is:
lambda arguments: expression
Lambda functions are almost always used within other functions, but
for demonstration purposes, we could assign a lambda function to a
variable, like this:
f = lambda n: n**2
f(5) # Returns 25
f(2) # Returns 4
def main():
words = ['Woodstock', 'Gary', 'Tucker', 'Gopher', 'Spike', 'Ed',
'Faline', 'Willy', 'Rex', 'Rhino', 'Roo', 'Littlefoot',
'Bagheera', 'Remy', 'Pongo', 'Kaa', 'Rudolph', 'Banzai',
'Courage', 'Nemo', 'Nala', 'Alvin', 'Sebastian', 'Iago']
three_letter_words = [w for w in words if len(w) == 3]
print(three_letter_words)
main()
Code Explanation
And here is a new example, in which we map all the elements in one
list to another using a function:
Demo 83: advanced-python-concepts/Demos/list_comp_mapping.py
def get_inits(name):
# Create list from first letter of each name part
inits = [name_part[0] for name_part in [Link]()]
# Join inits list on "." and append "." to end
return '.'.join(inits) + '.'
def main():
people = ['George Washington', 'John Adams',
'Thomas Jefferson', 'John Quincy Adams']
main()
Code Explanation
Assume that you need to create a list of tuples showing all the
possible permutations of rolling two six-sided dice. When dealing with
permutations, order matters, so (1, 2) and (2, 1) are not the same.
First, let’s look at how we would do this without a list comprehension.
We need to use a nested for loop:
Demo 84: advanced-python-concepts/Demos/dice_rolls.py
def main():
dice_rolls = []
for a in range(1, 7):
for b in range(1, 7):
roll = (a, b)
dice_rolls.append(roll)
print(dice_rolls)
main()
Code Explanation
def main():
dice_rolls = [
(a, b)
for a in range(1, 7)
for b in range(1, 7)
]
print(dice_rolls)
main()
Code Explanation
This code will create the same list of tuples containing all the possible
permutations of two dice rolls.
Notice that the list of permutations contains what game players would
consider duplicates. For example, (1, 2) and (2, 1) are considered
the same in dice. We can remove these pseudo-duplicates by starting
the second for loop with the current value of a in the first for loop.
Let’s do this first without a list comprehension:
Demo 86: advanced-python-concepts/Demos/dice_combos.py
def main():
dice_rolls = []
for a in range(1, 7):
for b in range(a, 7):
roll = (a, b)
dice_rolls.append(roll)
print(dice_rolls)
main()
Code Explanation
The first time through the outer loop, the inner loop from 1 to 7 (not
including 7), the second time through, it will loop from 2 to 7, then 3 to
7, and so on…
The dice_rolls list will now contain the different possible rolls (from a
dice rolling point of view):
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6),
(2, 2), (2, 3), (2, 4), (2, 5), (2, 6),
(3, 3), (3, 4), (3, 5), (3, 6),
(4, 4), (4, 5), (4, 6),
(5, 5), (5, 6),
(6, 6)]
Now, let’s see how we can do the same thing with a list
comprehension:
Demo 87: advanced-python-concepts/Demos/dice_combos_list_comp.py
def main():
dice_rolls = [
(a, b)
for a in range(1, 7)
for b in range(a, 7)
]
print(dice_rolls)
main()
Code Explanation
This code will create the same list of tuples containing all the possible
combinations of two dice rolls.
10 to 15 minutes.
Code Explanation
Collections Module
The collections module includes specialized containers (objects that
hold data) that provide more specific functionality than Python’s built-
in containers (list, tuple, dict, and set). Some of the more useful
containers are named tuples (created with the namedtuple() function),
defaultdict, and Counter.
Named Tuples
Imagine you are creating a game in which you need to set and get the
position of a target. You could do this with a regular tuple like this:
# Set target position:
target_pos = (100, 200)
namedtuple(typename, field_names)
Code Explanation
With regular dictionaries, trying to modify a key that doesn’t exist will
cause an exception. For example, the following code will result in a
KeyError:
foo = {}
foo['bar'] += 1
{
2: 1,
3: 2,
4: 3,
5: 4,
6: 5,
7: 6,
8: 5,
9: 4,
10: 3,
11: 2,
12: 1
}
roll_counts = {}
for roll in dice_rolls:
try:
roll_counts[sum(roll)] += 1
except KeyError:
roll_counts[sum(roll)] = 1
roll_counts = defaultdict(int)
for roll in dice_rolls:
roll_counts[sum(roll)] += 1
The result is a defaultdict object that can be treated just like a normal
dictionary:
defaultdict(<class 'int'>, {
2: 1,
3: 2,
4: 3,
5: 4,
6: 5,
7: 6,
8: 5,
9: 4,
10: 3,
11: 2,
12: 1
})
-------Lines Omitted-------
roll_counts = {}
for roll in dice_rolls:
if sum(roll) in roll_counts:
roll_counts[sum(roll)] += 1
else:
roll_counts[sum(roll)] = 1
Demo 90: advanced-python-concepts/Demos/dict_try_except.py
-------Lines Omitted-------
roll_counts = {}
for roll in dice_rolls:
try:
roll_counts[sum(roll)] += 1
except KeyError:
roll_counts[sum(roll)] = 1
Demo 91: advanced-python-concepts/Demos/[Link]
roll_counts = defaultdict(int)
>>> int()
0
You can create default dictionaries with any number of functions, both
built-in and user-defined:
defaultdict with Built-in Functions
a = defaultdict(list) # Default key value will be []
b = defaultdict(str) # Default key value will be ''
15 to 20 minutes.
In this exercise, you will organize the 1927 New York Yankees by
position by creating a default dictionary that looks like this:
defaultdict(<class 'list'>,
{
'OF': ['Earle Combs', 'Cedric Durst', 'Bob Meusel',
'Ben Paschal', 'Babe Ruth'],
'C': ['Benny Bengough', 'Pat Collins', 'Johnny Grabowski'],
'2B': ['Tony Lazzeri', 'Ray Morehart'],
'SS': ['Mark Koenig'],
'3B': ['Joe Dugan', 'Mike Gazella', 'Julie Wera'],
'P': ['Walter Beall', 'Joe Giard', 'Waite Hoyt',
'Wilcy Moore', 'Herb Pennock', 'George Pipgras',
'Dutch Ruether', 'Bob Shawkey', 'Urban Shocker',
'Myles Thomas'],
'1B': ['Lou Gehrig']
})
yankees_1927 = [
{'position': 'P', 'name': 'Walter Beall'},
{'position': 'C', 'name': 'Benny Bengough'},
{'position': 'C', 'name': 'Pat Collins'},
{'position': 'OF', 'name': 'Earle Combs'},
{'position': '3B', 'name': 'Joe Dugan'},
{'position': 'OF', 'name': 'Cedric Durst'},
{'position': '3B', 'name': 'Mike Gazella'},
{'position': '1B', 'name': 'Lou Gehrig'},
{'position': 'P', 'name': 'Joe Giard'},
{'position': 'C', 'name': 'Johnny Grabowski'},
{'position': 'P', 'name': 'Waite Hoyt'},
{'position': 'SS', 'name': 'Mark Koenig'},
{'position': '2B', 'name': 'Tony Lazzeri'},
{'position': 'OF', 'name': 'Bob Meusel'},
{'position': 'P', 'name': 'Wilcy Moore'},
{'position': '2B', 'name': 'Ray Morehart'},
{'position': 'OF', 'name': 'Ben Paschal'},
{'position': 'P', 'name': 'Herb Pennock'},
{'position': 'P', 'name': 'George Pipgras'},
{'position': 'P', 'name': 'Dutch Ruether'},
{'position': 'OF', 'name': 'Babe Ruth'},
{'position': 'P', 'name': 'Bob Shawkey'},
{'position': 'P', 'name': 'Urban Shocker'},
{'position': 'P', 'name': 'Myles Thomas'},
{'position': '3B', 'name': 'Julie Wera'}
]
# Loop through list of yankees appending player names to their position key
for player in yankees_1927:
positions[player['position']].append(player['name'])
print(positions['P'])
Code Explanation
Add the following line of code to the for loop to watch as the players
get added:
print(player['position'], positions[player['position']])
Although there are different ways of creating counters, they are most
often created with an iterable, like this:
[
2, 3, 4, 5, 6, 7,
3, 4, 5, 6, 7, 8,
4, 5, 6, 7, 8, 9,
5, 6, 7, 8, 9, 10,
6, 7, 8, 9, 10, 11,
7, 8, 9, 10, 11, 12
]
Counter({
7: 6,
6: 5,
8: 5,
5: 4,
9: 4,
4: 3,
10: 3,
3: 2,
11: 2,
2: 1,
12: 1
})
The code in the following file creates and outputs the colors and dice
rolls counters:
Demo 92: advanced-python-concepts/Demos/[Link]
dice_rolls = [(a,b)
for a in range(1,7)
for b in range(1,7)]
Updating Counters
Notice that the value for the 'yellow' and 'purple' keys are negative,
which is a little odd. We will learn how to create a non-negative
counter in a later lesson.
10 to 15 minutes.
In this exercise, you will create a counter that holds the most common
words used and the number of times they show up in the U.S.
Declaration of Independence.
with open('Declaration_of_Independence.txt') as f:
doi = [Link]()
c = Counter(word_list)
print(c.most_common(10))
map(function, iterable, …)
The built-in map() function is used to sequentially pass all the values of
an iterable (or multiple iterables) to a function and return an iterator
containing the returned values. It can be used as an alternative to list
comprehensions. First, consider the following code sample that does
not use map():
Demo 93: advanced-python-concepts/Demos/without_map.py
def main():
nums1 = range(0, 10)
nums2 = range(10, 0, -1)
multiples = []
for i in range(len(nums1)):
multiple = multiply(nums1[i], nums2[i])
[Link](multiple)
main()
Code Explanation
The following code sample does the same thing using map():
Demo 94: advanced-python-concepts/Demos/with_map.py
def main():
nums1 = range(0, 10)
nums2 = range(10, 0, -1)
main()
Code Explanation
We could also include the map() function right in the for loop:
Note that you can accomplish the same thing with a list
comprehension:
def is_odd(num):
return num % 2
def main():
nums = range(0, 10)
odd_nums = []
for num in nums:
if is_odd(num):
odd_nums.append(num)
main()
Code Explanation
The following code sample does the same thing using filter():
Demo 96: advanced-python-concepts/Demos/with_filter.py
def is_odd(num):
return num % 2
def main():
nums = range(0, 10)
main()
Code Explanation
As with map(), we can include the filter() function right in the for
loop:
for num in filter(is_odd, nums):
print(num)
Again, you can accomplish the same thing with a list comprehension:
The map() and filter() functions are both often used with lambda
functions, like this:
>>> v1 = [1, 2]
>>> v2 = v1
>>> id(v1) == id(v2)
True # Both variables point to the same list
>>> v1, v2
([1, 2], [1, 2])
>>> v2 += [3]
>>> v1, v2
([1, 2, 3], [1, 2, 3])
>>> id(v1) == id(v2)
True # Both variables still point to the same list
Notice that with lists, v2 changes when we change v1. Both are
pointing at the same list object, which is mutable. So, when we modify
the v2 list, we see the change in v1, because it points to the same
object.
Be careful though. If you use the assignment operator, you will
overwrite the old list and create a new list object:
>>> v1 = [1, 2]
>>> v2 = v1
>>> v1, v2
([1, 2], [1, 2])
>>> v1 = v1 + [3]
>>> v1, v2
([1, 2, 3], [1, 2])
Sorting
Python lists have a sort() method that sorts the list in place:
The sort() method can take two keyword arguments: key and
reverse.
reverse
key
The key argument takes a function to be called on each list item and
performs the sort based on the result. For example, the following code
will sort by word length:
colors = ['red', 'blue', 'green', 'orange']
[Link](key=len)
[
'John Adams',
'John Quincy Adams',
'Thomas Jefferson',
'George Washington'
]
Note that John Quincy Adams shows up after John Adams in the
result only because he shows up after him in the initial list. Our code
as it stands does not take into account middle or first names.
If you don’t want to create a new named function just to perform the
sort, you can use a lambda function. For example, the following code
would do the same thing as the code above without the need for the
get_lastname() function:
The key and reverse arguments can be combined. For example, the
following code will sort by word length in descending order:
15 to 25 minutes.
In this exercise, you will convert all the examples of sort() we saw
earlier to use sorted() instead.
ww2_leaders = [
('Charles', 'de Gaulle'),
('Winston', 'Churchill'),
('Teddy', 'Roosevelt'), # Not a WW2 leader, but helps make point
('Franklin', 'Roosevelt'),
('Joseph', 'Stalin'),
('Adolph', 'Hitler'),
('Benito', 'Mussolini'),
('Hideki', 'Tojo')
]
ww2_leaders.sort()
The ww2_leaders list will be sorted by first name and then by last
name. It will now contain:
[
('Adolph', 'Hitler'),
('Benito', 'Mussolini'),
('Charles', 'de Gaulle'),
('Franklin', 'Roosevelt'),
('Hideki', 'Tojo'),
('Joseph', 'Stalin'),
('Teddy', 'Roosevelt'),
('Winston', 'Churchill')
]
To change the order of the sort, use a lambda function:
ww2_leaders.sort(key=lambda leader: (leader[1], leader[0]))
The ww2_leaders list will now be sorted by last name and then by first
name. It will now contain:
[
('Winston', 'Churchill'),
('Adolph', 'Hitler'),
('Benito', 'Mussolini'),
('Franklin', 'Roosevelt'),
('Teddy', 'Roosevelt'),
('Joseph', 'Stalin'),
('Hideki', 'Tojo'),
('Charles', 'de Gaulle')
]
It may seem strange that “de Gaulle” comes after “Tojo,” but that is
correct. Lowercase letters come after uppercase letters in sorting. To
change the result, you can use the lower() function:
[
('Winston', 'Churchill'),
('Charles', 'de Gaulle'),
('Adolph', 'Hitler'),
('Benito', 'Mussolini'),
('Franklin', 'Roosevelt'),
('Teddy', 'Roosevelt'),
('Joseph', 'Stalin'),
('Hideki', 'Tojo')
]
itemgetter()
While the method shown above works fine, the operator module
provides an itemgetter() method that performs this same task a bit
faster. It works like this:
Demo 97: advanced-python-concepts/Demos/sorting_with_itemgetter.py
def main():
-------Lines Omitted-------
ww2_leaders.sort(key=itemgetter('dob'))
print('First born:', ww2_leaders[0]['fname'])
ww2_leaders.sort(key=itemgetter('lname', 'fname'))
print('First in Encyclopedia:', ww2_leaders[0]['fname'])
main()
import math
def distance_from_origin(a, b):
return [Link](a**2 + b**2)
But it would be nice to be able to call the function like this too:
point = (3, 4)
c = distance_from_origin(point)
However, that will cause an error because the function expects two
arguments and we’re only passing in one.
One solution would be to pass the individual elements of our point:
point = (3, 4)
c = distance_from_origin(point[0], point[1])
10 to 20 minutes.
import datetime
def str_to_date(str_date):
# Write function
pass
1. Open advanced-python-
concepts/Exercises/converting_date_string_to_datetime.py in your
editor.
2. The imported datetime module includes a date() method that can
create a date object from three passed-in parameters: year, month,
and day. For example:
[Link](1776, 7, 4)
import datetime
def str_to_date(str_date):
date_parts = [int(i) for i in str_date.split("-")]
return [Link](*date_parts)
Modules
Or you can import specific functions or classes from the module using:
from module_name import class_or_function_name
For example, if you want to use the random() function from the random
module, you can do so by importing the whole module or by importing
just the random() function:
As shown above, when you import the whole module, you must prefix
the module’s functions with the module name.
Every .py file is a module. When you build a module with the intention
of making it available to other modules for importing, it is common to
include a _test() function that runs tests when the module is run
directly. For example, if you run [Link], which is in the Lib
directory of your Python home, the output will look something like this:
2000 times random
0.003 sec, avg 0.500716, stddev 0.285239, min 0.000495333, max 0.99917
import sys
import os
python_home = [Link]([Link])
print(python_home)
Open [Link] in an editor and you will see it ends with this code:
if __name__ == '__main__':
_test()
if __name__ == '__main__':
print('This module is for importing, not for running directly.')
Packages
However, you can include code in the __init__.py file that will initialize
the package. You can also (but do not have to) set a global __all__
variable, which should contain a list of files to be imported when a file
imports your package using from package_name import *. If you do not
set the __all__ variable, then that form of import will not be allowed,
which may be just fine.
The pattern dog\b matches the first but not the second
occurence of “dog” in the phrase “The bulldog bit the dogfish.”
In the phrase “The dogfish bit the bulldog.”, it only matches the
second occurrence of “dog”, because a period is a word
boundary.
The pattern dog\B matches the second but not the first
occurence of “dog” in the phrase “The bulldog bit the dogfish.”
But in the phrase “The dogfish bit the bulldog.”, it only matches
the first occurrence of “dog”.
Number of Occurrences ( ? + * {} )
The pattern go?ad can be found in “goad” and “gad”, but not in
“gooad”. Only zero or one “o” is allowed before the “a”.
Curly braces with one parameter ( {n} ) indicate that the preceding
character should appear exactly n times in the pattern.
The pattern fo.d can be found in “food”, “foad”, “fo9d”, and “fo
d”.
The pattern fo\dd can be found in “fo1d”, “fo4d” and “fo0d”, but
not in “food” or “fodd”.
The pattern fo\Dd can be found in “good” and “gold”, but not in
“go4d”.
The pattern fo\wd can be found in “food”, “fo_d” and “fo4d”, but
not in “fo*d”.
The pattern fo\Wd can be found in “fo*d”, “fo@d” and “fo.d”, but
not in “food”.
The pattern fo\sd can be found in “fo d”, but not in “food”.
The pattern fo\Sd can be found in “fo*d”, “food” and “fo4d”, but
not in “fo d”.
Character Classes ( [] )
The pattern f[aeiou]d can be found in “fad” and “fed”, but not in
“food”, “fyd” or “fd”
[aeiou] matches an “a”, an “e”, an “i”, an “o”, or a “u”
Negation ( ^ )
When used as the first character within a character class, the caret ( ^
) is used for negation. It matches any characters not in the set.
The pattern f[^aeiou]d can be found in “fqd” and “f4d”, but not
in “fad” or “fed”.
Groups ( () )
The pattern f(oo)?d can be found in “food” and “fd”, but not in
“fod”.
Alternatives ( | )
Escape Character ( \ )
Backreferences
Within the caret (^) and dollar sign ($), which are used to specify the
beginning and end of the pattern, there are three sequences of digits,
optionally separated by a hyphen or a space. This pattern will be
matched in all of the following strings (and more):
123-45-6789
123 45 6789
123456789
123-45 6789
123 45-6789
123-456789
The last three strings are not ideal, but they do match the pattern.
Backreferences can be used to make sure that the second delimiter
matches the first delimiter. The regular expression would look like this:
^\d{3}([\- ]?)\d{2}\1\d{4}$
The \1 refers back to the first subpattern. Only the first three strings
listed above match this regular expression.
Python’s Handling of Regular Expressions
In Python, you use the re module to access the regular expression
engine. Here is a very simple illustration. Imagine you’re looking for
the pattern “r[aeiou]se” in the string “A rose is a rose is a rose.”
This will print the following, showing that the result is a match object
and that it found the match “rose” starting at index 2 and ending at
index 6:
<_sre.SRE_Match object; span=(2, 6), match='rose'>
>>> print('a\nb\nc')
a
b
c
>>> print('a\\nb\\nc')
a\nb\nc
Python provides another way of doing this. Instead of escaping all the
backslashes, you can use rawstring notation by placing the letter “r”
before the beginning of the string, like this: print(r'a\nb\nc'):
>>> print(r'a\nb\nc')
a\nb\nc
While this may not come in very handy in most areas of programming,
it is very helpful when writing regular expression patterns. That is
because the regular expression syntax also uses the backslash for
special characters. If you don’t use raw string notation, you may find
your patterns filled with backslashes.
The takeaway: Always use raw string notation for your patterns.
Groups
You can also get multiple groups at the same time returned as a tuple
of strings by passing in more than one argument (e.g., [Link](1,
2)).
When nested parentheses are used in the pattern, the outer group is
returned before the inner group. Here is an example that illustrates
that:
>>> import re
>>> p = [Link](r'(\w+)@(\w+\.(\w+))')
>>> match = [Link]('andre@[Link]')
>>> email = [Link](0)
>>> handle = [Link](1)
>>> domain = [Link](2)
>>> domain_type = [Link](3)
>>> print(email, handle, domain, domain_type, sep='\n')
andre@[Link]
andre
[Link]
com
>>> print([Link]())
('andre', '[Link]', 'com')
Flags
[Link](pattern, re.FLAG1|re.FLAG2)
The sub() method can either replace each match with a string or with
the return value of a specified function. The function receives the
match as an argument and must return a string that will replace the
matched pattern. Here is an example:
Demo 99: regular-expressions/Demos/clean_cusses.py
import re
import random
def clean_cuss(match):
# Get the whole match
cuss = [Link](0)
# Generate a random list of characters the length of cuss
chars = [[Link]('!@#$%^&*') for letter in cuss]
# Return the list joined into a string
return ''.join(chars)
def main():
pattern = r'\b[a-z]*(stupid|stinky|darn|shucks|crud|slob)[a-z]*\b'
p = [Link](pattern, [Link]|[Link])
s = """Shucks! What a cruddy day I\'ve had.
I spent the whole darn day with my slobbiest
friend darning his STINKY socks."""
result = [Link](clean_cuss, s)
print(result)
main()
Code Explanation
Word boundary:
\b[a-z]*(stupid|stinky|darn|shucks|crud|slob)[a-z]*\b
p = [Link](pattern, [Link]|[Link])
Run the file at the terminal to see that it replaces all those matches
with a random string of characters:
PS …\regular-expressions\Demos> python clean_cusses.py
@$@$#!! What a !&!#%* day I've had.
I spent the whole &*$* day with my @$@!%$^*^
friend ^&#*&#& his ^!@*#@ socks.
import re
from collections import Counter
with open('Declaration_of_Independence.txt') as f:
doi = [Link]().upper()
c = Counter(word_list)
print(c.most_common(10))
Code Explanation
20 to 30 minutes.
1. puddles
2. mommies
3. aardvarks
4. balloons
The following items cannot pass through the green glass door:
1. ponds
2. moms
3. anteaters
4. kites
Knowing that, which of the following can pass through the green glass
door?
1. bananas
2. apples
3. pears
4. grapes
5. cherries
Did you figure it out? The two that can pass are apples and cherries.
Any word with a double letter can pass through the green glass door.
Now, take a look at the following code:
Exercise Code: regular-expressions/Exercises/green_glass_door.py
def green_glass_door(word):
prev_letter = ''
for letter in word:
letter = [Link]()
if letter == prev_letter:
return True
prev_letter = letter
return False
import re
def green_glass_door(word):
pattern = [Link](r'(.)\1')
return [Link](word)
Code Explanation
The second part of the pattern uses a backreference to match the first
group: that is, the character matched by (.):
pattern = [Link](r'(.)\1')
The function then returns the result of searching the string for that
pattern:
return [Link](word)
That will either return a Match object, which evaluates to True, or it will
return None, which evaluates to False.
Conclusion
In this lesson, you have learned how to work with regular expressions
in Python. To learn more about regular expressions, see Python’s
Regular Expression HOWTO43.
LESSON 15
Working with Data
Topics Covered
They saw more rooms and made more discoveries than Mary had
made on her first pilgrimage. They found new corridors and corners
and flights of steps and new old pictures they liked and weird old
things they did not know the use of.
– The Secret Garden, Frances Hodgson Burnett
Virtual Environment
In this lesson, you will be instaling some libraries. So as not to mess
up your standard environment, you should create a virtual
environment and work within it the entire lesson:
Mac / Linux
source .venv/bin/activate
host: [Link]
user: python
password: python
database: lahmansbaseballdb
If this connection doesn’t work for you, see
[Link] to see if something has
changed.
PEP 0249 defines an API for Python interfaces that work with
databases. Generally, you follow the following steps to pull data from
a database (be sure to open a Python terminal and follow along):
3. Write your query. For example, this query will get the first and last
names, weight, and year of debut for the heaviest five people in
the people table:
query = """SELECT nameFirst, nameLast, weight, year(debut)
FROM people
ORDER BY weight DESC
LIMIT 5
"""
connection = [Link](
host='[Link]',
user='python',
passwd='python',
db='lahmansbaseballdb'
)
cursor = [Link]()
[Link](query)
results = [Link]()
[Link]()
[Link]()
print(results)
Code Explanation
This will output a list of tuples, one tuple for each record returned:
[
('Walter', 'Young', 320, 2005),
('Jumbo', 'Diaz', 315, 2014),
('CC', 'Sabathia', 300, 2001),
('Dmitri', 'Young', 295, 1996),
('Jumbo', 'Brown', 295, 1925)
]
Cursor Methods
import [Link]
connection = [Link](
host='[Link]',
user='python',
passwd='python',
db='lahmansbaseballdb'
)
cursor = [Link]()
[Link](query)
result = [Link]()
if result:
player_name = result[0] + ' ' + result[1]
birth_place = result[2] + ', ' + result[3]
birth_year = result[4]
print(f'{player_name} was born in {birth_place} in {birth_year}.')
else:
print('No records returned.')
[Link]()
[Link]()
Code Explanation
According to PEP 0249, the cursor fetch methods must return a single
sequence of values (for one row) or a sequence of sequences (for
multiple rows). By default, mysql-connector-python returns a tuple (for
fetchone()) and a list of tuples (for fetchmany() and fetchall()), but
you can change the cursor type to a dictionary by passing in
dictionary=True to the cursor() method:
cursor = [Link](dictionary=True)
For the query getting the five heaviest baseball players of all time, this
would change the result from a list of tuples to a list of dictionaries.
Give it a try:
3. Run the file. It will output something like this (though not laid out so
nicely48):
[
{
'nameFirst': 'Walter',
'nameLast': 'Young',
'weight': 320,
'year(debut)': 2005
},
{
'nameFirst': 'Jumbo',
'nameLast': 'Diaz',
'weight': 315,
'year(debut)': 2014
},
{
'nameFirst': 'CC',
'nameLast': 'Sabathia',
'weight': 300,
'year(debut)': 2001
},
{
'nameFirst': 'Dmitri',
'nameLast': 'Young',
'weight': 295,
'year(debut)': 1996
},
{
'nameFirst': 'Jumbo',
'nameLast': 'Brown',
'weight': 295,
'year(debut)': 1925
}
]
-------Lines Omitted-------
query = """SELECT nameFirst, nameLast, birthCity, birthState, birthYear
FROM people
WHERE playerID = 'jeterde01';"""
cursor = [Link](dictionary=True)
[Link](query)
result = [Link]()
if result:
player_name = result['nameFirst'] + ' ' + result['nameLast']
birth_place = result['birthCity'] + ', ' + result['birthState']
birth_year = result['birthYear']
print(f'{player_name} was born in {birth_place} in {birth_year}.')
-------Lines Omitted-------
Code Explanation
Passing Parameters
Often, your Python code won’t know some of the values in the SQL
query until execution time. For example, we could write a program that
allowed users to find out which five players had the most home runs in
a given year. The SQL query for that would look like this:
In this case, Python creates the whole query as a string and sends it
to the database to run.
There are multiple problems with the above approach, but the biggest
one is that it opens the database to hacking. A savvy and nefarious
person could try to pass in a value to year_id that ended one query
and started another that either sought to extract extra data (e.g.,
passwords) from the database or tried to wreak havoc on your
database by updating or deleting records.
1. It mitigates the security risk. The database can check the passed-
in parameters to make sure that they match the data types of the
corresponding columns. Any code that tried to end one query and
start another would get rejected.
2. It allows the database to compile the query so that it can reuse it
with different passed-in parameters without recompiling every time.
Different databases use different placeholders for parameters:
import [Link]
def main():
connection = [Link](
host='[Link]',
user='python',
passwd='python',
db='lahmansbaseballdb'
)
cursor = [Link](prepared=True)
checking = True
while checking:
year_id = int(input('Enter a year (0 to quit): '))
if year_id == 0:
break
[Link](query, [year_id])
results = [Link]()
[Link]()
[Link]()
main()
Code Explanation
Notice the “%s” parameter on line 18 and the way the parameter is
passed into [Link]() on line 28:
[Link](query, [year_id])
Run the program. You will see that it prompts the user repeatedly for a
year and then retrieves and prints out the five leading home-run hitters
for that year.
Things to notice:
SQLite50
SQLite is a server-less SQL database engine. Each SQLite database
is stored in a single file that can easily be transported between
computers. While SQLite is not as robust as enterprise relational
database management systems, it works great for local databases or
databases that don’t have large loads.
Python’s sqlite3 module conforms to PEP 0249.
import sqlite3
def main():
connection = [Link]('../data/[Link]')
connection.row_factory = [Link]
cursor = [Link]()
checking = True
while checking:
year_id = int(input('Enter a year (0 to quit): '))
if year_id == 0:
break
[Link](query, [year_id])
results = [Link]()
[Link]()
[Link]()
main()
Code Explanation
Things to notice:
10 to 15 minutes.
In this exercise, you will use your knowledge of PEP 0249 to connect
to and query the [Link] database.
1. Open working-with-data/Exercises/querying_a_sqlite_database.py
in your editor.
2. The connection to the SQLite database has already been made
and the query has been written. We have also included the
following line, which allows you to access the values by column
name:
connection.row_factory = [Link]
3. Add code that runs the query and assigns the results to the
results variable.
4. Print out the results using a for loop, so that it outputs:
Walter Young weighed 320 when he debuted in 2005.
Jumbo Diaz weighed 315 when he debuted in 2014.
CC Sabathia weighed 300 when he debuted in 2001.
Jumbo Brown weighed 295 when he debuted in 1925.
Dmitri Young weighed 295 when he debuted in 1996.
Note that the debut field is returned as a string in the format YYYY-
MM-DD. You could use the datetime module to get at the year, or
you can just use slicing.
5. Don’t forget to close your cursor and connection.
Solution: working-with-data/Solutions/querying_a_sqlite_database.py
import sqlite3
connection = [Link]('../data/[Link]')
connection.row_factory = [Link]
cursor = [Link]()
[Link](query)
results = [Link]()
[Link]()
[Link]()
import sqlite3
connection = [Link](':memory:')
cursor = [Link]()
[Link](create)
members = [
('John', 'Lennon', 'The Smart One'),
('Paul', 'McCartney', 'The Cute One'),
('George', 'Harrison', 'The Funny One'),
('Ringo', 'Starr', 'The Quiet One')
]
results = [Link]()
[Link]()
[Link]()
print(results)
[Link](insert, member)
-------Lines Omitted-------
members = [
('John', 'Lennon', 'The Smart One'),
('Paul', 'McCartney', 'The Cute One'),
('George', 'Harrison', 'The Funny One'),
('Ringo', 'Starr', 'The Quiet One')
]
20 to 30 minutes.
In this exercise, you will use the data from a text file to populate a
database table.
Open working-with-data/data/[Link] in your editor. The file has 52
lines of data (50 states and Washington, D.C. and Puerto Rico). Each
line contains three pieces of data51 separated by tabs:
1. State Name
2. Population in 2020
3. Population in 2000
For example, the line for California reads:
California 39,631,049 37,254,523
import sqlite3
connection = [Link](':memory:')
connection.row_factory = [Link]
results = [Link]()
[Link]()
[Link]()
import sqlite3
connection = [Link](':memory:')
connection.row_factory = [Link]
cursor = [Link]()
[Link](create)
data = []
with open('../data/[Link]') as f:
for line in [Link]():
state_data = [Link]('\t')
tpl_state_data = (state_data[0],
int(state_data[1].replace(',','')),
int(state_data[2].replace(',','')))
[Link](tpl_state_data)
[Link](insert, data)
[Link](select)
results = [Link]()
[Link]()
[Link]()
for record in results:
state = record['state']
pop2040 = record['pop2040']
print(f'The projected 2040 population of {state} is {pop2040:,}.')
1. Open the file using the built-in open() function with newline set to
an empty string.
2. Pass the file object to the [Link]() method.
3. Read the file row by row. Each row is a list of strings.
Demo 108: working-with-data/Demos/csv_reader.py
import csv
csv_file = '../data/[Link]'
with open(csv_file, newline='', encoding="utf-8") as csvfile:
pops = [Link](csvfile)
for row in pops:
print(', '.join(row))
Code Explanation
DictReader
import csv
csvfile = '../data/[Link]'
with open(csvfile, newline='', encoding="utf-8") as csvfile:
pops = [Link](csvfile)
print('Headers:', [Link])
for row in pops:
# Convert to integers
pop_2020 = int(row['2020'].replace(',', ''))
pop_2010 = int(row['2010'].replace(',', ''))
Code Explanation
Code Explanation
Things to notice:
fieldnames
import csv
def get_data_from_csv(csvfile):
with open(csvfile, newline='', encoding="utf-8") as csvfile:
data = [Link](csvfile)
return data
def main():
data = get_data_from_csv('../data/[Link]')
main()
What do you expect to happen? Clearly, the intention is to print all the
states in the “State” column of the CSV, but instead the code will error:
ValueError: I/O operation on closed file.
The reason it errors is that files opened using with are automatically
closed at the end of the with block. An effective way of dealing with
this issue is to convert the [Link] object to a list and return
that instead:
Demo 111: working-with-data/Demos/csv_dictreader_3.py
import csv
def get_data_as_list_from_csv(csvfile):
with open(csvfile, newline='', encoding="utf-8") as csvfile:
data = [Link](csvfile)
return list(data)
def main():
data = get_data_as_list_from_csv('../data/[Link]')
main()
20 to 30 minutes.
In this exercise, you will use your knowledge of Python lists and
dictionaries to search data in a CSV file.
import csv
def get_data_as_list_from_csv(csvfile):
with open(csvfile, newline='', encoding="utf-8") as csvfile:
data = [Link](csvfile)
return list(data)
def main():
data = get_data_as_list_from_csv('../data/[Link]')
state = input('State name: ')
year = input('Year between 2010 and 2020: ')
population = get_population(data, state, year)
if population:
print(f'{state}\'s population in {year}: {population}.')
else:
print(f'No state found matching "{state}".')
main()
Writer
1. Open the file using the built-in open() function in writing mode and
with newline set to an empty string:
with open('../csvs/[Link]', 'w', newline='') as csvfi
3. Write the file row by row with the writerow(sequence) method or all
at once with the writerows(sequence_of_sequences) method.
Note that the sequences mapping to rows may only contain strings
and numbers.
The following example shows how you could write data retrieved from
a database into a CSV file:
Demo 112: working-with-data/Demos/csv_writer.py
import [Link]
import csv
connection = [Link](
host='[Link]',
user='python',
passwd='python',
db='lahmansbaseballdb'
)
cursor = [Link]()
[Link](query)
results = [Link]()
[Link]()
[Link]()
csv_file = '../data/[Link]'
with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
writer = [Link](csvfile)
[Link](['Year', 'Weight'])
[Link](results)
DictWriter
import csv
grades = [
{
"English": 97,
"Math": 93,
"Art": 74,
"Music": 86
},
{
"English": 89,
"Math": 83,
"Art": 97,
"Music": 94
}
]
csv_file = '../data/[Link]'
with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['Math', 'Art', 'English', 'Music']
writer = [Link](csvfile, fieldnames=fieldnames)
[Link]()
[Link](grades)
Code Explanation
Notice that the order of the fields maps to the fieldnames list.
To get the order of the fields from the first dictionary in the grades list,
use:
fieldnames = grades[0].keys()
import csv
grades = [
{
"English": 88,
"Math": 88,
"Art": 88,
"Music": 88
},
{
"English": 77,
"Math": 77,
"Art": 77,
"Music": 77
}
]
csv_file = '../data/[Link]'
with open(csv_file, 'a', newline='', encoding='utf-8') as csvfile:
fieldnames = grades[0].keys()
writer = [Link](csvfile, fieldnames=fieldnames)
[Link](grades)
Code Explanation
10 to 15 minutes.
import [Link]
import csv
connection = [Link](
host='[Link]',
user='python',
passwd='python',
db='lahmansbaseballdb'
)
cursor = [Link](dictionary=True)
[Link](query)
results = [Link]()
[Link]()
[Link]()
csv_file = '../data/[Link]'
with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = results[0].keys()
writer = [Link](csvfile, fieldnames=fieldnames)
[Link]()
[Link](results)
Python has a built-in urllib module for making HTTP requests, but
the Requests53 package is far more developer-friendly. In this section,
you’ll learn how the Requests package works and how to combine it
with the Beautiful Soup54 library to parse the code.
The first thing is to install the Requests package. With your virtual
environment activated, run:
Although all HTTP request methods (e.g., post, put, head,…) can be
used, in most cases, you will use the get method using
[Link](), to which you will pass in the URL as a string like this:
Demo 115: working-with-data/Demos/using_requests.py
import requests
url = '[Link]
r = [Link](url)
content = [Link]
print(content[:125]) # print first 125 characters
Code Explanation
This will grab all the content from the web page at
[Link] and
print out the first 125 characters. The result will be something like:
<!doctype html>
<html lang="en">
<head>
<title>Webucator</title>
1. Navigate to [Link]
demos/python/[Link] in your web browser.
2. Right-click on the page and select View page source or View
source. You should see something like:
This is the code returned by the web server that the browser uses
to draw the web page. Notice that the page begins with the text
that our script output.
Custom Headers
A web server might choose to block any requests that don’t identify
the user agent. You may be able to get around this by passing in
headers that include a value for “user-agent”, like this:
<tr class="steroids-era">
<td>1</td>
<td>Mark McGwire</td>
<td>70</td>
<td>1998</td>
<td>1963-10-01</td>
</tr>
Table rows begin with an opening <tr> tag and end with a
closing </tr> tag.
Within table rows, table data cells begin with an opening <td>
tag and end with a closing </td> tag, and table header cells
begin with an opening <th> tag and end with a closing </th> tag.
Tags often have attributes for further defining the element. Attributes
usually come in name-value pairs.
Note that attributes only appear in the opening tag, like so:
<tagname att1="value" att2="value">Element content</tagname>
Some of the home-run table rows, like the one just shown, have a
class attribute with the value of “steroids-era”:
<tr class="steroids-era">
Beautiful Soup
Beautiful Soup is a Python library for extracting HTML and XML data.
It is often used to find specific content on a web page, a process
known as “scraping.”
Install Beautiful Soup in your virtual environment:
pip install beautifulsoup4
You can choose between several options55 for parsing the HTML
content. BeautifulSoup recommends lxml. Install that as well:
To get a feel for how Beautiful Soup works, start up Python at the
terminal:
There are none, because the first row contains table header
elements (<th> tags). Just as we can treat the main soup object as
a method as a shortcut for find_all(), we can treat a Tag object as
a method as well:
>>> first_row('th')
[<th>#</th>, <th>Player</th>, <th>Home runs</th>, <th>Year</th>, <th>Bi
9. You can also pass in a text argument to search for elements that
contain certain text:
>>> ruths = soup.find_all('td', text='Babe Ruth')
>>> len(ruths)
9
Other Attributes
In addition to finding elements by name and class value, you can find
elements by any of their attributes. To do so, pass in name-value pairs
when you create the soup object. For example, the following code
would get all a elements (HTML links) with a target attribute set to
_blank:
soup.find_all('a', target='_blank')
The following file prompts the user for a URL and then searches the
web page for links with a target of “_blank”:
Demo 116: working-with-data/Demos/a_blanks.py
import requests
from bs4 import BeautifulSoup
def get_content():
url = input('Enter a URL: ')
r = [Link](url)
return [Link]
def main():
content = get_content()
soup = BeautifulSoup(content, 'lxml')
found = False
for i, link in enumerate(external_links, 1):
found = True
print(f'{i}. {link}')
if not found:
print('None found.')
main()
Code Explanation
Run the file and enter the URL of your choice to see if it has any links
that target “_blank”.
At the time of this writing, [Link] had some
such links.
The following file prompts the user for a URL and then searches the
web page for img elements with no alt value:
Demo 117: working-with-data/Demos/imgs_wo_alt.py
import requests
from bs4 import BeautifulSoup
def get_content():
url = input('Enter a URL: ')
r = [Link](url)
return [Link]
def main():
content = get_content()
soup = BeautifulSoup(content, 'lxml')
found = False
for i, img in enumerate(images, 1):
found = True
print(f'{i}. {img}')
if not found:
print('None found.')
main()
Code Explanation
Run the file and enter the URL of your choice to see if it has any
images are missing the alt value.
At the time of this writing, [Link] had some such
images.
In this exercise, you will try to scrape data from the web page at:
[Link]
Working at the terminal or in a file, try to find:
3. The name of the player with the most home runs in a single
season:
>>> data_container = [Link]('tbody')
>>> first_row = data_container.find('tr')
>>> player_name = first_row.find('td').text
>>> player_name = first_row.find_all('td')[1].text
>>> player_name
'Mark McGwire'
import requests
from bs4 import BeautifulSoup
def get_content():
url = '[Link]
r = [Link](url)
return [Link]
def get_soup(content):
return BeautifulSoup(content, 'lxml')
def get_players(soup):
data_container = [Link]('tbody')
players = []
# Loop through the rows
for row in rows:
# Get int value of HRS text
hrs = int(row.find_all('td')[HRS].text)
if hrs >= 50:
player = row.find_all('td')[PLAYER].text
# Add the name of the player to players
# but only if they're not already in there
if player not in players:
[Link](player)
if hrs < 50:
return players # No need to keep looking
return players # Just in case all have 50 or more hrs
def main():
content = get_content()
soup = get_soup(content)
players = get_players(soup)
main()
XML
XML (eXtensible Markup Language) is a meta-language; that is, it is a
language in which other languages are created. In XML, data is
"marked up" with tags, similar to HTML tags. In fact, one version of
HTML, called XHTML, is an XML-based language, which means that
XHTML follows the syntax rules of XML.
XML is used to store data or information; this data might be intended
to be read by people or by machines. It can be highly structured data
such as data typically stored in databases or spreadsheets, or loosely
structured data, such as data stored in letters or manuals.
Beautiful Soup is also used to parse XML documents. lxml is the
recommended parser for parsing XML as well as HTML:
soup = BeautifulSoup(content, 'lxml')
The home-runs page we have been working with has an XML version
at:
[Link]
The XML for the page contains record elements, an abridged version
of which is shown here:
<records>
<record>
<Player>Mark McGwire</Player>
<HR>70</HR>
<Year>1998</Year>
<Birthday>1963-10-01</Birthday>
</record>
<record>
<Player>Sammy Sosa</Player>
<HR>66</HR>
<Year>1998</Year>
<Birthday>1968-11-12</Birthday>
</record>
…
</records>
Note that each player name is stored in the Player tag, but lxml will
convert all tags and attributes to lowercase letters, so we will search
for “player”. Here is the code to list all players in the XML file:
Demo 118: working-with-data/Demos/soup_xml.py
import requests
from bs4 import BeautifulSoup
url = '[Link]
r = [Link](url)
content = [Link]
players = soup.find_all('player')
for i, player in enumerate(players, 1):
print(f'{i}. {[Link]}')
JSON
JSON stands for JavaScript Object Notation. According to the official
JSON website56, JSON is:
{
"list": [{
"dt": 1581886800,
"main": {
"temp": 36.86,
"feels_like": 29.97,
"temp_min": 33.22,
"temp_max": 36.86,
"humidity": 98
},
"wind": {
"speed": 7.11,
"deg": 243
},
"dt_txt": "2020-02-16 21:00:00"
}, {
"dt": 1581897600,
"main": {
"temp": 30.65,
"feels_like": 23.94,
"temp_min": 27.93,
"temp_max": 30.65,
"humidity": 94
},
"wind": {
"speed": 4.85,
"deg": 254
},
"dt_txt": "2020-02-17 00:00:00"
}],
"city": {
"id": 5140405,
"name": "Syracuse",
"country": "US",
"population": 145170,
"timezone": -18000,
"sunrise": 1581854499,
"sunset": 1581892559
}
}
Code Explanation
1. The value for the “list” key is a list of dicts, each representing the
weather for the subsequent three-hour period.
2. The value for the “city” key is a dict containing data about the city.
After assigning the dict to a weather variable, you could get the name
of the city and the max temparature for the next period as follows:
city_name = weather['city']['name']
max_temp = weather['list'][0]['main']['temp_max']
import requests
from datetime import datetime
API_KEY = 'abca198b092b0295697beb48914a442c'
FEED = '[Link]
def main():
city = 'Syracuse'
state = 'New York'
country_code = 'us'
params = {
'q': city + ',' + state + ',' + country_code,
'units': 'imperial',
'APPID': API_KEY
}
r = [Link](FEED, params)
print([Link]) # prints the URL created using the params
weather = [Link]()
main()
Code Explanation
20 to 30 minutes.
In this exercise, you will work with the JSON data at the following
URL:
[Link]
import requests
data="[Link]
r = [Link](data)
records = [Link]()
Conclusion
In this lesson, you have learned to work with data stored in databases,
CSV files, HTML, XML, and JSON.
LESSON 16
Testing and Debugging
Topics Covered
After testing both smiles and frowns, and proving that neither mode of
treatment possessed any calculable influence, Hester was ultimately
compelled to stand aside, and permit the child to be swayed by her
own impulses.
– The Scarlet Letter, Nathaniel Hawthorne
In this lesson, you will learn to test the performance and the
functionality of your Python code.
Testing for Performance
time.perf_counter()
import time
t1 = time.perf_counter()
t2 = time.perf_counter()
print(t1, t2, t2-t1)
Code Explanation
Scientific Notation
5.999999999999062e-07 seconds is a very small amount of time.
The number is displayed in scientific notation and is the equivalent
of 0.0000005999999999999062. That’s approximately .00006
milliseconds. If you need to brush up on your scientific notation,
check out the short video at [Link]
import random
import time
start_time = time.perf_counter()
numbers = str([Link](1, 100))
for i in range(1000):
num = [Link](1, 100)
numbers += ',' + str(num)
end_time = time.perf_counter()
td1 = end_time - start_time
start_time = time.perf_counter()
numbers = [str([Link](1, 100)) for i in range(1, 1000)]
numbers = ', '.join(numbers)
end_time = time.perf_counter()
td2 = end_time - start_time
Code Explanation
import random
import time
start_time = time.perf_counter()
for j in range(1000):
numbers = str([Link](1, 100))
for i in range(1000):
num = [Link](1, 100)
numbers += ',' + str(num)
end_time = time.perf_counter()
td1 = end_time - start_time
start_time = time.perf_counter()
for j in range(1000):
numbers = [str([Link](1, 100)) for i in range(1, 1000)]
numbers = ', '.join(numbers)
end_time = time.perf_counter()
td2 = end_time - start_time
Code Explanation
1. [Link]()
2. [Link]()
[Link]()
The timeit() method can take multiple parameters, but the most
important are:
import random
from timeit import timeit
def string_nums1():
numbers = str([Link](1, 100))
for i in range(1000):
num = [Link](1, 100)
numbers += ', ' + str(num)
def string_nums2():
numbers = [str([Link](1, 100)) for i in range(1, 1000)]
numbers = ', '.join(numbers)
Code Explanation
This will show that the string_nums1() function is about 30% less
efficient than the string_nums2() function:
Namespaces
In Python, a namespace is a names-to-objects mapping.
Namespaces are closely related to scope and are used to
distinguish between identically named attributes and variables
defined in different modules. The namespace of the top-level of the
running module is globals. And the namespace of each imported
module is the name of that module. Functions defined within a
module have their own local namespaces.
Another way to do this is to place the code you want to test in strings
and then import random in the setup argument, like this:
Demo 125: testing-debugging/Demos/using_timeit2.py
str_nums1 = """
numbers = str([Link](1, 100))
for i in range(1000):
num = [Link](1, 100)
numbers += ', ' + str(num)"""
str_nums2 = """
numbers = [str([Link](1, 100)) for i in range(1, 1000)]
numbers = ', '.join(numbers)"""
import random
from timeit import timeit
str_nums1 = """
numbers = str([Link](1, 100))
for i in range(1000):
num = [Link](1, 100)
numbers += ', ' + str(num)"""
str_nums2 = """
numbers = [str([Link](1, 100)) for i in range(1, 1000)]
numbers = ', '.join(numbers)"""
All of these methods are fine and should yield similar results.
[Link]()
The repeat() method is similar to the timeit() method, but it runs the
loop multiple times and returns a list with the results of each repetition.
In addition to stmt, setup, and number, the repeat() method has a
repeat parameter, which takes the number of times to repeat the loop.
The default value of repeat is 5.59
Here is the previous example using repeat() instead of timeit():
Demo 127: testing-debugging/Demos/using_repeat.py
import random
from timeit import repeat
str_nums1 = """
numbers = str([Link](1, 100))
for i in range(1000):
num = [Link](1, 100)
numbers += ', ' + str(num)"""
str_nums2 = """
numbers = [str([Link](1, 100)) for i in range(1, 1000)]
numbers = ', '.join(numbers)"""
Code Explanation
After four iterations of looping through each approach 1,000 times, it’s
pretty clear that the second approach is faster.
You may find that using timeit() interactively is more convenient for
small snippets of code.
Let’s try the code that generates a list of 1,000 random integers in the
range of 1 to 100:
Your output will likely be slightly different from the output shown
above.
Exercise 45: Comparing Times to Execute
10 to 15 minutes.
import re
import random
def get_word():
words = ['Charlie', 'Woodstock', 'Snoopy', 'Lucy', 'Linus',
'Schroeder', 'Patty', 'Sally', 'Marcie']
return [Link](words).upper()
def green_glass_door_1():
word = get_word()
prev_letter = ''
for letter in word:
letter = [Link]()
if letter == prev_letter:
return True
prev_letter = letter
return False
def green_glass_door_2():
word = get_word()
pattern = [Link](r'(.)\1')
return [Link](word)
Solution: testing-debugging/Solutions/compare_code.py
import re
import random
from timeit import repeat
def get_word():
words = ['Charlie', 'Woodstock', 'Snoopy', 'Lucy', 'Linus',
'Schroeder', 'Patty', 'Sally', 'Marcie']
return [Link](words).upper()
def green_glass_door_1():
word = get_word()
prev_letter = ''
for letter in word:
letter = [Link]()
if letter == prev_letter:
return True
prev_letter = letter
return False
def green_glass_door_2():
word = get_word()
pattern = [Link](r'(.)\1')
return [Link](word)
Code Explanation
It appears that the regular expression version is less efficient than the
other.
Classes
In this lesson, we will be discussing classes. You will learn to create
your own classes in the Classes and Objects lesson. In that
lesson, you will write tests for the classes you create. So, we have
a catch 22: it is helpful to understand classes when learning to write
tests, but it is also helpful to understand testing when learning to
write classes. Fortunately, you only need to know a little about
classes for the rest of this lesson:
Don’t get hung up on the class syntax for now. Look to understand
the methods (functions) within the class.
Here is a class to test the three functions above along with code to
create and run the suite of tests:
Demo 129: testing-debugging/Demos/unit_test_functions.py
import unittest
from functions_error import prepend, append, insert
class TestMyMethods([Link]):
def test_prepend(self):
[Link](prepend('bar', 'foo'), 'foobar')
def test_append(self):
[Link](append('bar', 'foo'), 'barfoo')
def test_insert(self):
[Link](insert('wetor', 'buca', 2), 'webucator')
if __name__ == '__main__':
[Link]()
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
This shows that three tests ran and one failed. And it gives details on
the test that failed. Our code expected insert('wetor', 'buca', 2) to
equal “webucator”, but instead it resulted in “webucato”. That means
there is something wrong with our insert() function. Can you fix it?
You can run many test files at once from the command line with the
following command:
That will search the directory_with_tests folder for Python files with
names that begin with “test”. For example, in the testing-
debugging/Demos folder, there are two files with helper functions in
them:
1. math_functions.py
2. string_functions.py
Each of these has an associated unit test file:
1. test_math_functions.py
2. test_string_functions.py
All four files are shown here:
Demo 130: testing-debugging/Demos/math_functions.py
import math
def round_down(f):
return int(f) # doesn't work for negative numbers
def round_up(f):
return [Link](f)
Code Explanation
Note that the int() function does not round down. It strips the decimal
portion of the number, leaving just the integer. For negative numbers,
this effectively rounds up (e.g., -5.4 becomes -5). We should have
used [Link]() instead.
Demo 131: testing-debugging/Demos/string_functions.py
import random
import string
import re
Code Explanation
import unittest
from math_functions import *
class TestMathFunctions([Link]):
def test_round_down(self):
[Link](round_down(1.3), 1)
[Link](round_down(-1.3), -2)
[Link](round_down(1.7), 1)
[Link](round_down(-1.7), -2)
[Link](round_down(0), 0)
def test_round_up(self):
[Link](round_up(1.3), 2)
[Link](round_up(-1.3), -1)
[Link](round_up(1.7), 2)
[Link](round_up(-1.7), -1)
[Link](round_up(0), 0)
if __name__ == '__main__':
[Link]()
Code Explanation
Notice that this file imports all the functions from the math_functions
module.
Demo 133: testing-debugging/Demos/test_string_functions.py
import unittest
from string_functions import *
class TestStringFunctions([Link]):
def test_prepend(self):
[Link](prepend('bar', 'foo'), 'foobar')
def test_append(self):
[Link](append('bar', 'foo'), 'barfoo')
def test_insert(self):
[Link](insert('wetor', 'buca', 2), 'webucator')
if __name__ == '__main__':
[Link]()
Code Explanation
Notice that this file imports all the functions from the string_functions
module.
Or, if you want to see a list of all the tests that run, include the -v
option, like this:
python -m unittest discover Demos -v
Here are the results with the -v option included (run in Windows
PowerShell):
PS …\Python\testing-debugging> python -m unittest discover Demos -v
test_round_down (test_math_functions.TestMathFunctions) ... FAIL
test_round_up (test_math_functions.TestMathFunctions) ... ok
test_append (test_string_functions.TestStringFunctions) ... ok
test_insert (test_string_functions.TestStringFunctions) ... FAIL
test_prepend (test_string_functions.TestStringFunctions) ... ok
======================================================================
FAIL: test_round_down (test_math_functions.TestMathFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\ndunn\OneDrive\Documents\Webucator\Courseware\complete-cou
[Link](round_down(-1.3), -2)
AssertionError: -1 != -2
======================================================================
FAIL: test_insert (test_string_functions.TestStringFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\ndunn\OneDrive\Documents\Webucator\Courseware\complete-cou
[Link](insert('wetor', 'buca', 2), 'webucator')
AssertionError: 'webucato' != 'webucator'
- webucato
+ webucator
? +
----------------------------------------------------------------------
Ran 5 tests in 0.007s
FAILED (failures=2)
This shows that five tests were run and two failed.
Exercise 46: Fixing Functions
10 to 15 minutes.
In this exercise, you will correct the functions that failed the unit tests
in our demos. The same files we used in the demos are in the testing-
debugging/Exercises folder.
1. At the terminal, run the command to discover and run unit tests in
the testing-debugging/Exercises folder.
2. Open testing-debugging/Exercises/math_functions.py.
A. Fix the function that failed the unittest.
3. Open testing-debugging/Exercises/string_functions.py.
A. Fix the function that failed the unittest.
4. Run the command to discover and run unit tests again.
5. If any unit tests failed, go back and fix the associated functions.
Challenge
Try writing your own simple function and an associated unit test.
Solution: testing-debugging/Solutions/string_functions.py
import random
import string
import re
def prepend(s,c):
return c + s
def append(s,c):
return s + c
def insert(s,c,pos):
return s[0:pos] + c + s[pos:]
Solution: testing-debugging/Solutions/math_functions.py
import math
def round_down(f):
return [Link](f)
def round_up(f):
return [Link](f)
OK
You wouldn’t usually print “Setting up” and “Tearing down”. We do this
only to show that the fixture methods get called once for each test.
Assert Methods
The assertEqual() method we used in our examples is by far the most
common, but there are many other assert methods available in
TestCase classes. For full documentation, see
[Link]
Conclusion
In this lesson, you have learned to test the performance of different
pieces of code and to create unit tests to test your Python code.
LESSON 17
Classes and Objects
Topics Covered
Every object she saw, the moment she crossed the threshold,
appeared to delight her…
– Wuthering Heights, Emily Bronte
Attributes
If you can say “x is y” or “x has y,” then x is an object, and y is an
attribute of x. Some examples:
rock_he_holds.weight = 'heavy'
backyard_apple_tree.branches = [branch1, branch2, branch3, branch4]
[Link] = 'strong'
final_match.sets = [set1, set2, set3]
serena.serve1 = .572
Behaviors
If you can say “x does” then does is a behavior of x. Some examples of
behaviors:
rock_he_holds.fall('fast')
backyard_apple_tree.bear_fruit([Link](2002, 8, 23))
[Link](ball)
final_match.end([Link](11, 45))
[Link]()
Classes vs. Objects
A class is a template for an object. An object is an instance of a class.
When we say Serena Williams is a tennis player, we are saying that
Serena Williams is an object of the TennisPlayer class. There are
other tennis players who have the same attributes and behaviors as
Serena Williams, but not in the same way. For example, Serena has a
winning percentage. Her sister Venus also has a winning percentage.
So do Roger Federer and Rafael Nadal. But their winning percentages
are all different. They also all have backhands, but they don’t all have
the same backhand. Roger Federer has a one-handed backhand,
while the others all have two-handed backhands. If you needed to
express that in code, you could do it this way:
serena.two_handed_backhand = True
venus.two_handed_backhand = True
roger.two_handed_backhand = False
rafa.two_handed_backhand = True
class Player:
pass
Everything Is an Object
Before we go further with this, let’s take a look at some of the classes
and objects we have already worked with in Python. In Python,
everything is an object. Strings are objects, lists are objects, integers
are objects, functions are objects. Even classes themselves are
objects. Python includes two built-in functions that help identify the
class of an object:
>>> type('Hello')
<class 'str'>
>>> isinstance('Hello', str)
True
>>> type(1)
<class 'int'>
>>> isinstance(1, int)
True
>>> type(['a','b','c'])
<class 'list'>
>>> isinstance(['a','b','c'], list)
True
>>> type((1,2,3))
<class 'tuple'>
>>> isinstance((1,2,3), tuple)
True
>>> type({'a':1, 'b':2})
<class 'dict'>
>>> isinstance({'a':1, 'b':2}, dict)
True
>>> type(print)
<class 'builtin_function_or_method'>
There are some built-in functions that are really classes and not
functions. Examples are tuple, list, dict, and range. The following
code shows that age is of type range and range is of type type
(meaning that it is a class):
class Player:
pass
This shows:
class Die:
pass
Code Explanation
We could import this class and instantiate a new Die object like this:
import Die1
die = [Link]()
die = Die()
Double Underscores
Note that the double underscores surrounding init indicate that
this is a special method (a magic method) in Python. You should
never name your own methods in this way.
For now, let’s initialize Die objects with a single attribute: sides with a
default value of 6:
Demo 135: classes-objects/Demos/[Link]
class Die:
def __init__(self, sides=6):
[Link] = sides
Code Explanation
Look at the class definition for Die again and notice that in the
__init__() method we assign sides to [Link]. Remember that
self is the object created by the class. The value we pass in for sides
is assigned to the sides attribute of self as the following test code
demonstrates:
Demo 136: classes-objects/Demos/test_Die2.py
import unittest
from Die2 import Die
class TestDieFunctions([Link]):
def setUp(self):
self.die1 = Die()
self.die2 = Die(8)
def test_init(self):
[Link]([Link], 6)
[Link]([Link], 8)
if __name__ == '__main__':
[Link]()
Code Explanation
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
import math
class Circle:
def __init__(self, val, prop='r'):
if prop == 'r': # radius
[Link] = val
elif prop == 'd': # diameter
[Link] = val / 2
elif prop == 'c': # circumference
[Link] = val / (2 * [Link])
elif prop == 'a': # area
[Link] = (val / [Link]) ** .5
else:
raise Exception('prop must be r, d, c, or a')
[Link] = [Link] * 2
[Link] = [Link] * 2 * [Link]
[Link] = [Link] ** 2 * [Link]
Code Explanation
In this case, a Circle object is initialized with two parameters: val and
prop, neither of which is directly assigned to the object. Rather, we use
the values passed in to determine four of the object’s attributes:
1. radius
2. diameter
3. circumference
4. area
import unittest
import math
from Circle1 import Circle
class TestCircleFunctions([Link]):
def setUp(self):
self.circle_r = Circle(5, 'r')
self.circle_d = Circle(10, 'd')
self.circle_c = Circle(10 * [Link], 'c')
self.circle_a = Circle(25 * [Link], 'a')
# Test circle_r
def test_circle_radius(self):
[Link](self.circle_r.radius, 5)
[Link](self.circle_r.diameter, 10)
[Link](self.circle_r.circumference, 10 * [Link])
[Link](self.circle_r.area, 25 * [Link])
# Test circle_d
def test_circle_diameter(self):
[Link](self.circle_d.radius, 5)
[Link](self.circle_d.diameter, 10)
[Link](self.circle_d.circumference, 10 * [Link])
[Link](self.circle_d.area, 25 * [Link])
# Test circle_c
def test_circle_circumference(self):
[Link](self.circle_c.radius, 5)
[Link](self.circle_c.diameter, 10)
[Link](self.circle_c.circumference, 10 * [Link])
[Link](self.circle_c.area, 25 * [Link])
# Test circle_a
def test_circle_area(self):
[Link](self.circle_a.radius, 5)
[Link](self.circle_a.diameter, 10)
[Link](self.circle_a.circumference, 10 * [Link])
[Link](self.circle_a.area, 25 * [Link])
if __name__ == '__main__':
[Link]()
Code Explanation
Running this file should return the following, indicating that the class
calculates the attribute values correctly:
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
import math
class Circle:
def __init__(self, val, prop='r'):
if prop == 'r':
[Link] = val
elif prop == 'd':
[Link] = val / 2
elif prop == 'c':
[Link] = val / (2 * [Link])
elif prop == 'a':
[Link] = (val / [Link]) ** .5
else:
raise Exception('prop must be r, d, c, or a')
[Link] = [Link] * 2
[Link] = [Link] * 2 * [Link]
[Link] = [Link] ** 2 * [Link]
Code Explanation
import unittest
import math
from Circle2 import Circle
class TestCircleFunctions([Link]):
def setUp(self):
-------Lines Omitted-------
self.circle_resized = Circle(5, 'r')
self.circle_resized.resize_by(.5) # grow by 50%
-------Lines Omitted-------
def test_circle_resized(self):
[Link](self.circle_resized.radius, 7.5)
[Link](self.circle_resized.diameter, 15)
[Link](self.circle_resized.circumference, 15 * [Link])
[Link](self.circle_resized.area, 7.5 * 7.5 * [Link])
if __name__ == '__main__':
[Link]()
Code Explanation
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
Exercise 47: Adding a roll() Method to Die
15 to 25 minutes.
Currently, we have a Die class to create die objects with some number
of sides, but we have no way to roll the die. In this exercise, you will
add a roll() method to the Die class.
Challenge
Write code to roll the die 100,000 times and then use a Counter object
to create a list that shows how many times each side was rolled. It
should output something like this:
[(1, 16422), (2, 16596), (3, 16567), (4, 16761), (5, 16951), (6, 16703)]
Solution: classes-objects/Solutions/[Link]
import random
class Die:
def __init__(self, sides=6):
[Link] = sides
def roll(self):
roll = [Link](1, [Link])
return roll
Solution: classes-objects/Solutions/roll_die1.py
die = Die()
roll = [Link]()
print(roll)
Challenge Solution: classes-objects/Solutions/roll_die1_challenge.py
die = Die()
rolls = []
for i in range(100000):
roll = [Link]()
[Link](roll)
c = Counter(rolls)
c_sorted = sorted([Link]())
print(c_sorted)
Private Attributes
Many object-oriented programming languages have the concept of
private attributes – attributes of the instance objects that can only be
modified within the class definition. To understand the need for private
attributes, consider what would happen if we set a value for the radius
of our circle c like this:
[Link] = 25
That would change the value of radius, but would not change the
values of diameter, circumference, and area, and so, the circle’s
radius would now be out of sync with its other attributes. In languages
that allow for private attributes, you would explicitly mark those
attributes private and only allow access to them through getter and
setter methods, like this:
Demo 141: classes-objects/Demos/[Link]
import math
class Circle:
def __init__(self, val, prop='r'):
if prop == 'r':
self.set_radius(val)
elif prop == 'd':
self.set_diameter(val)
elif prop == 'c':
self.set_circumference(val)
elif prop == 'a':
self.set_area(val)
else:
raise Exception('prop must be r, d, c, or a')
def get_radius(self):
return self._radius
def get_diameter(self):
return self._diameter
def get_circumference(self):
return self._circumference
Code Explanation
Pythonic
[Link] = 25
a = [Link]
class Circle:
def __init__(self):
self._radius = None
def get_radius():
return self._radius
The method above makes use of get_ and set_ methods like many
other object-oriented languages, but then it uses the built-in
property() function to create a radius property, which allows radius to
be got and set directly using [Link].
class Circle:
def __init__(self):
self._radius = None
@property
def radius(self):
return self._radius
@[Link]
def radius(self, r):
self._radius = r
-------Lines Omitted-------
def resize_by(self, amount):
r = self._radius * (1 + amount)
self.set_radius(r)
@property
def radius(self):
return self._radius
@[Link]
def radius(self, r):
self._radius = r
self._diameter = r * 2
self._circumference = r * 2 * [Link]
self._area = r ** 2 * [Link]
@property
def diameter(self):
return self._diameter
@[Link]
def diameter(self, d):
[Link] = d / 2
@property
def circumference(self):
return self._circumference
@[Link]
def circumference(self, c):
[Link] = c / (2 * [Link])
@property
def area(self):
return self._area
@[Link]
def area(self, a):
[Link] = (a / [Link]) ** .5
class Foo:
@property
def name(self):
return [Link]
a = Foo()
print([Link])
15 to 25 minutes.
class Simulation:
def __init__(self, fnct_to_run, iterations):
self._fnct_to_run = fnct_to_run
self._iterations = iterations
self._results = []
[Link]()
def run(self):
for i in range(self._iterations):
result = self._fnct_to_run()
self._results.append(result)
@property
def mean(self):
return [Link](self._results)
@property
def median(self):
return [Link](self._results)
@property
def mode(self):
try:
return [Link](self._results)
except:
return None
import random
class Die:
def __init__(self, sides=6):
if type(sides) != int or sides < 1:
raise Exception('sides must be a positive integer.')
self._sides = sides
self._rolls = []
@property
def rolls(self):
return self._rolls
def roll(self):
roll = [Link](1, self._sides)
self._rolls.append(roll)
return roll
Code Explanation
Each time the roll() method is called the die instance will
automatically append the result of the roll to its _rolls property.
Demo 144: classes-objects/Demos/roll_die3.py
die = Die()
for i in range(100000):
roll = [Link]()
rolls = [Link]
c = Counter(rolls)
c_sorted = sorted([Link]())
print(c_sorted)
Code Explanation
rolls = [Link]
Documenting Classes
One nice thing about using classes is the documentation you get for
free. Check out the documentation for our Circle class in classes-
objects/Demos/[Link]:
>>> import Circle4
>>> help(Circle4)
Help on module Circle4:
NAME
Circle4
CLASSES
[Link]
Circle
class Circle([Link])
| Circle(val, prop='r')
|
| Methods defined here:
|
| __init__(self, val, prop='r')
| Initialize self. See help(type(self)) for accurate signature.
|
| resize_by(self, amount)
|
| -------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| area
|
| circumference
|
| diameter
|
| radius
This tells us that the Circle class has two methods: __init__() and
resize_by() and four properties: area, circumference, diameter, and
radius.
Using docstrings
class Circle:
"A circle"
def __init__(self, val, prop='r'):
"""Create a circle based on a radius, diameter,
circumference, or area
Keyword arguments:
val (float) -- the value of prop
prop (str)
-- 'r' : radius (default)
-- 'd' : diameter
-- 'c' : circumference
-- 'a' : area
"""
self._radius = None
self._diameter = None
self._circumference = None
self._area = None
if prop == 'r':
[Link] = val
elif prop == 'd':
[Link] = val
elif prop == 'c':
[Link] = val
elif prop == 'a':
[Link] = val
else:
raise Exception('prop must be r, d, c, or a')
@property
def radius(self):
"radius of the circle object"
return self._radius
@[Link]
def radius(self, r):
"""sets _radius, _diameter, _circumference, and _area of
circle object"""
self._radius = r
self._diameter = r * 2
self._circumference = r * 2 * [Link]
self._area = r ** 2 * [Link]
@property
def diameter(self):
"diameter (2 x r) of the circle object"
return self._diameter
@[Link]
def diameter(self, d):
"""uses diameter d to set radius, which then
updates all related pseudo-private attributes"""
[Link] = d / 2
@property
def circumference(self):
"circumference (PI x d) of the circle object"
return self._circumference
@[Link]
def circumference(self, c):
"""uses circumference c to set radius, which then updates
all related pseudo-private attributes"""
[Link] = c / (2 * [Link])
@property
def area(self):
"area (PI x r squared) of the circle object"
return self._area
@[Link]
def area(self, a):
"""uses area a to set radius, which then updates all
related pseudo-private attributes"""
[Link] = (a / [Link]) ** .5
def resize_by(self, amount):
"""resizes radius, which then updates all related
pseudo-private attributes
Keyword arguments:
amount (float) -- the amount by which to resize the radius
-- a negative number shrinks the radius
"""
[Link] = [Link] * (1 + amount)
NAME
Circle5
CLASSES
[Link]
Circle
class Circle([Link])
| Circle(val, prop='r')
|
| A circle
|
| Methods defined here:
|
| __init__(self, val, prop='r')
| Create a circle based on a radius, diameter,
| circumference, or area
|
| Keyword arguments:
| val (float) -- the value of prop
| prop (str)
| -- 'r' : radius (default)
| -- 'd' : diameter
| -- 'c' : circumference
| -- 'a' : area
|
| resize_by(self, amount)
| resizes radius, which then updates all related
| pseudo-private attributes
|
| Keyword arguments:
| amount (float) -- the amount by which to resize the radius
| -- a negative number shrinks the radius
|
Notice the help does not include the setter documentation. If you want
that documentation to show up in the help, you could include it in the
getter, like this:
@property
def radius(self):
"""radius of the circle object
setter: sets _radius, _diameter, _circumference,
and _area of Circle object
"""
@property
def diameter(self):
"""diameter (2 x r) of the circle object
setter: uses diameter d to set radius, which then updates
all pseudo-private attributes
"""
10 to 20 minutes.
class Die([Link])
| A die
|
| Methods defined here:
|
| __init__(self, sides=6)
| Creates a new standard die
|
| Keyword arguments:
| sides (int) -- number of die sides.
|
| roll(self)
| Returns a value between 1 and the number of die sides.
|
| --------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| rolls
| history of rolls
Solution: classes-objects/Solutions/[Link]
import random
class Die:
"A die"
def __init__(self, sides=6):
"""Creates a new standard die
Keyword arguments:
sides (int) -- number of die sides."""
if type(sides) != int or sides < 1:
raise Exception('sides must be a positive integer.')
self._sides = sides
self._rolls = []
@property
def rolls(self):
"history of rolls"
return self._rolls
def roll(self):
"Returns a value between 1 and the number of die sides."
roll = [Link](1, self._sides)
self._rolls.append(roll)
return roll
Inheritance
Often you will find a class has a lot of the functionality you need, but is
missing something. No worries. You can create your own class that
inherits all the functionality of the other class and then you can make
additions and modifications. The syntax for doing so is:
class A:
pass
class B(A):
pass
B is a subclass of A. A is a superclass of B.
class A:
def __init__(self, name):
[Link] = name
def intro(self):
print('Hello, my name is {}.'.format([Link]))
def outro(self):
print('Goodbye!')
class B(A):
def intro(self):
print('Hi, I am {}.'.format([Link]))
a = A('George')
b = B('Ringo')
[Link]()
[Link]()
[Link]()
[Link]()
Things to notice:
The built-in list class has an append() method for appending new
items to a list. It also has an insert() method for inserting new items
at a specific index. However, it has no prepend() method. If you want
to prepend an item to a list, the syntax is [Link](0, item).
Let’s create our own subclass of list that includes a prepend() method:
Demo 147: classes-objects/Demos/[Link]
class MyList(list):
"A subclass of list with additional functionality"
def prepend(self, obj):
"""prepend obj to list
Keyword arguments:
obj -- obj to prepend"""
[Link](0, obj)
Notice that mylist has the append() method, which MyList inherited
from list and it also has the new prepend() method.
15 to 25 minutes.
In this exercise, you will create a WeightedDie class that extends the
Die class.
Exercise Code: classes-objects/Exercises/[Link]
import random
from collections import Counter
from Die2 import Die
class WeightedDie(Die):
"A weighted die"
def __init__(self, weights, sides=6):
"""Creates a new weighted die
Keyword arguments:
sides (int) -- number of die sides.
weights (list) -- a list of integers holding the weights
for each die side
"""
if len(weights) != sides:
raise Exception(f'weights must be a list of length {sides}.')
super().__init__(sides)
self._weights = weights
def roll(self):
"""Returns a value between 1 and the number of die sides."""
for i in range(100000):
roll = [Link]()
c = Counter([Link])
c_sorted = sorted([Link]())
c_sorted
import random
class WeightedDie(Die):
"A weighted die"
def __init__(self, weights, sides=6):
"""Creates a new weighted die
Keyword arguments:
sides (int) -- number of die sides.
weights (list) -- a list of integers holding the weights
for each die side
"""
if len(weights) != sides:
raise Exception(f'weights must be a list of length {sides}.')
super().__init__(sides)
self._weights = weights
def roll(self):
"""Returns a value between 1 and the number of die sides."""
options = []
for i in range(self._sides):
for j in range(self._weights[i]):
[Link](i+1)
roll = [Link](options)
self._rolls.append(roll)
return roll
class A:
def __init__(self, name):
[Link] = name
def intro(self):
print('Hello, my name is {}.'.format([Link]))
def outro(self):
print('Goodbye!')
class B(A):
def intro(self):
super().intro()
print('It\'s very nice to meet you.')
a = A('George')
b = B('Ringo')
[Link]()
print('-------')
[Link]()
print('-------')
[Link]()
[Link]()
You may remember from a previous lesson that when subtracting from
a Counter, it is possible to end up with negative counts:
Subtracting with a Counter
>>> c = Counter(['green', 'blue', 'blue', 'red', 'yellow', 'green', 'blue']
>>> c # Before subtraction:
Counter({'blue': 3, 'green': 2, 'red': 1, 'yellow': 1})
>>> [Link](['red', 'yellow', 'yellow', 'purple'])
>>> c # After subtraction:
Counter({'blue': 3, 'green': 2, 'red': 0, 'yellow': -1, 'purple':
class NonNegativeCounter(Counter):
'Counter that disallows negative values'
def __setitem__(self, key, value):
value = 0 if value < 0 else value
super().__setitem__(key, value)
Code Explanation
Notice that this time the values for the yellow and purple keys are
both 0.
10 to 20 minutes.
In this exercise, you will create a WeightingDie class that extends the
WeightedDie class. A weighting die starts with equal weights on each
side, but it becomes weighted by giving more weight to rolls it has
rolled before. It does this by modifying the _weights attribute with each
roll. Here is the initial code:
Exercise Code: classes-objects/Exercises/[Link]
import random
from collections import Counter
from WeightedDie import WeightedDie
class WeightingDie(WeightedDie):
"A weighting die"
def __init__(self, sides=6):
"""Creates a die that favors sides it has previously rolled
Keyword arguments:
sides (int) -- number of die sides.
"""
self._weights = [1] * sides
super().__init__(self._weights, sides)
def roll(self):
"""Returns a value between 1 and the number of die sides."""
# COMPLETE THE CODE
Code Explanation
for i in range(10000):
roll = [Link]()
c = Counter([Link])
c_sorted = sorted([Link]())
c_sorted
import random
from WeightedDie import WeightedDie
class WeightingDie(WeightedDie):
"A weighted die"
def __init__(self, sides=6):
"""Creates a die that favors sides it has previously rolled
Keyword arguments:
sides (int) -- number of die sides.
"""
self._weights = [1] * sides
super().__init__(self._weights, sides)
def roll(self):
"""Returns a value between 1 and the number of die sides."""
result = super().roll()
self._weights[result-1] += 1
return result
Static Methods
As we’ve discussed, when you call a regular method on an instance of
a class, the instance itself is passed in as the first argument. Python
classes can also have static methods, which are created by including
@staticmethod immediately before the method. A static method can be
called directly on the class or on an instance of the class. It does not
take self as the first parameter. For instance:
class Planet:
@staticmethod
def greet():
print('hello')
[Link]()
earth = Planet()
[Link]()
class Triangle:
def __init__(self, sides):
if not self.is_triangle(sides):
raise Exception('Cannot make triangle with those sides.')
self._sides = sides
@property
def perimeter(self):
return sum(self._sides)
@property
def area(self):
p = [Link]/2
a = self._sides[0]
b = self._sides[1]
c = self._sides[2]
return ( p * (p-a) * (p-b) * (p-c) ) ** .5
@staticmethod
def is_triangle(sides):
if len(sides) != 3:
return False
[Link]()
if sides[0] + sides[1] < sides[2]:
return False
return True
Things to notice:
Things to note:
Class Attributes
class A:
foo = 1
bar = 1
def __init__(self):
[Link] = 2
a = A()
print([Link], [Link], [Link], [Link])
Output:
2 1 1 1
Notice that [Link] and [Link] return different values, but [Link] and
[Link] both return the value of the class attribute.
class A:
foo = ['a','b','c']
def __init__(self):
[Link]('d')
a = A()
[Link], [Link])
Output:
['a', 'b', 'c', 'd'] ['a', 'b', 'c', 'd']
In this case, [Link] and [Link] return the same values. In other words,
they are both pointing to the same object.
Class Methods
Class methods are different from standard methods, which receive the
instance object as their first parameter, and static methods, which do
not receive any default arguments. Class methods receive the class
itself as their first argument. To indicate that a method is a class
method, precede it with @classmethod. Just like with a static method, a
class method can be called directly on the class or on an instance of
the class like this:
class A:
foo = 1
@classmethod
def my_class_method(cls):
[Link] += 1
return [Link]
x = A.my_class_method()
a = A()
y = a.my_class_method()
print(x, y)
class Plane:
planes = []
def __init__(self):
self._in_air = False
type(self).[Link](self)
def take_off(self):
self._in_air = True
def land(self):
self._in_air = False
@classmethod
def num_planes(cls):
return len([Link])
@classmethod
def num_planes_in_air(cls):
return len([plane for plane in [Link] if plane._in_air])
Imagine that we test the Plane class with the following statements:
3 1
Things to note:
1. When we append self to [Link] (line 5), the plane instance
actually gets appended to the class attribute planes. That’s
because there is no instance attribute planes, so the instance
looks to the class for the attribute.
2. num_planes() and num_planes_in_air() are both class methods,
which is why we can call them on Plane in line 27.
3. After two planes take off and one lands, there is only one plane left
in the air.
class Jet(Plane):
def __init__(self):
[Link] = []
super().__init__()
the result is
3 1
But it’s better not to use the class name within the class definition.
Instead, you can do this:
def __init__(self):
self._in_air = False
type(self).[Link](self)
4 2
class FlyingObject:
flyingobjects = []
def __init__(self):
self._in_air = False
type(self).[Link](self)
def take_off(self):
self._in_air = True
def land(self):
self._in_air = False
@classmethod
def num_objects(cls):
return len([Link])
@classmethod
def num_objects_in_air(cls):
return len([fo for fo in [Link] if fo._in_air])
Output:
1
For the most part, this class serves our purposes, but it has one flaw;
it can be instantiated, as the output from the script reveals above.
To prevent that, we need to explicitly specify that FlyingObject is an
abstract class. The way to do that is to make any one of its methods
abstract. Doing so, will:
import abc
class FlyingObject(metaclass=[Link]):
flyingobjects = []
def __init__(self):
self._in_air = False
type(self).[Link](self)
@[Link]
def take_off(self):
self._in_air = True
@[Link]
def land(self):
self._in_air = False
@classmethod
def num_objects(cls):
return len([Link])
@classmethod
def num_objects_in_air(cls):
return len([fo for fo in [Link] if fo._in_air])
@property
def pilot_awake(self):
return True
def take_off(self):
if self.pilot_awake:
super.take_off()
self._in_air = True
But the attempt to create a Plane object fails and causes the following
error message:
TypeError: Can't instantiate abstract class Plane with abstract methods lan
@property
def pilot_awake(self):
return True
@property
def over_land(self):
return True
def take_off(self):
if self.pilot_awake:
super.take_off()
self._in_air = True
def land(self):
if self.over_land:
[Link]()
Now, we can successfully instantiate a Plane object:
class Bird(FlyingObject):
@property
def healthy_wings(self):
return True
def take_off(self):
if self.healthy_wings:
super().take_off()
self._in_air = True
def land(self):
super().land()
1 1
Understanding Decorators
Class Files Examples
Examples from this section are in classes-
objects/Demos/[Link].
def foo(f):
print(f)
def foo_inner():
pass
return foo_inner
def bar():
pass
print(bar)
bar = foo(bar)
print(bar)
1. On line 10, we print bar and see that it is a function object. The
long number, 0x00…, is the object’s unique memory address.
2. On line 11, we call foo() and pass it the bar function object. The
foo() function prints out the object (line 2). And we can see that it
prints exactly the same thing, meaning that the local variable f is
pointing to the bar() function defined on lines 7 and 8.
3. On lines 3 and 4, we define foo_inner(), which is a function local
to the foo() function, meaning it can only be called from within the
foo() function, unless foo() returns it, which it does.
4. Back on line 11, we overwrite the bar variable with whatever foo()
returns, which is the the foo_inner() function.
5. On line 12, we print bar and see that it now contains a different
function, the local foo_inner() function. However, because bar is
global, this function can now be called from anywhere.
The main takeaway from this is that functions can be passed
around just like any other object.
Now, let’s take a look at an example of how we can decorate a
function with a decorator:
from datetime import datetime
def format_report(f):
def inner(text):
print('MY REPORT')
print('-' * 50)
f(text)
print('-' * 50)
print('Report completed: {}.'.format([Link]()))
return inner
def report(text):
print(text)
report = format_report(report)
MY REPORT
--------------------------------------------------
I have created my first decorator.
--------------------------------------------------
Report completed: 2018-10-09 18:13:57.690144.
1. Look at the report() function on lines 12 and 13. All it does is print
the text that is passed in.
2. On line 15, the report function is passed to format_report(),
which defines and later returns an inner() function. This inner()
function:
A. Prints a couple of lines of text.
B. Runs the passed-in function.
C. Prints another couple of lines of text.
3. Back on line 15, we overwrite the report variable with the function
object returned by format_report().
4. Now, on line 17, when we call report(), it runs the inner()
function returned by format_report().
Can you see how format_report() is decorating (i.e., adding
functionality to) the report() function? Of course, format_report()
doesn’t do anything terribly exciting, but a decorator can do anything
you want it to do. For example, it could keep an event log or send an
email.
There is a special decorator syntax, which you have already seen
when creating properties and static, class and abstract methods.
Instead of explicitly overwriting a function variable with the function
returned by a decorator (e.g., report = format_report(report)), you
indicate that the function will be decorated like this:
@foo
bar():
pass
bar():
pass
bar = foo(bar)
Using this syntax, we can decorate the report() function like this:
def format_report(f):
def inner(text):
print('MY REPORT')
print('-' * 50)
f(text)
print('-' * 50)
print('Report completed: {}.'.format([Link]()))
return inner
@format_report
def report(text):
print(text)
In three words I can sum up everything I’ve learned about life: it goes
on.
– Robert Frost
[← 2].
[Link]
[← 3].
The generic term for the various terminals is command line shell
([Link] Visual Studio Code
will select an appropriate terminal for you. On Windows, that will
most likely be Command Prompt or PowerShell. On a Mac, it will
most likely be bash or Zsh. The names “prompt,” “command
prompt,” “shell,” and “terminal” are used interchangeably.
[← 4].
The ls command works in Windows PowerShell as well.
[← 5].
[Link]
lang=en
[← 6].
Unicode is a 16-bit character set that can handle text from most of
the world’s languages.
[← 7].
[Link]
[← 8].
Although only a convention, the name “main()” was not chosen
arbitrarily. It is used because modules refer to themselves as
“__main__”, so it seems fitting to get them started with a
corresponding “main()” function.
[← 9].
Absolute paths start from the top of the file system and work their
way downwards towards the referenced file. Relative paths start
from the current location (the location of the file containing the path)
and work their way to the referenced file from that location.
[← 10].
The lists of names come from
[Link]
[← 11].
[Link]
[← 12].
[Link]
[← 13].
[Link]
[← 14].
The min() and max() functions can also compare strings.
[← 15].
To get a full list of the math module’s methods, import math and then
type help(math) in the Python shell or visit
[Link]
[← 16].
[Link]
[← 17].
To get a full list of the random module’s methods, import random and
then type help(random) in the Python shell or visit
[Link]
[← 18].
[Link]
str
[← 19].
Note that there is no “character” type in Python. A single character is
just a string of length 1. So, when you index a string, you get
multiple one-character strings (or strings of length 1).
[← 20].
Three single quotes will work as well, but double quotes are
recommended. More information at
[Link]
[← 21].
[Link]
[← 22].
This is actually a slightly simplified version of the format
specification. For the full format specification, see
[Link]
[← 23].
If you are a mathematician or scientist, you can see all the different
available types at
[Link]
[← 24].
[Link]
[← 25].
f-strings were introduced in Python 3.6.
[← 26].
As we will see later, the len() function can also take objects other
than strings.
[← 27].
The min() and max() functions can also compare numbers.
[← 28].
In versions prior to 3.7, an arbitrary item was removed.
[← 29].
You can name this directory whatever want, but it is commonly
called “.venv” (notice the prepended dot) to indicate that it is a
special directory for holding a virtual environment.
[← 30].
[Link]
[← 31].
The
[Link]
image is used under the terms of Public Domain
([Link]
[← 32].
The time of the epoch varies across computing systems, but in
Python it is pretty much guaranteed to by January 1, 1970. For a
discussion on this, see [Link]
dev/086gxjdb5a/epoch-and-platform.
[← 33].
Documentation on the time module:
[Link]
[← 34].
[Link]
[← 35].
Documentation on date and time formatting directives is available at
[Link]
highlight=strptime#strftime-and-strptime-format-codes.
[← 36].
Documentation on the datetime module:
[Link]
[← 37].
[Link]
baby-boys/569962/
[← 38].
[Link]
[← 39].
In our class files, we use only a single blank line to separate
functions to keep files shorter for printing purposes.
[← 40].
[Link]
February/[Link]
[← 41].
[Link] contains a list of strings specifying the search path for
modules. The list is os-dependent. To see your list, import sys, and
then output [Link].
[← 42].
Groups can also be named through a Python extension to regular
expressions. For more information, see
[Link]
named-groups.
[← 43].
[Link]
[← 44].
[Link]
[← 45].
[Link]
[← 46].
[Link]
[← 47].
PEP stands for Python Enhancement Proposal.
[← 48].
Check out the pprint library at
[Link] to see how you can
pretty print data results. We have an example at working-with-
data/Demos/pretty_print.py.
[← 49].
[Link]
[← 50].
See [Link] for documentation
on Python’s sqlite3 library and [Link] for
documentation on SQLite itself.
[← 51].
The state populations are based on data from
[Link]
2020 numbers are extrapolated.
[← 52].
The population data is from
[Link]
[Link]. The 2020 numbers are extrapolated.
[← 53].
[Link]
[← 54].
[Link]
[← 55].
[Link]
the-parser-to-use
[← 56].
[Link]
[← 57].
[Link]
[← 58].
For a list of many JSON APIs, see
[Link]
data_format=21173.
[← 59].
In Python 3.6 and earlier, the default value for repeat was 3.
[← 60].
Historically, there were some differences, but those differences are
largely academic.
[← 61].
The tuple, list, dict, and range classes are exceptions because
they are usually used like functions rather than classes.
[← 62].
You could name the first parameter by a different name, but you
really shouldn’t. It would just be self-punishment.
[← 63].
Decorators are functions that add functionality to (i.e., “decorate”)
other functions.
[← 64].
[Link]
MySQL and SQLite are both popular database systems used in Python applications, but they serve different use cases. MySQL is a full-featured relational database management system known for handling large-scale applications with high query volumes, providing robust security features and supporting concurrent transactions and operations. It's suitable for enterprise-level applications that require persistent connections to a server. Conversely, SQLite is a lightweight, server-less database ideal for local, mobile, or small to medium applications where simplicity and ease of setup are more critical than high throughput. SQLite stores data in a single file, making it easy to deploy and transport. However, its performance may not match MySQL's in high-concurrency environments due to its lock mechanism and file-based nature .
Global variables can be considered bad practice because they can lead to code that is difficult to understand and maintain. They increase the risk of unintentional side-effects since any function can modify the global variable, potentially leading to bugs that are hard to trace. Additionally, global variables can make functions impure, as their output might depend on a variable that is not explicitly passed as an argument, thereby making the functions less predictable and harder to test .
Docstrings enhance the usability and maintainability of a class by providing easily accessible documentation of the class's purpose and functionality directly in the code. They should ideally contain a brief description of what the class does, how it is meant to be used, and any important attributes or methods. Including examples can also be beneficial for understanding. By using docstrings, developers can use the built-in help() function to quickly reference how to interact with the class, making it easier for others—or oneself in the future—to comprehend and work with the code .
The potential pitfall of using os.rmdir in Python is that it can only remove one directory at a time, and it requires the directory to be empty; otherwise, it will raise an error. Conversely, os.removedirs is capable of removing a directory and all empty parent directories, which can be more efficient and convenient in cleaning up directory structures. Nonetheless, os.removedirs will also raise an error if it attempts to remove non-empty directories higher up the tree, so both functions require caution to avoid unexpected errors .
Keyword arguments in Python allow for greater flexibility when calling functions with multiple parameters because they enable arguments to be passed in any order, provided their names are used. This helps in clarifying what each argument represents, especially in functions with multiple parameters or when default values are present. Additionally, they can make function calls more readable and maintainable by clarifying the role of each parameter through the use of parameter names explicitly .
os.walk is a powerful method in Python that generates file names in a directory tree by walking either top-down or bottom-up. It returns a generator that yields a tuple for each directory it visits, containing the directory path, a list of directories, and a list of files. This is particularly useful in file processing as it allows for the automatic traversal of directory trees and enables efficient and recursive operations on files and directories without manually tracking each directory and file .
In Python, parameters are effectively passed by assignment, which is sometimes misunderstood as neither strictly by value nor by reference. This means that immutable objects (such as integers, strings, and tuples) behave as though they are passed by value, while mutable objects (such as lists, dictionaries, and sets) behave as though they are passed by reference. This can affect function behavior significantly: changes made to mutable objects within a function can have persistent effects outside the function, whereas changes to immutable objects are confined within the function scope unless explicitly returned. This dual behavior necessitates clear understanding to avoid unintended side effects in the program .
Moving the program logic out of the main() function into separate functions helps in organizing the code better and makes it more modular. The main() function typically handles the flow of the program, while other functions perform the actual work. This separation can make the program easier to read, maintain, and reuse across different projects as the main() function acts more like a high-level controller, keeping the business logic and operations encapsulated within their respective functions .
Lambda functions are used in Python for creating small, anonymous functions on-the-fly. They are limited to a single expression and are generally used for simple operations where a full function definition would be overkill. Unlike regular functions, lambdas are syntactically restricted to a single expression and are less readable due to the lack of a name and documentation. However, they can be more concise and functional in scenarios where a simple operation is repeatedly needed, such as with higher-order functions like map() and filter().
Decorators in Python are a design pattern used to alter or enhance the functionality of functions or methods without modifying their internal logic. They are implemented as callable objects (usually functions) that take another function as a parameter and return a new function that usually adds some pre- or post-processing. Decorators are widely used in Python for logging, access control, caching, and memoization. They are applied by using the @decorator_name syntax above the function definition. This aspect of decorators makes them a powerful tool for code reuse and modular design [Not directly covered in provided sources, general Python knowledge].