Python
Toolset
venv
Create virtual env
$ python3 -m venv ./<venv-name>
# Ex: python3 -m venv ./venv
start venv
$ source ./<venv-name>/bin/activate # you don't need the './' before the command, but the tutorial used it
exit venv
$ deactivate
pip
- Run pip show on every package that you want to uninstall so you know that it doesn’t depend on any package that you plan to uninstall
- pip uninstall
-y
Commands
$ pip list
$ pip show <package-name>
$ pip uninstall <package-name>
requirements.txt
Requirements file lists all of the program’s dependencies. You can pass this program to pip.
- To get all of the program’s dependencies, enter pip freeze > requirements.txt
- Verify with cat requirements.txt
- Go to your new venv enter pip install -r requirements.txt to install every package listed in tthe file.
Manually change requirements.txt to update package version requirements.
== means the exact version
>= means version greater than or equal to
, != 1.0.1 means there is a restriction. Put those after a comma in the version
Ex: pyvim>=3.0.2, <= 4.0.0 or , != 3.0.6
To upgrade new requirements, enter pip install –upgrade -r requirements.txt
Dev requirements files
requirements_dev.txt
$ -r requirements.txt # copies regular requirements.txt files into this
prod-packageName>=5.3.5
requirements_locked.txt is a copy of requirements.txt - special version that has locked down, known good dependencies
Types of req files
requirements.txt base level file
requirements_dev.txt dev env
requirements_locked.txt prod env
Project setup
Basics
Operators
= # assignment
== # equality (not memory)
# Identity
is # same object with same memory location
is not # different objects with same memory location
# Membership
in # True if x in y
not in # True if x not in y
# Logical
and
or
not
# Boolean
True
False
To test equality, use ==
.
if, elif, else
name = 'Smith'
if name == 'Nelson':
print('This is wrong')
elif name == 'Douglas':
print('This is wrong')
elif name == 'Smith':
print('This is right')
else:
print('No answer')
Strings
# Use three single or double quotes for multi-line strings
song = '''Happy birthday to you,
Happy birthday to you,
Happy birthday dear person,
Happy birthday to you!'''
# create a string out of another data type
str(9.87)
song.startswith('Happy') # True
song.endswith('everyone!') # False
# Find the offset of the first occurrence (returns -1 if not found)
word = 'to'
song.find(word)
15
# last occurrence (returns -1 if not found)
song.rfind(word)
89
# number of occurrences
song.count(word)
3
# all chars letters or numbers?
song.isalnum()
False
# string case funcs
phrase.strip(' .')
'you win some'
phrase = 'you win some ...'
phrase.capitalize()
'You win some ...'
phrase.title()
'You Win Some ...'
phrase.upper()
'YOU WIN SOME ...'
phrase.lower()
'you win some ...'
phrase.swapcase()
'YOU WIN SOME ...'
# multiply strings
name = 'Jack'
name * 4
# 'a', get char from string
name[1]
Substring with a slice
offsets = '0123456789'
# [:] extracts the entire sequence from start to end
offsets[:] # 0123456789
# [start:] from the start offset to the end, inclusive
offsets[5:] # 56789
# [:end] from the beginning to the end offset -1 (non-inclusive)
offsets[:5] # 01234
# [start:end] from the start offset to the end offset - 1 (end is non-inclusive)
offsets[2:7] # 23456
# [start:end:step] from the start offset to the end offset - 1 (end is non-inclusive), skipping characters by step
offsets[2:7:2] # 246
Built-in functions
offsets = '0123456789'
len(offsets) # 10
split()
tasks = 'one,two,three,four'
# if you don't provide a separator, split() uses anything that makes sense
tasks.split(',') # ['one', 'two', 'three', 'four']
tasks.split() # ['one,two,three,four']
join()
join() collapses a list of strings into a single string. Use the
following syntax:
str_list = ['one', 'two', 'three', 'four']
'\n'.join(str_list)
'one\ntwo\nthree\nfour'
' '.join(str_list)
'one two three four'
replace()
replace() does simple substring substitution
sub = 'This is the dumbest string ever'
sub.replace('dumb', 'cool')
'This is the coolest string ever'
Formatting strings
animal = 'dog'
place = 'house'
# {} and format()
'The {} is in the {}.'.format(animal, place)
'The dog is in the house.'
'The {a} is in the {b}.'.format(a='cat', b='litter box')
'The cat is in the litter box.'
# f-strings
f'The {animal} is in the {place}'
'The dog is in the house'
f'The {animal.capitalize()} is in the {place.rjust(20)}'
'The Dog is in the house'
Loops
while loop
count = 0
while count < 5:
print(count)
count += 1
0
1
2
3
4
for and in with iterators
Iterators allow you to traverse a data structure without knowing how loarge they are or how they are implemented. They include:
- strings
- lists
- tuples
- dictionaries
# for element in interable:
# do something
sentence = 'A string is an iterable'
for letter in sentence:
print(letter)
A
s
t
r
i
n
g
i
...
range() to generate numbers
range()
returns a stream of numbers within a specified range without having to use memory on a data structure. range()
returns in iterable object that you can step through with a for ... in
loop or convert to a sequence, such as a list.
range(start: stop: step)
# standard usage
for x in range(0, 5):
print(x)
0
1
2
3
4
# create a list
list(range(0, 5))
[0, 1, 2, 3, 4]
# count down with a step
for x in range(5, -1, -1):
print(x)
5
4
3
2
1
0
# create a list
list((5, -1, -1))
[5, -1, -1]
Tuples
Tuples and lists can both contain zero or more Python objects of any type.
Creating a tuple
Use parentheses or a ‘,’:
# parens
> empty_tuple = ()
> empty_tuple
()
# string followed by ','
> one_stooge = 'Larry',
> one_stooge
('Larry',)
# for multiple items, do not add a ',' after the last one
> all_stooges = 'Larry', 'Curly', 'Moe'
> all_stooges
('Larry', 'Curly', 'Moe')
# parens make it easier to read
> parens_stooges = ('Larry', 'Curly', 'Moe')
> parens_stooges
('Larry', 'Curly', 'Moe')
Tuple actions
When you assign more than one tuple value at the same time:
> parens_stooges = ('Larry', 'Curly', 'Moe')
> parens_stooges
('Larry', 'Curly', 'Moe')
> a, b, c = all_stooges
> a
'Larry'
> b
'Curly'
> c
'Moe'
# swap vals without creating a temp var
> one = 'one'
> two = 'two'
> one, two = two, one
> one
'two'
> two
'one'
# Tuple from a list
> stooge_list = ['Larry', 'Curly', 'Moe']
> tuple(stooge_list)
('Larry', 'Curly', 'Moe')
# Combine tuples with '+'
# Combine with a '+'. Note the ',' after the item in the single-value tuple:
> ('Larry',) + ('Curly', 'Moe')
('Larry', 'Curly', 'Moe')
# Duplicate items in tuple
> ('howdy',) * 5
('howdy', 'howdy', 'howdy', 'howdy', 'howdy')
# Compare tuples
> x = (1, 2, 3)
> y = (2, 3, 4)
> x == y
False
> x < y
True
> x <= y
True
# Modify a tuple
# When you concatenate a tuple with another tuple, it creates a new Python object
> first = ('one', 'two', 'three')
> second = ('four',)
> third = first + second
> third
('one', 'two', 'three', 'four')
> id(first)
2049613520640
> id(second)
2049618746960
> id(third)
2049614519888
Iterate through a tuple
Use for and in to iterate through a tuple:
> nums
('one', 'two', 'three', 'four')
> for n in nums:
... print(n)
...
one
two
three
four
Lists
Use lists to keep track of ordered items, especially when the order might change. Lists are mutable.
Creating a list
# Create an empty list with brackets
> empty_list = []
> empty_list
[]
# Empty list with list() func
> empty_too = list()
> empty_too
[]
# List with values
> beatles = ['John', 'Paul', 'George', 'Ringo']
> beatles
['John', 'Paul', 'George', 'Ringo']
# Create list out of items in an iterable
> list('individual')
['i', 'n', 'd', 'i', 'v', 'i', 'd', 'u', 'a', 'l']
# List from a tuple with list()
> tup = ('one', 'two', 'three')
> list(tup)
['one', 'two', 'three']
# list using string.split() func
> adj = 'once-in-a-lifetime'
> adj.split('-')
['once', 'in', 'a', 'lifetime']
Getting list items
> turtles = ['Leonardo', 'Donatello', 'Michaelangelo', 'Raphael']
# Get with an offset
> turtles[2]
'Michaelangelo'
# Get with a slice
> turtles[0:2]
['Leonardo', 'Donatello']
List functions
> nums = ['one', 'two', 'three', 'four']
> nums
['one', 'two', 'three', 'four']
# append to the end
> nums.append('five')
> nums
['one', 'two', 'three', 'four', 'five']
# insert at an index
> nums.insert(0, 'zero')
> nums
['zero', 'one', 'two', 'three', 'four', 'five']
# multiply a list
> ['blah'] * 3
['blah', 'blah', 'blah']
# combine lists
> numeros = ['uno', 'dos', 'tres']
> nums.extend(numeros)
> nums
['zero', 'one', 'two', 'three', 'four', 'five', 'uno', 'dos', 'tres']
# edit by index
> nums[2] = 'too'
> nums
['zero', 'one', 'too', 'three', 'four', 'five', 'uno', 'dos', 'tres']
# edit by slice
> nums[6:] = ['six', 'seven', 'eight']
> nums
['zero', 'one', 'too', 'three', 'four', 'five', 'six', 'seven', 'eight']
# delete by index
> del nums[0]
> nums
['one', 'too', 'three', 'four', 'five', 'six', 'seven', 'eight']
# delete by value
> nums.remove('seven')
> nums
['one', 'too', 'three', 'four', 'five', 'six', 'eight']
# remove from end of list
> nums.pop()
'eight'
> nums
['one', 'too', 'three', 'four', 'five', 'six']
# remove by index
> nums.pop(1)
'too'
> nums
['one', 'three', 'four', 'five', 'six']
# delete every item in the list
> nums.clear()
> nums
[]
# get index by value
> nums.index('two')
1
# use 'in' to check if in list
> 'three' in nums
True
> 'four' in nums
True
> 'nine' in nums
False
# get number of occurences by value
> nums
['one', 'two', 'three', 'four', 'two']
> nums.count('two')
2
# convert to string with a delimiter
> ', '.join(nums)
'one, two, three, four, two'
# get the length
> len(nums)
5
# sort with sorted(list)
> alpha_nums = sorted(nums)
> alpha_nums
['four', 'one', 'three', 'two', 'two']
# sort with .sort()
> nums
['one', 'two', 'three', 'four', 'two']
> nums.sort()
> nums
['four', 'one', 'three', 'two', 'two']
# copy and compare lists
> a = nums
> a
['four', 'one', 'three', 'two', 'two']
# not a copy, assigned same object to two different variables
> b = a
> b
['four', 'one', 'three', 'two', 'two']
> id(b)
2660463301888
> id(a)
2660463301888
> c = a.copy()
> id(c)
2660462605824
> d = list(c)
> id(d)
2660463342336
> e = d[:]
> id(d)
2660463342336
> a = b
> a == b
True
> a == c
True
> a == d
True
> a == e
True
Iterating through lists
# for and in to iterate through a list
> ls = ['one', 'two', 'three', 'four', 'five']
> for i in ls:
... print(i)
...
one
two
three
four
five
> es = ['uno', 'dos', 'tres', 'quatro', 'cinco']
> es
['uno', 'dos', 'tres', 'quatro', 'cinco']
# zip to iterate through multiple lists simultaneously
> for english, spanish in zip(ls, es):
... print(english, ":\t", spanish)
...
one : uno
two : dos
three : tres
four : quatro
five : cinco
List comprehensions
List comprehensions are a way to build a list. It uses the following format:
[expression
for item
in iterable
]
It moves the loop into the brackets to create a list out of what the expression returns for each item in the list. This would otherwise be written as follows:
for item
in iterable
:
expression
> ls = ['one', 'two', 'three']
# basic list comprehensions
> upper = [item.capitalize() for item in ls]
> upper
['One', 'Two', 'Three']
> num_ls = [n for n in range(0, 10)]
> num_ls
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# expression
> index_adjusted = [n + 1 for n in num_ls]
> index_adjusted
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Conditional list comprehensions add a condition after the iterable:
[expression
for item
in iterable
if condition
]
This is equal to the following:
for item
in iterable
:
if (condition
):
expression
> animals = ['cat', 'dog', 'mouse', 'rat', 'bird']
> three = [a for a in animals if len(a) == 3]
> three
['cat', 'dog', 'rat']
> Caps = [a.capitalize() for a in animals if len(a) != 3]
> Caps
['Mouse', 'Bird']
Create nested list comprehensions by listing the conditions one after the other:
# standard nested loop
> nums = range(1, 4)
> nums
range(1, 4)
> alpha = ['x', 'y']
> for num in nums:
... for a in alpha:
... print(num, a)
...
1 x
1 y
2 x
2 y
3 x
3 y
# same loop as a list comprehension
> ls_comp = [(num, a) for num in nums for a in alpha]
> for x in ls_comp:
... print(x)
...
(1, 'x')
(1, 'y')
(2, 'x')
(2, 'y')
(3, 'x')
(3, 'y')
# tuple unpacking
> for (num, a) in ls_comp:
... print(num, a)
...
1 x
1 y
2 x
2 y
3 x
3 y
Lists of lists
Exactly what it sounds like:
> evens = [2, 4, 6, 8, 10]
> odds = [1, 3, 5, 7, 9]
> prime = [1, 7, 13, 19, 23]
> nums = [evens, odds, prime]
> nums
[[2, 4, 6, 8, 10], [1, 3, 5, 7, 9], [1, 7, 13, 19, 23]]
Dictionaries
Dictionaries are key/value pairs. Keys are often strings, but they can be any value.
Creating dictionaries
# with {}
> empty_dict = {}
> empty_dict
{}
> beatles = {
... "John": "Lennon",
... "Paul": "McCartney",
... "George": "Harrison",
... "Ringo": "Starr",
... }
> beatles
{'John': 'Lennon', 'Paul': 'McCartney', 'George': 'Harrison', 'Ringo': 'Starr'}
# Create with dict()
> poet = dict(first='Edgar', middle='Alan', last='Poe')
> poet
{'first': 'Edgar', 'middle': 'Alan', 'last': 'Poe'}
You can convert two-value sequences into a dictionary:
# two-item tuples
> tups = [('a', 'z'), ('b', 'y'), ('c', 'x')]
> dict(tups)
{'a': 'z', 'b': 'y', 'c': 'x'}
# tuple of two-item lists
> tup_of_lis = (['a', 'z'], ['b', 'y'], ['c', 'x'])
> dict(tup_of_lis)
{'a': 'z', 'b': 'y', 'c': 'x'}
> two_char_str = ['ab', 'cd', 'ef']
# two-char strings
> dict(two_char_str)
{'a': 'b', 'c': 'd', 'e': 'f'}
Adding or changing an item by [key]
If there is no item with the key, then it is added. If there is, then it is replaced:
> beatles = {
... 'Lennon': 'John',
... 'McCartney': 'Paul',
... 'Harrison': 'George',
... 'Starr': 'Ringo',
... }
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo'}
# Add an item
> beatles['Preston'] = 'Billy'
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Preston': 'Billy'}
# Add, then replace an item
> beatles['Clapton'] = 'Erik'
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Preston': 'Billy', 'Clapton': 'Erik'}
> beatles['Clapton'] = 'Eric'
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Preston': 'Billy', 'Clapton': 'Eric'}
Getting items
Use .get() or .keys() to return items from the dictionary:
# get by key
> beatles.get('Martin')
> beatles.get('Martin', 'Not in the Beatles')
'Not in the Beatles'
> beatles.get('Harrison')
'George'
# get all keys in the dict
> beatles.keys()
dict_keys(['Lennon', 'McCartney', 'Harrison', 'Starr', 'Preston', 'Clapton'])
# get all values in the dict
> beatles.values()
dict_values(['John', 'Paul', 'George', 'Ringo', 'Billy', 'Eric'])
# store them in a list
> list(beatles.values())
['John', 'Paul', 'George', 'Ringo', 'Billy', 'Eric']
# get the items as a tuple
> list(beatles.items())
[('Lennon', 'John'), ('McCartney', 'Paul'), ('Harrison', 'George'), ('Starr', 'Ringo'), ('Preston', 'Billy'), ('Clapton', 'Eric')]
# length
> len(beatles)
6
> stones = {
... 'Jagger': 'Mick',
... 'Richards': 'Keith',
... 'Watts': 'Charlie',
... 'Wyman': 'Bill'
... ,}
> stones
{'Jagger': 'Mick', 'Richards': 'Keith', 'Watts': 'Charlie', 'Wyman': 'Bill'}
# combine dicts with **
> supergroup = {**beatles, **stones}
> supergroup
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Preston': 'Billy', 'Clapton': 'Eric', 'Jagger': 'Mick', 'Richards': 'Keith', 'Watts': 'Charlie', 'Wyman': 'Bill'}
> ruttles = {}
# combine dicts with update()
> ruttles.update(beatles)
> ruttles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Preston': 'Billy', 'Clapton': 'Eric'}
# delete an item by key
> del beatles['Preston']
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Clapton': 'Eric'}
> del beatles['Clapton']
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo'}
# remove an item with .pop()
> beatles.pop('Martin')
'George'
> beatles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo'}
# delete everything in the dict with .clear()
> ruttles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo', 'Preston': 'Billy', 'Clapton': 'Eric'}
> ruttles.clear()
> ruttles
{}
# check for a value with 'in'
> 'Lennon' in beatles
True
# copy()
> ruttles = beatles.copy()
> ruttles
{'Lennon': 'John', 'McCartney': 'Paul', 'Harrison': 'George', 'Starr': 'Ringo'}
# check equality
> beatles == ruttles
True
# for in loops print key by default
> for guy in beatles:
... print(guy)
...
Lennon
McCartney
Harrison
Starr
# use .values() to get values
> for guy in beatles.values():
... print(guy)
...
John
Paul
George
Ringo
# use items() to get a tuple for each dict entry
> for guy in beatles.items():
... print(guy)
...
('Lennon', 'John')
('McCartney', 'Paul')
('Harrison', 'George')
('Starr', 'Ringo')
# assign var names to k/v pairs
> for last, first in beatles.items():
... print(first, '\'s last name is ', last)
...
John 's last name is Lennon
Paul 's last name is McCartney
George 's last name is Harrison
Ringo 's last name is Starr
Dictionary comprehensions
Like lists, dictionaries have comprehension that use the following format:
{key_expression
: value_expression
for expression
in iterable
}
The following example creates a dictionary where each letter in word
is a key, and its value is the number of occurrences of each key:
> word = 'better'
# for every letter in word, create a k/v pair
> better_count = {letter: word.count(letter) for letter in word}
> better_count
{'b': 1, 'e': 2, 't': 2, 'r': 1}
Dictionary comprehensions with conditions using the following format:
{key_expression
: value_expression
for expression
in iterable
if condition
}
> vowels = 'aeiou'
> word = 'superpower'
> vowel_counts = {letter: word.count(letter) for letter in set(word) if letter in vowels}
> vowel_counts
{'o': 1, 'u': 1, 'e': 2}
Sets
A set is like a dictionary with only keys. Each key must be unique.
Creating sets
Sets are unordered:
> empty_set = set()
> empty_set
set()
> evens = {2, 4, 6, 8}
> evens
{8, 2, 4, 6}
> odds = {1, 3, 5, 7, 9}
> odds
{1, 3, 5, 7, 9}
# Convert data structures to sets
> set('letters')
{'e', 's', 't', 'r', 'l'}
> set(['Leonardo', 'Donatello', 'Raphael', 'Michaelangeo'])
{'Leonardo', 'Michaelangeo', 'Donatello', 'Raphael'}
> set( ('dog', 'cat', 'fish') )
{'cat', 'fish', 'dog'}
# Only uses keys from dictionaries
> set( {'John': 'Lennon', 'Paul': 'McCartney', 'George': 'Harrison', 'Ringo': 'Starr'} )
{'John', 'George', 'Ringo', 'Paul'}
Set functions
> evens
{8, 2, 4, 6}
> len(evens)
4
> evens.add(10)
> evens
{2, 4, 6, 8, 10}
> evens.remove(10)
> evens
{2, 4, 6, 8}
Iterate and test with for and in
Sets are often nested, like the following example that is an example of a pizza menu:
> menu = {
'classic': {'pepperoni', 'cheese'},
'italian': {'sausage', 'peppers', 'onions'},
'veggie': {'peppers', 'onions', 'mushrooms', 'olives'},
'supreme': {'pepperoni', 'ham', 'beef', 'sausage', 'peppers', 'onions', 'mushrooms', 'olives'}
}
> for pizza, topping in menu.items():
... if 'onions' in topping:
... print(pizza)
...
italian
veggie
supreme
> for pizza, topping in menu.items():
... if 'onions' in topping and not ('sausage' in topping):
... print(pizza)
...
veggie
Combinations
# Set intersection (&) checks for combinations
# of set values
> for pizza, topping in menu.items():
... if topping & {'peppers', 'onions'}:
... print(pizza)
...
italian
veggie
supreme
> for pizza, topping in menu.items():
... if 'onions' in topping and not topping & {'mushrooms', 'olives'}:
... print(pizza)
...
italian
Operators
> a = {1, 2}
> b = {2, 3}
> a
{1, 2}
> b
{2, 3}
# get the intersection
> a & b
{2}
> a.intersection(b)
{2}
> fave = menu['italian']
> fave
{'peppers', 'onions', 'sausage'}
> worst = menu['veggie']
> worst
{'peppers', 'mushrooms', 'onions', 'olives'}
> fave & worst
{'peppers', 'onions'}
# get the union (combine both sets)
> fave | worst
{'peppers', 'olives', 'onions', 'sausage', 'mushrooms'}
> fave.union(worst)
{'peppers', 'olives', 'onions', 'sausage', 'mushrooms'}
> a | b
{1, 2, 3}
# difference
> a - b
{1}
> fave - worst
{'sausage'}
# exclusive or (item in one set but not both)
> a ^ b
{1, 3}
> fave ^ worst
{'olives', 'sausage', 'mushrooms'}
# subsets
> a <= b
False
> a.issubset(b)
False
> fave <= worst
False
> fave.issubset(worst)
False
> best = menu['supreme']
> fave <= best
True
> fave < best
True
# supersets
> fave >= best
False
> fave.issuperset(worst)
False
> fave >= fave
True
> fave > fave
False
Set comprehensions
Set comprehensions use the following format:
[expression
for expression
in iterable
]
You can add a condition afer the iterable:
[expression
for expression
in iterable
if condition
]
> even_set = {number for number in range(1,20) if number % 2 == 0}
> even_set
{2, 4, 6, 8, 10, 12, 14, 16, 18}
Immutable sets
Use frozenset() to create a set that you cannot change:
# create a frozenset()
> frozenset([1, 2, 3])
frozenset({1, 2, 3})
> frozenset(set([2, 4, 6]))
frozenset({2, 4, 6})
> frozenset({1, 2, 3})
frozenset({1, 2, 3})
> frozenset( (2, 3, 1) )
frozenset({1, 2, 3})
# cannot change a frozenset()
> test = frozenset({1, 2, 3})
> test
frozenset({1, 2, 3})
> test.remove(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'remove'
> test.add(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
Functions
When you call a function with arguments, the values of the arguments are copied to the corresponding parameters inside the function body.
Python requires the pass
statement to show that the function does nothing:
> def nothing():
... pass
...
> nothing()
>
Positional argument values are copied to their corresponding parameters in order:
> def menu(wine, entree, dessert):
... return {'wine': wine, 'entree': entree, 'dessert': dessert}
...
> menu('Pino noir', 'Ribeye', 'Chocolate cake')
{'wine': 'Pino noir', 'entree': 'Ribeye', 'dessert': 'Chocolate cake'}
Default Parameter Values
The default value is used if the caller does not provide a corresponding argument.
> def menu(wine, entree, dessert='ice cream'):
... return {'wine': wine, 'entree': entree, 'dessert': dessert}
...
> menu('Chardonnay', 'Tilapia')
{'wine': 'Chardonnay', 'entree': 'Tilapia', 'dessert': 'ice cream'}
Do not use data structures, such as lists, as default values. Mutable values are instantiated with the function is defined, not when it is run:
# The first sample creates the list when the function is
# defined, and it retains the values throughout calls
> def todo(arg, tasks=[]):
... tasks.append(arg)
... print(tasks)
...
> todo('shop')
['shop']
> todo('clean')
['shop', 'clean']
# Using None as the default allows you instantiate the list each time
> def todo(arg, result=None):
... if result is None:
... result = []
... result.append(arg)
... print(result)
...
> todo('shop')
['shop']
> todo('clean')
['clean']
None vs False
None
is a special value that is not the same as False
, although it might seem that way. Use None
to distinguish a missing value from an empty value. Zero-valued objects like empty strings are all False
, but not None
.
Use the is
operator to test
> test = None
> test
> if test is None:
... print('Use the is operator')
...
Use the is operator
* parameter
The *<arg-name>
groups a variable number of positional arguments into a single tuple of parameter values:
# All args are stored in a tuple
> def print_args(*args):
... print('positional tuple:', args)
...
> print_args('one', 1, ['two', 'three'], 2, 'four')
positional tuple: ('one', 1, ['two', 'three'], 2, 'four')
# After the positional args are satisfied, all args are stored in a tuple
> def multi_args(a1, a2, *args):
... print('First:\t', a1)
... print('Second:\t', a2)
... print('Rest:\t', args)
...
> multi_args('one', 'two', 3, 4, 5, 6, 7, 8, 9)
First: one
Second: two
Rest: (3, 4, 5, 6, 7, 8, 9)
** keyword argument parameter
The **<arg-name>
groups keywords into a dictionary, where the first word is the key and the second the value. It is common to name this argument kwargs
:
# No args
> print_kwargs()
Keyword arguments: {}
# adding args
> print_kwargs(first=1, second=2, third=3)
Keyword arguments: {'first': 1, 'second': 2, 'third': 3}
Docstrings
Add docstrings to explain your functions. Use '
for a single line. For longer quotes, place '''
at the beginning and end of the quote, where the quotes are on their own line:
> def printer(arg):
... print(arg)
...
> def printer(arg):
... 'printer prints any argument to the console'
... print(arg)
# Multi-line docstrings
> def print_if_true(arg, check):
... '''
... Prints the first argument if a second argument is true.
... The operation is:
... 1. Check whether the *second* argument is true.
... 2. If it is, print the *first* argument.
... '''
... if check:
... print(thing)
...
# Pass the function name to the help() function to print the docstring
> help(print_if_true)
The __doc__
dunder method is the internal name of the docstring as a variable within the function. Use it to access the docstring for a function:
> print(print_if_true.__doc__)
Prints the first argument if a second argument is true.
The operation is:
1. Check whether the *second* argument is true.
2. If it is, print the *first* argument.
First-class citizens
Each function is an object. This means that you can assign them to variables, use them as arguments to other functions, and return them from functions.
Remember not to use the ()
after a first-class function becuase that calls the function. To assign the function, just use the name:
# define a function
> def name():
... print('My name is Bob.')
...
# function that takes a function as an argument
> def do_func(func):
... func()
...
> do_func(name)
My name is Bob.
# assign the function to a variable
> test = name
# pass the variable as an argument
> do_func(test)
My name is Bob.
# functions are just another object
> type(name)
<class 'function'>
> type(test)
<class 'function'>
Inner functions and closures
Define a function within another function to take advantage of scoped.
A closure is a function that is dynamically generated by another function and can both change and remember the values of variables that were created outside the function.
# function that contains another function, and returns that other function
> def cue_card(phrase):
... def announcer():
... return f'The first guest tonight is {phrase}'
... return announcer
...
# cannot call the func with an argument
> cue_card('Elvis')
<function cue_card.<locals>.announcer at 0x7f223d6754c0>
# assign the funciton to variables
> a = cue_card('Elvis')
> b = cue_card('Ozzy Osbourne')
# types
> type(a)
<class 'function'>
> type(b)
<class 'function'>
# call the functions
> a()
'The first guest tonight is Elvis'
> b()
'The first guest tonight is Ozzy Osbourne'
Lambdas
A lambda function is an anonymous function expressed as a single statement. It has zero or more comma-separated arguments followed by a colon (:), and then the definition of the function. You don’t have to use parentheses.
Lambdas use the following format:
lambda arg1[,arg2,...]: expression
# normal functions
> def alter_list(alist, func):
... for item in alist:
... print(func(item))
...
> def upper(word):
... return word.capitalize()
...
> alter_list(names, upper)
John
Paul
George
Ringo
# with a lambda
> alter_list(names, lambda word: word.capitalize())
John
Paul
George
Ringo
Generators
A generator is a sequence creation object that iterates through huge sequences without creating and storing the entire sequence in memory. range()
is an example.
The generator keeps track of where it was the last time it was called and returns the next value.
To write a generator, use a yield
statement instead of return
:
> def new_range(first=0, last=10, step=1):
... number = first
... while number < last:
... yield number
... number += step
...
> test = new_range(1, 5)
> type(test)
<class 'generator'>
> test
<generator object new_range at 0x7f223d687dd0>
Decorators
A decorator is a function that takes one function as input and returns another function.
> def document_func(func):
def new_function(*args, **kwargs):
print('Running function:', func.__name__)
print('Positional args:', args)
print('Keyword args:', kwargs)
result = func(*args, **kwargs)
print('Result:', result)
return result
return new_function
> def add_ints(a, b):
return a + b
> add_ints(4, 5)
9
> doc_add_ints = document_func(add_ints)
> doc_add_ints(3, 5)
Running function: add_ints
Positional args: (3, 5)
Keyword args: {}
Result: 8
8
Use the @decorator-name
before the function that you want to decorate:
> @document_func
def add_ints(a, b):
return a + b
> add_ints(4, 5)
Running function: add_ints
Positional args: (4, 5)
Keyword args: {}
Result: 9
9
# Multiple decorators per function
@document_func
@times_three_func
def add_ints(4, 5)
...
Exceptions
Exceptions are code that is executed when an error occurs. Use try
to wrap code that might fail for whatever reason, and use except
to handle the error:
> alist = [0, 1, 2, 3, 4]
> bad_index = 6
> alist[bad_index]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
# With error handling
> try:
alist[bad_index]
except:
print('Requires an index between 0 and ', len(alist)-1, ' but got', bad_index)
Requires an index between 0 and 4 but got 6
Provide an except
for each possible exception that might occur. You can capture the full exception object in a variable as follows:
except
exceptiontype as
name:
> alist = [1, 2, 3]
> bad_index = 4
# Function that catches exceptions
> def exception_func(the_list, index):
try:
the_list[index]
except IndexError as err:
print('Requires index between 0 and ', len(the_list)-1,' but got', index)
except Exception as other:
print('There was a different error:', other)
# Throw IndexError
> exception_func(alist, 5)
Requires index between 0 and 2 but got 5
# Throw Exception
> exception_func(alist, 'dog')
There was a different error: list indices must be integers or slices, not str
Defining your own Exceptions
Python’s standard library provides predefined exceptions, but you can also define your own by inheriting the Exception
class:
> class UppercaseException(Exception):
print('You threw the UpperclassException!')
> words = ['one', 'two', 'three', 'FOUR']
> words
['one', 'two', 'three', 'FOUR']
# Throw the exception that you made
> for word in words:
if word.isupper():
raise UppercaseException(word)
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
__main__.UppercaseException: FOUR
Objects
An object is a data structure that contains data, stored in attributes, and behavior, called methods (or functions).
Simple objects
# Create a dog class
> class Dog():
pass
# Instantiate a few dog objects
> fido = Dog()
> fido
<__main__.Dog object at 0x7f3c175ecfa0>
> pluto = Dog()
> pluto
<__main__.Dog object at 0x7f3c18c230d0>
Adding attributes
Assign object attributes at creation time with the __init__()
method.
> class Teacher:
def __init__(self):
pass
In the previous example:
__init__()
is the special name for a method that initializes an object from its class definition. It is a constructor.self
argument specifies that the__init__()
method refers to the individual object itself. You also have to passself
as the first argument to an instance method.
Initializers, not constructors
__init__()
is an initializer, not a constructor. After Python finds the class code, it creates an object. __init()__
initializes that object with the attributes and methods that make it unique. You do not need to have an __init()__
method for each class definition. __init()__
is used to distinguish each object of the class from one another.
Its first parameter must be self
so that it can assign any attributes to the object. For example:
> class Teacher:
def __init__(self, name):
self.name = name
> mr_smith = Teacher('Smith')
> mr_smith.name
'Smith'
In the previous example:
- Python looks up the Teacher class.
- Instantiates a new object in memory
- Calls the object’s
__init()__
method and passes the new object to__init()__
asself
, and the argument as thename
attribute. - Stores the value of
name
in the object - Returns the new object.
- Assigns the variable
mr_smith
to the object.
Inheritance
Inheritance is when you create a new class from an existing one, with some modifications. In a child class, you define only what you need to add or change in the new class, and that overrides the behavior of the old class.
To inherit from a parent class, define a subclass and pass the name of the parent in the parentheses:
> class Guitar():
def strum(self):
print('raaaaannnngggg')
> class Fender(Guitar):
pass
> issubclass(Fender, Guitar)
True
# The subclass accesses methods from the parent
> blackie = Fender()
> blackie.strum()
raaaaannnngggg
# Override the strum method
> class Fender(Guitar):
def strum(self):
print('brrroooooonnnnngggggg')
> blackie = Fender()
# Python looks up the class of the object 'blackie'
# Passes the object 'blackie' as the self param to the strum() method of the class
> blackie.strum()
brrroooooonnnnngggggg
# Overriding __init__()
> class Person():
def __init__(self, name):
self.name = name
> class MDPerson(Person):
def __init__(self, name):
self.name = "Doctor " + name
> class JDPerson(Person):
def __init__(self, name):
self.name = name + ", Esquire"
> person = Person('Jack')
> doctor = MDPerson('Jack')
> lawyer = JDPerson('Jack')
> print(person.name)
Jack
> print(doctor.name)
Doctor Jack
> print(lawyer.name)
Jack, Esquire
super()
super()
calls a parent method. The __init__()
method for a child class replaces the init() method for the parent class:
> class Person():
def __init__(self, name):
self.name = name
> class EmailPerson(Person):
def __init__(self, name, email):
super().__init__(name)
self.email = email
In the previous example:
super()
gets the definition of the parent class, Person__init__()
calls the Person.init() method. It passes the self arg to the superclass, so it needs any optional arguments that the superclass init() method needsself.email = email
assigns a value to email, which is the attribute that makes the child class unique
Use super()
to make sure that you always inherit from the superclass, especially if the superclass changes in the future.
Getters and Setters
Python doesn’t have a way to make attributes private. The best way to handle this is with ‘secret’ attribute names and properties. Use the following decorators:
@property
for the getter method.@<name>
.setter for the setter method. If you don’t specify a setter property for an attribute, you can’t set it from outside code.
Prepend hidden attribute names with double underscores (__
). For example, __hidden_attribute
.
In the following example, the object has an attribute __name
that cannot be accessed from outside the class. However, you can set it with the getters and setters:
> class Person():
def __init__(self, name):
self.__name = name
@property
def name(self):
print('getter method')
return self.__name
@name.setter
def name(self, name):
print('setter method')
self.__name = name
> musician = Person('Paul')
> musician.name
getter method
'Paul'
> musician.name = 'John'
setter method
> musician.name
getter method
'John'
Python changes the hidden attributes to look like this:
> musician._Person__name
'John'
Class and object attributes
You can assign attributes with default values to classes and change them in any objects:
> class Boots:
color = 'brown'
> red_wing = Boots()
> red_wing.color
'brown'
> red_wing.color = 'tan'
> red_wing.color
'tan'
# default class value is preserved
> Boots.color
'brown'
Method types
There are three main method types:
- instance: First argument is
self
.self
refers to the individual object. - class: First argument is
cls
(or anything you want, just be consistent). Naming itcls
is Pythonic and refers to the class itself. The first parameter refers to the class itself.
Precede class methods with the@classmethod
decorator.
Any change you make to the class affects all of the objects instantiated from the class. - static: No first argument. Precede static methods with the
@staticmethod
decorator.
Static mthods do not affect the class or its objects, it’s a convenience method.
To demonstrate a class method, we create a class that tracks the number of objects created using it:
> class ChildCounter():
count = 0
def __init__(self):
# set the class attribute with the class name
ChildCounter.count += 1
def exclaim(self):
print("I'm in the ChildCounter() class!")
@classmethod
def children(cls):
# access the class with cls
print('ChildCounter has ', cls.count, 'child objects.')
> first = ChildCounter()
> second = ChildCounter()
> third = ChildCounter()
> ChildCounter.count
To demonstrate a static method, we create a simple class with a method that does not require an object:
> class Nike():
@staticmethod
def slogan():
print('Just Do it')
> Nike.slogan()
Just Do it
Magic methods
Magic methods are methods that Python recongnizes that you can define on your objects. Here is a good list of magic methods.
Common ones are:
Method | Implementation | Description |
---|---|---|
__eq__(self, other) | self == other | checks if self is equal to other |
__str__(self) | str(self) | returns a string of your object. How you print your object |
__len__(self) | len(self) | returns the length of self |
Aggregation and Composition
Build objects with objects, like this boat:
> class Sail():
def __init__(self, description):
self.description = description
> class Tiller():
def __init__(self, length):
self.length = length
> class Boat():
def __init__(self, sail, tiller):
self.sail = sail
self.tiller = tiller
def about(self):
print('This boat has a', self.sail.description, 'sail and a', self.tiller.length, 'long tiller')
> sail = Sail('blue and white')
> tiller = Tiller('5 ft')
> boat = Boat(sail, tiller)
> boat.about()
This boat has a blue and white sail and a 5 ft long tiller
Named tuples
Named tuples are a type of tuples that you can access values by .name
and position with [offset]. You have to load the namedtuple
module, and they use the following format:
namedtuple('Type-name', 'attr1 attr2[ ...]')
This boat has a blue and white sail and a 5 ft long tiller
> from collections import namedtuple
> Boat = namedtuple('Boat', 'sail tiller')
> boat = Boat('blue', 'rudder')
> boat
Boat(sail='blue', tiller='rudder')
> boat.sail
'blue'
> boat.tiller
'rudder'
Dataclasses
Data classes let you create objects that store only data–they don’t have behavior. To define a dataclass, you have to do the following things:
- Import dataclass
- Use the
@dataclass
decorator - Define attributes with the variable annotations form of
name: type
orname: type = val
Here is how you define a dataclass:
> from dataclasses import dataclass
> @dataclass
class GuitarClass:
brand: str
color: str
price: int = 0 # 0 is the default value
> strat = GuitarClass('Fender', 'Black', 1000)
> strat
GuitarClass(brand='Fender', color='Black', price=1000)
# Define attributes in any order with named arguments
> les_paul = GuitarClass(price=2500, brand='gibson', color='honeyburst')
> les_paul
GuitarClass(brand='gibson', color='honeyburst', price=2500)