Recap 1


After writing a few posts, I started to feel lost: I had taken a loose approach to tracking the topics I covered. Did I post about dispatch techniques in my second or third post? Maybe in the first?
I accepted my fate and got to listing them one by one on a piece of paper. As I was writing, I figured this was the beginning of a recap post.
Looking back, you've made a lot of progress. You've accumulated some valuable programming mileage. Give yourself a pat on the back cause you can be proud of yourself 🎉🥳
This post will recap all the topics we've covered so far. If you haven't read them, here are the articles this post will cover:
Part 1: We wrote a program that asks for user input and create a personalized greeting.
Part 2: A restaurant bill calculator
Part 3: A grocery list program
Part 4: A simple poker card game
Part 5: A barbershop appointment booking system
Variables and data types
What's a variable? What's a constant?
Variables are names referring to values in memory. You assign a value to a variable with =
. To change a variable's value, you reassign it with =
.
>>> x = 10
>>> x
10
>>> x = 12
>>> x
12
Constants are variables whose values don't change. We recognize them by their all-caps names. This is a convention between programmers, and Python doesn't enforce it.
PLANETS = [
'Mercury',
'Venus',
'Earth',
'Mars',
'Jupiter',
'Saturn',
'Uranus',
'Neptune'
]
Try to give meaningful names to your variables. student_age
is better than st_a
, or a
. Remember that you might return to your code in a few months. By then, you'll be happy you spent a few moments creating descriptive names!
Let's review the various data that can go in a variable.
Integers and floating-point numbers
Integers are whole numbers. They can be positive or negative. Python allows integers of arbitrary size. You can go as big as your computer memory will allow!
Floats, in contrast, represent numbers with decimal points. They work like the scientific notation you learned in high school.
You can't represent all possible numbers as floats: there are max and min values.
The biggest positive float is 1.7976931348623157e+308
. The positive float closest to zero that has good precision is 2.2250738585072014e-308
.
You can go even closer to zero but will lose some precision.
There's little chance you'll need to care about these values, which can even vary from system to system. Use sys.float_info
if you need to check them. Here's what it looks like on my computer.
>>> import sys
>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024,
max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021,
min_10_exp=-307, dig=15, mant_dig=53,
epsilon=2.220446049250313e-16, radix=2, rounds=1)
While integers are exact values, floating points are subject to precision errors. Keep this in mind because it can cause annoying bugs.
>>> 0.1 + 0.2
0.30000000000000004
Python converts your numbers from ints to floats as needed. Use the int
and float
functions if you need to be explicit. Those two functions also work to convert strings to numbers.
Booleans
Booleans are the values True
and False
.
You convert values to a boolean by using the bool
function. It's intuitive: 0
, None
, ""
, []
, {}
, or ()
become False
. Nonzero numbers, nonempty lists, and strings become True
.
>>> bool(())
False
>>> bool((1))
True
>>> bool(2)
True
>>> bool(None)
False
Python also converts to a boolean when needed: 2 and True
is True
.
Strings
A string is a sequence of characters. We delimit strings with either single ('
), double("
), or triple ("""
) quotes.
my_string = "This is a string."
other_string = 'Don\'t forget to escape special characters!'
multiline_string = """ This is a multiline
string."""
Strings support indexing and slicing.
>>> s = "Hello."
>>> s[1] # indexing
'e'
>>> s[1:3] # slicing
'el'
Slicing can take up to three arguments inside brackets, separated by a colon <:>. The first is the slice's start, the second is the end, and the third indicates whether you go in order or reverse.
>>> s = "Hello."
>>> s[1:3:1]
'el'
>>> s[3:1:-1]
'll'
>>> s[:3]
'Hel'
Try some combinations until you get a good grasp of slicing strings.
Slicing is how you reverse a string, too.
>>> s[::-1]
'.olleH'
We saw some methods on strings:
strip:
my_string.strip()
removes leading and trailing whitespace. e.g.," a ".strip()
returns a new string"a"
.isdigit checks if the string contains only numerical characters. e.g.,
"123".isdigit()
isTrue
, while"abc1".isdigit()
isFalse
.lower converts a string to lowercase characters. e.g.,
"Aa1Bb".lower()
returns a new string"aa1bb"
.capitalize capitalizes the first letter of a string. The other characters are in lowercase. e.g.,
"i love Python".capitalize()
returns the new string"I love python"
. Note that if there was already a capital letter after the first character, it will be in lowercase.title capitalizes the first letter of each word and turns the other letters into lowercase. e.g.,
"i lOve Python".title()
returns the new string"I Love Python"
.rsplit(separator, maxsplits) splits the string into a list. It starts from the right and uses the
separator
to choose where to split. The number of resulting parts is one more thanmaxsplit
. If you split once, you'll have two parts; if you split twice, you'll get three. If you don't specify a maxsplit, there's no limit on the number of splits. When you don't specify a separator, any whitespace will be a separator. e.g.,"This will return a list of all the words".rsplit()
returns['This', 'will', 'return', 'a', 'list', 'of', 'all', 'the', 'words']
.
Basic operations
Arithmetic operations
Arithmetic operations are +
, -
, /
, %
(modulo), and **
(power). When operands (i.e., the numbers) are of a different type (e.g., a float and an int), Python will handle the conversions.
Logical operations
Python provides logical operators: and
, not
, or
, xor
.
The result is either True
or False
. Look at the truth tables below.
AND Truth Table
---------------
A | B | A AND B
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
OR Truth Table
--------------
A | B | A OR B
---+---+-------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 1
XOR Truth Table
---------------
A | B | A XOR B
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
NOT Truth Table
---------------
A | NOT A
---+------
0 | 1
1 | 0
Comparison operations
Here are the ways to compare values in Python:
==
: equal to!=
: not equal to>
: greater than<
: smaller than<=
: smaller or equal>=
: greater or equal
The operators work on numbers. Be careful when comparing floats, as precision errors are always possible.
But you can compare other data types.
Like strings:
>>> "hello" == "hello"
True
>>> "Hello" == "hello" # case sensitive
False
Booleans: True
is 1 and False
is 0.
>>> True == 1
True
>>> False == 0
True
>>> True == 'a' # True == 1 != 'a'
False
>>> True < 2 # pretty useless but funny
True
Lists and tuples: the items' values AND ordering must be the same for equality.
>>> [1, 2, 3] == [1, 2, 3]
True
>>> (1, 2, 3) == (3, 2, 1) # not the same order
False
>>> [1, 2, 3] == (1, 2, 3) # a list and a tuple
False
Sets and dictionaries: only the elements' values matter for equality, not their ordering.
>>> {"a": 1, "b": 2} == {"a": 1, "b": 2}
True
>>> {1, 2, 3} == {3, 1, 2}
True
You can even compare the None type.
>>> None == None
True
>>> None == 0
False
Membership testing
Use the in
and not in
keywords to test if an item is in a sequence, be it a list, dictionary, set, or tuple.
>>> 2 in [1, 2, 3]
True
>>> 1 in (1, 2, 3)
True
>>> "a" in {"a'": 1, "b": 2}
True
>>> 1 in {1, 2, 3}
True
>>> 1.0 in (1, 2, 3) # type conversion
True
Here's a common idiom. (let's say we have a program dealing with birds)
<some code>
if bird not in birds: # let's say birds is a dictionary
birds[bird] = some_value
<some other code>
Control flow
Programs need to be more than linear. They need to make decisions, which is where control flow comes in.
Conditional statements
The most basic way to control the flow of the program is with an if
statement.
Here's a general template.
if <condition 1>:
<code 1>
elif <condition 2>:
<code 2>
... More elif branches can go here ...
else:
<catch-all code>
Read it as "if <condition 1> is True, do <code 1>. If <condition 1> is False, but <condition 2> is True, do <code 2>. If no condition is ever met, do the <catch-all code>."
To understand elif
, consider the difference between these two pieces of code. Before typing them in a Python environment, try to figure out what each does.
Here's elif in action.
x = 1
y = 2
if x == 1:
print("x is 1")
elif y == 2:
print("y is 2")
And here's the same code but with if
instead of elif
.
if x == 1:
print("x is 1")
if y == 2:
print("y is 2")
The conditions in if
and elif
statements need to evaluate to either a True
or False
value. This means:
Simple predicates with comparison operators. e.g.,
x == 2
,y < 3
, etc.More complex predicates gluing simple predicates with logical operators. e.g.,
x == 2 and y < 3
.Membership testing, i.e., testing if an item is an element of a sequence. e.g., if
fruits
is a list of fruit name strings, you can check if the list contains the string"banana"
withif "banana" in fruits
."Truthy" and "Falsy" values. Remember we said that
0
,""
,[]
,{}
,()
, andNone
evaluate to False. Those are "Falsy" values. All values that eval to True are "Truthy" values. This means if you have a variablemy_var
, you can use it as a predicate in an if statement:if <my_var>:
for loops
Here’s the general form of a for
loop.
for <element> in <collection>:
<code involving element>
You can translate this to "for each element in the collection, execute some code". In fact, outside of Python, the programming jargon for this type of loop is a "for-each" loop rather than a for loop.
For
loops iterate over sequences and ranges. This is what we call definite iteration: we know the number of repetitions in advance. If we iterate over a 3-element list, we know for sure we'll have three iterations.
Here's a basic for loop iterating over a list.
>>> fruits = ["banana", "strawberry", "cherry", "apple", "pear"]
>>> for fruit in fruits:
... print(fruit.title())
Banana
Strawberry
Cherry
Apple
Pear
If you want to iterate over natural numbers, use the range
function. The range(start, stop, step)
generates all numbers between start
(included) and stop
(not included), with a step
increment.
>>> for i in range(1, 10):
... print(i, end=" ")
1 2 3 4 5 6 7 8 9
You can also iterate over each character of a string.
>>> all_caps = "LET\'S TURN THIS TO LOWERCASE"
>>> for char in all_caps:
... print(char.lower(), end="")
let's turn this to lowercase
The typical way to iterate over a dictionary is to extract key-value pairs with the items
method.
>>> bob = {
... 'name': 'Bob',
... 'age': 30,
... 'job': 'Pilot',
... }
>>> for key, value in bob.items():
... print(f"{key.title()}:\t{value}")
Name: Bob
Age: 30
Job: Pilot
Sometimes, you create 2-D structures. A common way to traverse them is with nested for
loops.
>>> for i in range(4):
... for j in range(5):
... print(f"i:{i} j={j}", end="\t|\t")
... print("\n")
i:0 j=0 | i:0 j=1 | i:0 j=2 | i:0 j=3 | i:0 j=4 |
i:1 j=0 | i:1 j=1 | i:1 j=2 | i:1 j=3 | i:1 j=4 |
i:2 j=0 | i:2 j=1 | i:2 j=2 | i:2 j=3 | i:2 j=4 |
i:3 j=0 | i:3 j=1 | i:3 j=2 | i:3 j=3 | i:3 j=4 |
while loops
While
loops repeat a block of code as long as a condition is True. This is indefinite iteration: the while statement has no idea how many times it will loop.
Here is a basic while loop
>>> x = 0
>>> while x < 4:
... print(x)
... x = x + 1
0
1
2
3
Can you guess the output if you switched print(x)
and x = x + 1
?
One practical application of while loops is infinite loops. For example, you might want to query users for input until they type "quit". A break
statement can stop the loop.
while True:
response = input("Type "quit" to stop: ")
if response == "quit:
break
for vs while
Use for when… | Use while when… |
You know how many iterations you need | You don’t know the number of iterations |
You iterate ofver a sequence (list, dictionary, string) | You’re waiting for a condition to change |
You’re using range() | You're waiting for user input or checking a state. |
Loop control with break, continue and else
To exit a loop, use a break
statement.
For example, in a for
loop.
>>> for num in range(1, 6):
... if num == 3:
... break
... print(num)
1
2
And here's a while
loop that does the same thing.
>>> x = 0
>>> while x < 5:
... x += 1
... if x == 3:
... break
... print(x)
1
2
If you don't want to exit the loop but rather skip some iterations, use the continue
statement.
The following code prints all numbers not divisible by three and smaller than 11. It uses a continue
statement to skip multiples of 3 (and 3 itself).
>>> for i in range(1, 11):
... if i % 3 == 0:
... continue
... print(i)
1
2
4
5
7
8
10
Try to write the corresponding while
loop, then check your solution against mine.
>>> x = 0
>>> while x < 10:
... x += 1
... if x % 3 == 0:
... continue
... print(x)
1
2
4
5
7
8
10
If you decide to execute some code after the loop, add an else
clause.
>>> for num in range(1, 4):
... print(num)
... else:
... print("Loop finished!")
1
2
3
Loop finished!
Let's do the same with a while loop.
>>> x = 0
>>> while x < 3:
... print(x)
... x += 1
... else:
... print("Loop completed!")
If the program execution exits the loop with a break
statement, the else
block won't execute.
>>> for i in range(1, 10):
... if i % 3 == 0:
... break
... print(i)
... else:
... print("won't print")
1
2
Functions
Functions are pieces of code that you can call from the rest of your program. Use them to break down problems into manageable parts and to avoid repetitive code.
Defining and calling functions
Define functions with def
, then the function's name, then parentheses with the arguments. Don't forget to end the line with a colon (:
).
You then add the function's body, indented right from the definition. A function's body is the instructions that do the actual work.
def function_name(arguments):
body
You call a function by its name, with its arguments (if any) in parentheses.
Function arguments
There are four kinds of arguments in Python
positional arguments
arbitrary arguments (
*args
)keyword arguments
keyword arbitrary arguments (
**kwargs
)
Positional arguments are your plain old regular arguments. You must pass them in the order in which they appear in the function definition.
>>> def my_function(a, b):
... print(f"a is {a}")
... print(f"b is {b}")
>>> my_function(2, 3)
a is 2
b is 3
You must provide as many arguments in your function call as there are parameters in your function.
Arbitrary arguments let you pass a variable number of arguments to a function.
>>> def sum_all(*numbers):
... return sum(numbers)
>>> print(sum_all(1, 2, 3))
6
In sum_all
's body, numbers
is a tuple.
Keyword arguments let you pass arguments using their names.
Imagine we were in the business of launching rockets off Cape Canaveral. We could create a function to launch rockets, taking some default parameters.
def rocket_launch(latitude=28.396837, longitude=-80.605659, payload=0, stages=3):
<launch sequence>
We can launch without explicit arguments if we're OK with the default params. rocket_launch()
We can also change the parameters to send a 2-stage rocket. rocket_launch(stages=2)
Sometimes, you don't know in advance the exact arguments you'll need AND also want keywords. That's where keyword arbitrary arguments (**kwargs
) come in. Here's an example.
def show_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
show_info(name="Alice", age=30, job="Engineer")
In the function's body, kwargs
is a dictionary: the keywords are the keys, and the arguments are the values.
Return values and None
When a function finishes executing, it returns control of the program to its caller. From the caller’s point of view, the evaluated function is its return value. Let's look at an example:
c = 0
def callee(a, b):
print("the caller only sees the return value")
c = a + b # caller doesn't care about this line
return a * b # caller only cares about the return value
def caller(a, b):
print(callee(a, b)) # callee(a, b) is the same as a * b
In our somewhat contrived example, caller
calls callee
. Despite callee
having a print
statement and an assignment, caller
doesn't 'see' them. It can only use the return
statement. This doesn't mean the body doesn't do interesting work (like callee
's print statement).
The default return statement is None
.
Python functions can pack several values together and return them as a tuple. return 3, 4
returns the tuple (3, 4)
.
Anonymous functions and lambdas
Sometimes, you want a throwaway function: its name is unimportant, and you'll use it only once. An example we saw was <sort>. It takes several arguments, among which is a "sorting function", often written as a lambda
.
The syntax for defining anonymous functions is
lambda arguments: expression
For example:
>>> (lambda x, y: x + y)(1, 3) # note the parentheses
4
Here is another example using arbitrary arguments:
>>> (lambda *numbers: sum(numbers))(1, 2, 3, 4)
10
Here's how you use lambdas with the sort method.
>>> words = ['we', 'will', 'sort', 'by', 'word', 'length']
>>> words.sort(key=lambda word: len(word))
>>> words
['we', 'by', 'will', 'sort', 'word', 'length']
Now let's sort by word length modulo three.
>>> words.sort(key=lambda word: len(word) % 3)
>>> words
['length', 'will', 'sort', 'word', 'we', 'by']
I/O: input & output
Programs communicate with the world through input and output. Up to now, we've been using simple textual I/O.
input()
The input
function waits for user input during program execution. It returns the user input as a string, which you can store as a variable. Provide a nice prompt as an argument when calling input
.
name = input("Enter your name: ")
age = input(f"How old are you, {name}? ")
input
doesn't do much to your input: it stores it as is in a string. If a string is not the best type for your data (e.g., age should be an int, not a string), you must convert it further.
Cleaning input
One of the first things you can do to user input is to strip leading and trailing whitespace with strip
.
username = input("Enter your name: ").strip()
String methods I find useful to change the case of your input's characters are:
title
: words start with a capital letter, and the other letters are lowercase. "This Is In Title Case."capitalize
: the first character in the string is in uppercase, and the rest is in lowercase. "Only the first letter becomes a capital letter.lower
: all the characters are in lowercase. "this is all in lowercase".upper
: all the characters are in uppercase. "LOOKS LIKE I\'M YELLING!"
Validating & converting input
I showed you a simple (and error-prone) way of validating and converting input.
You get input.
Check it to prevent errors.
Then, use the input in your code once you know it's valid.
Validating before using inputs is the LBYL approach (Look Before You Leap).
This is error-prone because you have to consider everything that could go wrong and defend against it. In a later post, I'll show you an alternative way of handling errors.
The first step is to check if the user has entered an input, not an empty string.
user = input("Enter your name: ").strip()
while not user:
print("You didn't enter anything.")
user = input("Enter your name: ").strip()
After ensuring the user has entered something, you'll want to make sure it's the right type. Only then can you convert the input to the desired type.
age = input("Enter your age: ").strip()
if age.isdecimal():
age = int(age)
else:
print("Invalid input. Please enter a number: ")
We could combine the two examples above.
user_age = get_age()
while not user_age:
while not age.isdecimal():
user_age = get_age()
user_age = int(user_age)
We've used isdecimal
to check if we could convert a string to a number. There are other string testing methods.
str.isalnum
: returnsTrue
if the string is not empty and holds only alphanumeric chars.str.isalpha
: returnsTrue
if the string is not empty and holds only alphabetic chars.str.isascii
: returnsTrue
if the string is empty (!!) or holds only ASCII characters. Read more on ASCII characters here.str.isdecimal
: returnsTrue
if the string is not empty and holds only numeric chars in base 10 (i.e., regular numbers).str.isdigit
andstr.isnumeric
: likestr.isdecimal
but accept a broader set of characters, like ². <"²".isdigit()> and <"²".isnumeric()> both returnTrue
, but <"²".isdecimal()> isFalse
.str.islower
,str.istitle
,str.isupper
: returnTrue
if the string is not empty and also tests for case.
print & pprint
We've covered input; now, let's move on to output. Our programs have used simple textual output, for the sake of simplicity.
In its simplest form, the print
function takes a variable number of objects and prints them.
>>> print("a")
a
>>> print(1, 2, "3")
1 2 3
You control print
's behavior with keyword arguments. Among those, sep
and end
control how the output looks like. sep
is a string separating your inputs: print(1, 2, 3, sep=", ")
is 1, 2, 3
. end
is what comes at the end of your output.
>>> print(1, 2, end=" -- this is the end of the output --")
1 2 -- this is the end of the output -->>>
The default value for sep
is a single space; for end
, it's a newline. You can learn more about print here.
Printing out your function outputs or data structures is often helpful as we program. This ensures the code does what it's supposed to. When the data structures get too big or too complex, you want something more practical than print
.
Use pretty-printing, part of the print module, to format your strings with proper indentation, line breaks, and spacing.
from pprint import pprint
data = {
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding", "hiking"],
"address": {
"city": "Paris",
"country": "France"
}
}
pprint(data)
Lists
Lists are collections. They hold items in a specific order. This means you can access elements by their position or index, like in strings.
Lists can hold items of any type. A list inside a list is a nested list.
Creating lists
The simplest way to make a list is with square brackets. my_list = []
creates an empty string and assigns it to the variable my_list
. You can also define non-empty lists: fruit_list = ['apples', 'bananas', 'strawberries']
. Note the commas separating each element.
Another way to create a list is with the list
function. You hand it a sequence to turn it into a list.
>>> list('abc')
['a', 'b', 'c']
>>> list(('a', 'b', 'c'))
['a', 'b', 'c']
The last way to create a list is with a list comprehension, which we'll see at the end of this section.
Accessing list elements
Since lists are ordered sequences, we can access their elements by their index.
>>> cars = ["Ford", "Ferrari", "Hyundai"]
>>> cars[0]
"Ford"
>>> cars[1]
'Ferrari"
>>> cars[-1]
"Hyundai"
>>> cars[-2]
"Ferrari"
Notice how we can index from beginning to end (0, 1, 2, 3, etc) and from end to beginning (-1, -2, -3, etc).
You'll get an error if you try to use an item that is out of range.
Modifying lists
Since lists are mutable sequences, you can change, add, and remove elements.
You add an element using the append method or inserting it at a specific index.
>>> fruits = ["apple", "banana", "cherry"]
# Append to end
>>> fruits.append("orange")
['apple', 'mango', 'cherry', 'orange']
# Insert at specific index
>>> fruits.insert(1, "grape")
['apple', 'grape', 'mango', 'cherry', 'orange']
To change an item, access it by index.
fruits[1] = "strawberry"
To remove an element, either use the remove
method, the del
keyword, or the pop
method.
Use list.remove
when you want to remove an item by specifying its value.
fruits.remove("mango")
The del
keyword lets you remove an item by index.
del fruits[2]
The pop
method removes and returns the last element of a list.
last_fruit = fruits.pop()
Looping over lists
The most common way to loop over a list is with a for loop. It's common practice to name the items after the list, like in the example below.
for fruit in fruits:
print(fruit)
This makes for clear and readable code.
If you want to use both the list's elements and their index, use the enumerate
function.
>>> fruits = ["apple", "banana", "cherry"]
>>> for index, fruit in enumerate(fruits):
... print(index, fruit)
0 apple
1 banana
2 cherry
List slicing
Like strings, lists are subject to slicing.
>>> my_list = ['this', 'is', 'a', ['nested', 'list']]
>>> my_list[1:2]
['is']
>>> my_list[::-1]
[['nested', 'list'], 'a', 'is', 'this']
List comprehensions
List comprehensions let you create complex lists with a one-liner.
The basic syntax is
new_list = [expression for item in iterable if condition]
Let's unpack this definition. To understand a list comprehension, you have to start with the iterable
(can be a list, a range of numbers, etc). This tells you where the data comes from.
Next, for item in iterable
means you go through each element from the data source.
Then, we can either
filter those elements with
if condition
do something from the elements with
expression
Let's look through some examples. They'll build upon each other.
We generate the list of the first 10 (non-zero) numbers.
nums = [x for x in range(1, 11)]
Here, x
is the expression, x in range(1, 11)
tells you the data source. Now we can use a more interesting expression, like squares.
squares = [x**2 for x in range(1, 11)]
Last, we can filter the numbers from our source so that we only consider odd numbers.
squares_odd = [x**2 for x in range(1, 11) if x % 2 == 1]
Let's see how much code we would have written without list comprehensions.
squares_odd = []
for i in range(1, 11):
if i % 2 == 1:
squares_odd.append(i**2)
We used a range to generate integers for our list, but we could have used a list.
names = ["joe", "sam", "tom", "bob"]
cap_names = [name.title() for name in names]
Dictionaries
Dictionaries are collections where keys pair with values. The keys are not ordered; rather, their order is an implementation detail, and you shouldn't count on it.
Creating dictionaries
You create a dictionary in very much the same way you would create a list:
By creating an empty dictionary
{}
, like you would do with an empty list[]
By creating the dictionary with some key-value pairs separated by commas.
With a function
dict
, just like you would create a list with the functionlist
.With a dictionary comprehension
# With an empty dict
empty_dict = {}
# With key-value pairs
capitals = {
"France": "Paris",
"USA": "Washington, D.C.",
"Italy": "Rome",
}
As you can see above, we separate key-value pairs with commas and include a colon between each key and its value.
# With dict()
scandinavian_capitals = dict(
Sweden="Stockholm",
Norway="Oslo",
Finland="Helsinki",
Denmark="Copenhagen"
)
dict
takes a variable number of keyword arguments. Each keyword become a key, while the arguments will become the values. Note that keywords cannot be strings. In the example above, Sweden, Norway, Finland, and Denmark don't have quotes around them.
# With a comprehension
>>> squares_of_evens = {n: n**2 for n in range(1, 11) if n%2==0}
>>> squares_of_evens
{2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
Values can be of any data type, and you can make nested dictionaries.
>>> built_ins_dict = {
... 'input': {
... 'name': 'input',
... 'type': 'function',
... 'number of args': 1
... },
... 'float': {
... 'name': 'float',
... 'type': 'function',
... 'number of args': 1
... },
... True: {
... 'name': 'True',
... 'type': 'boolean constant',
... },
... }
Accessing dictionary values by key
There are two ways to get a value when you know its key:
by encasing the key in square brackets
by using the <get> method.
Given the capitals
dictionary we defined as an example a few paragraphs back:
>>> capitals["USA"] # Square brackets method
'Washington, D.C.'
>>> capitals["China"] # There's no China key in the dict
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'China'
The square brackets method produces an error whenever we use a key it doesn't recognize. The get
method is more lenient and won't crash your program.
>>> capitals.get("China")
>>> capitals.get("France")
'Paris'
Modifying dictionaries
There's one way to both add and change a value.
capitals["China"] = "Berlin" # add a (wrong) value
capitals["China"] = "Beijing" # change the value
If you want to delete a dictionary entry, you have two options: you can use the del
keyword or pop
a value.
# del keyword
>>> del capitals["China"]
# pop
>>> cn_capital = capitals.pop("China")
>>> cn_capital
'Beijing'
Iterating over keys and values
Since dictionary entries are key-value pairs, we can loop over:
keys
values
key-value pairs
The most basic way to loop is by key.
for key in dict:
do_something(key)
For example:
>>> for country in capitals:
>>> print(country)
France
USA
Italy
Use the values
method to loop through values.
for value in dict.values():
do_something(value)
For example:
>>> for capital in capitals.values():
>>> print(capital)
Paris
Washington, D.C.
Rome
The most useful looping idiom is to loop through key-value pairs.
for key, value in dict.items():
do_something(key, value)
For example:
>>> for country, capital in capitals.items():
... print(f"country: {country}\t\tcapital: {capital}")
country: France capital: Paris
country: USA capital: Washington, D.C.
country: Italy capital: Rome
Dictionary comprehensions
Dictionary comprehensions work the same as list comprehension (and all comprehensions, really).
new_dict = {key_expression: value_expression for item in iterable if condition}
In this definition, key_expression
defines the keys, and value_expression
defines the values. Note that if iterable
is a dictionary, you can replace for item in iterable
with for key, value in iterable.items()
.
Here's how you would create a dictionary of the squares of even integers ranging from 1 to 10.
squares_even = {n: n**2 for n in range(1, 11) if n%2==0}
Here's our capitals dict, but now everything's in lowercase:
caps = {country.lower(): capital.lower() for country, capital in capitals.items()}
Sets
Properties of sets
Sets are collections containing unique elements (i.e., no duplicates). They are suitable for membership testing and set operations like union, intersection, etc.
Create sets
Sets, like dictionaries, have curly braces. If you type my_set = {}
to create an empty set, you'll get a dictionary instead. That's where the set constructor set()
comes in.
empty_set = set()
If you want to create a set with some elements, you can use the set function or curly braces. Both examples below are valid set definitions.
>>> fruits = set(['apples', 'pears', 'kiwis'])
{'kiwis', 'apples', 'pears'}
>>> animals = {'bears', 'cats', 'kiwis'}
{'kiwis', 'cats', 'bears'}
Note that set
takes one collection as an argument. You can think of the set
function as a converter between collections and sets:
# set with a dict argument
>>> set({'a': 1, 'b': 2, 'c': 3})
{'c', 'a', 'b'}
# set with a list argument
>>> set(['a', 'b', 'c'])
{'c', 'a', 'b'}
# set with a set argument
>>> set({'a', 'b', 'c'})
{'c', 'a', 'b'}
# set with a tuple argument
>>> set(('a', 'b', 'c'))
{'c', 'a', 'b'}
It also works with a range argument:
>>> set(range(0, 3))
{0, 1, 2}
Set operations
There are two kinds of set operations:
Constructive Operations:
union
,intersection
,difference
, andsymmetric_difference
. Those create a new set.Relational Operations:
issubset
,issuperset
, andisdisjoint
. Those don't create a new set but return a boolean.
The union of sets A and B combine their elements. You can either write A | B
or A.union(B)
.
The intersection of seta A and B isolates their common elements. You can either write A & B
or A.intersection(B)
.
The difference between sets A and B is the elements in A but not in B. You can either write A - B
or A.difference(B)
.
The symmetric difference between sets A and B is the elements in either set but not both. You can either write A ^ B
or A.symmetric_difference(B)
.
We can test if a set A's elements are all part of set B with A.issubset(B)
. A is a subset of B.
The inverse tests if A is a superset of B with A.issuperset(B)
. If the result is True, B's elements are all in A.
We can also test if sets A and B share no elements with A.isdisjoint(B)
.
Check membership and looping
The in
keyword lets you check if an element is part of a set.
if "apple" in fruits:
do_something
This is also useful for looping.
for fruit in fruits:
do_something(fruit)
Add and remove elements
To add an element to a set, use the add
method.
fruits.add("cantaloupe")
To remove an element, you have two options: the remove
method and the discard
method. If the element was not in the set, removing it will give an error, while discarding it won't.
>>> fruits.remove('tomato')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'tomato'
>>> fruits.discard('tomato')
>>>
Tuples
A tuple is an ordered sequence of immutable elements. You can access an element by its index, but you can't change the tuple after it is created.
Tuple creation
You create an empty tuple with parentheses.
my_empty_tuple = ()
Create a tuple with elements with it in the same way. You can mix data types.
my_tuple = (1, "one", [1, 2, 3], ("a", "b"))
Note that you must add a trailing comma for single-element tuples.
one_item_tuple = ('a',)
Turn a list into a tuple with the tuple
function: tuple([1, 2, 3])
.
Tuple element access
Access elements by indexing.
>>> fruits = ("apple", "pineapple", "cherry")
>>> fruits[0]
"apple"
>>> fruits[-1]
"cherry"
Pack and unpack tuples
Packing is when you store several values in a tuple. It is the same as creating a regular tuple with parentheses.
Unpacking is when you extract values from a tuple. You write an assignment where the left-hand side is the variables where you unpack the tuple.
# Packing
person = ("Bob", 30, "Astronaut")
# Unpacking
name, age, job = person
You can use *
for arbitrary values when unpacking.
>>> numbers = (1, 2, 3, 4, 5)
>>> first, *middle, last = numbers
>>> print(first)
1
>>> print(middle)
[2, 3, 4]
>>> print(last)
5
Conclusion
That was a lot of material.
Congrats!
We didn't review some topics from previous posts, like sorting or dispatching. I included only the info necessary for future lessons.
I encourage you to revisit our previous projects. In light of this review, reread the code, play with it, and change it a bit.
We'll go back to learning with projects in the next blog post.
See you next time!
Subscribe to my newsletter
Read articles from Had Will directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
