๐ฝ Recording
import this
>>> The Zen of Python, by Tim Peters
>>> Beautiful is better than ugly.
>>> Explicit is better than implicit.
>>> Simple is better than complex.
>>> Complex is better than complicated.
>>> Flat is better than nested.
>>> Sparse is better than dense.
>>> Readability counts.
>>> Special cases aren't special enough to break the rules.
>>> Although practicality beats purity.
>>> Errors should never pass silently.
>>> Unless explicitly silenced.
>>> In the face of ambiguity, refuse the temptation to guess.
>>> There should be one-- and preferably only one --obvious way to do it.
>>> Although that way may not be obvious at first unless you're Dutch.
>>> Now is better than never.
>>> Although never is often better than *right* now.
>>> If the implementation is hard to explain, it's a bad idea.
>>> If the implementation is easy to explain, it may be a good idea.
>>> Namespaces are one honking great idea -- let's do more of those!
There are 4 ways to do that in Python, with a single preferred one.
+
)๐ซ Plain approach to glue strings together:
name = "Bob"
greeting = "hello"
message = greeting + " there, " + name + "!"
message
>>> 'hello there, Bob!'
โน๏ธ One can only concatenate strings with strings:
units = 10
items = "apples"
print("Currently in stock: " + str(units) + " " + items)
>>> 'Currently in stock: 10 apples'
%
)๐ซ
name = "Bob"
greeting = "hello"
message = "%s there, %s!" % (greeting, name)
message
>>> 'hello there, Bob!'
# same result:
message = "%(greeting)s, %(name)s!" % {"greeting": greeting, "name": name}
.format
)๐ซ
name = "Bob"
greeting = "hello"
message = "{} there, {}!".format(greeting, name)
message
>>> 'hello there, Bob!'
# can enumerate params:
message = "{0} there, {1}!".format(greeting, name)
message = "{1} there, {0}!".format(name, greeting)
>>> 'hello there, Bob!'
# same result:
message = "{greeting} there, {name}!".format(greeting=greeting, name=name)
f-strings
)Added in Python 3.6:
โ
Looks better, more powerful, better performance (2x faster than format
, 50% faster than %
):
name = "Bob"
greeting = "hello"
message = f"{greeting} there, {name}!"
message
>>> 'hello there, Bob!'
โน๏ธ Any Python expressions and value formatting support:
import math
r = 2
print(f"Circle of radius {r} has a circumference of {2 * math.pi * r}")
>>> Circle of radius 2 has a circumference of 12.566370614359172
print(f"Circle of radius {r} has a circumference of {2 * math.pi * r:.2f}")
>>> Circle of radius 2 has a circumference of 12.57
โน๏ธ Debugging specifier =
(added in Python 3.8):
x = 123; y = 456
print(f"Calculated values: x={x}, y={y}")
>>> Calculated values: x=123, y=456
print(f"Calculated values: {x=}, {y=}")
>>> Calculated values: x=123, y=456
data = {'city': 'Berlin', 'country': 'DE'}
print(f"Result: {data=}")
>>> Result: data={'city': 'Berlin', 'country': 'DE'}
A list is a mutable, ordered array of values
data = [1, 3, 5]
data.append(7)
data
>>> [1, 3, 5, 7]
data.extend([9, 11]) # same as: data += [9, 11]
>>> [1, 3, 5, 7, 9, 11]
len(data)
>>> 6
๐ซ Index-based iteration loops:
for i in range(len(data)):
print(data[i])
>>> 1
>>> 3
>>> 5
โ Every list is iterable:
for x in data:
print(x)
>>> 1
>>> 3
>>> 5
โน๏ธ In case one needs to access the current elementโs index:
for i, x in enumerate(data):
print(f"Element {i}: {x}")
>>> Element 0: 1
>>> Element 1: 3
>>> Element 2: 5
๐ซ
data = [1, 2, -3, 4, 5]
sum_ = 0
min_ = data[0]
max_ = data[0]
for x in data:
sum_ += x
if x < min_:
min_ = x
if x > max_:
max_ = x
sum_
>>> 9
min_
>>> -3
max_
>>> 5
โ
data = [1, 2, -3, 4, 5]
sum(data)
>>> 9
min(data)
>>> -3
max(data)
>>> 5
[::]
Done with so-called ๐ฃ sushi-operator ([::]
):
array[<start_index>:<stop_index>:<step>]
start_index
= 0
if not specifiedstop_index
= len(array)
if not specified
stop_index
value is not included in the slice resultstep_index
= 1
if not specifieddata = [2, 4, 6, 8, 10]
# index: 0 1 2 3 4
data[1:] # same as [1::] or [1:5:1]
>>> [4, 6, 8, 10]
data[1:3] # same as [1:3:1]
>>> [4, 6]
data[::2] # same as [0:5:2]
>>> [2, 6, 10]
data == data[0:5:1]
>>> True
data == data[:]
>>> True
Slicing the full list with step -1
(backwards) returns a reversed version of the list:
data = [2, 4, 6, 8, 10]
data[::-1]
>>> [10, 8, 6, 4, 2]
in
๐ซ Implement searching algorithm yourself:
array = [1, 2, 3, 4, 5]
search_for = 3
found = False
for i in range(len(array)):
if array[i] == search_for:
found = True
break
print(f"Found: {found}")
>>> True
โ Let Python do it:
array = [1, 2, 3, 4, 5]
search_for = 3
found = search_for in array
print(f"Found: {found}")
>>> True
Formula: [value for item in iterable]
(for every item
in iterable
map it to value
)
# range(A, B, C) = iterator of integer sequence from A to B with a step C (B is excluded)
data = [x ** 2 for x in range(0, 5)]
data
>>> [0, 1, 4, 9, 16]
List comprehension with a condition (formula: [value for item in iterable if condition]
)
data = [3, 2, -5, 10, 21, 7]
even = [x for x in data if x % 2 == 0]
even
>>> [2, 10]
Alternative to list comprehension is to use map
(with a lambda function (inline function))
data = map(lambda x: x ** 2, range(0, 5))
print(list(data)) # `map` returns an iterator, `list` creates a materialized list of it
>>> [0, 1, 4, 9, 16]
Alternative to list comprehension with a condition is to use filter
(with a lambda function (inline function)):
data = [3, 2, -5, 10, 21, 7]
even = filter(lambda x: x % 2 == 0, data)
list(even) # `filter` returns an iterator, `list` creates a materialized list of it
>>> [2, 10]
data = [
{'city': 'Paris', 'country': 'FR'},
{'city': 'Berlin', 'country': 'DE'},
{'city': 'London', 'country': 'UK'}
]
# order by city name:
sorted(data, key=lambda x: x['city'])
>>> [{'city': 'Berlin', 'country': 'DE'}, {'city': 'London', 'country': 'UK'}, {'city': 'Paris', 'country': 'FR'}]
# order by country code reversed:
sorted(data, key=lambda x: x['country'], reverse=True)
>>> [{'city': 'London', 'country': 'UK'}, {'city': 'Paris', 'country': 'FR'}, {'city': 'Berlin', 'country': 'DE'}]
๐ซ Check if the list is empty/not empty:
if len(data) == 0:
print("List is empty")
if len(data) > 0:
print("List is not empty")
โ
if not data:
print("List is empty")
if data:
print("List is not empty")
regular_list = [[1, 2, 3, 4], [5, 6, 7], [8, 9]]
flat_list = [item for sublist in regular_list for item in sublist]
print('Original list', regular_list)
>>> Original list [[1, 2, 3, 4], [5, 6, 7], [8, 9]]
print('Transformed list', flat_list)
>>> Transformed list [1, 2, 3, 4, 5, 6, 7, 8, 9]
A string can be seen as an iterable list of characters:
data = 'oslo'
for letter in data:
print(letter.upper())
>>> O
>>> S
>>> L
>>> O
data[2]
>>> 'l'
len(data)
>>> 4
data[::-1]
>>> 'olso'
[ord(x) for x in data] # ord(x) == Unicode integer of character x
>>> [111, 115, 108, 111]
in
๐ซ Is substring in string:
"restaurant".find("aura") > -1
>>> True
"waterfall".find("fun") > -1
>>> False
โ
"aura" in "restaurant"
>>> True
"fun" in "waterfall"
>>> False
data = ' empty spaces, what are we living for? '
print(data.strip())
>>> 'empty spaces, what are we living for?'
print(data.rstrip())
>>> ' empty spaces, what are we living for?'
print(data.lstrip())
>>> 'empty spaces, what are we living for? '
Added in Python 3.9:
print("INFRA-123".removeprefix("INFRA-"))
>>> '123'
print("INFRA-123".removesuffix("-123"))
>>> 'INFRA'
string = 'lorem ipsum dolor sit amet'
tokens = string.split(" ")
tokens
>>> ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
Dict is key-val storage:
data = {'city': 'Berlin', 'country': 'DE', 'areas': ['Moabit', 'Mitte', 'Westend']}
data['areas'][1]
>>> 'Mitte'
data['city'] = 'Bielefeld'
data['city']
>>> 'Bielefeld'
Any dict is iterable:
data = {'city': 'Berlin', 'country': 'DE', 'areas': ['Moabit', 'Mitte', 'Westend']}
# iterate other keys
for key in data:
print(f"{key}: {data[key]}")
>>> city: Berlin
>>> country: DE
>>> areas: ['Moabit', 'Mitte', 'Westend']
# iterate over keys with values:
for key, val in data.items():
print(f"{key}: {val}")
>>> city: Berlin
>>> country: DE
>>> areas: ['Moabit', 'Mitte', 'Westend']
in
๐ซ
data = {'city': 'Berlin', 'country': 'DE', 'areas': ['Moabit', 'Mitte', 'Westend']}
found = False
for key in data:
if key == 'city':
found = True
break
found
>>> True
โ
data = {'city': 'Berlin', 'country': 'DE', 'areas': ['Moabit', 'Mitte', 'Westend']}
'city' in data
>>> True
'population' in data
>>> False
'continent' not in data
>>> True
Formula: {key: val for item in iterable}
data = {x: x.upper() for x in ['apple', 'banana']}
data
>>> {'apple': 'APPLE', 'banana': 'BANANA'}
[]
vs. get
data = {'a': 123, 'b': 456}
data['a']
>>> 123
data['b']
>>> 456
data['c']
>>> KeyError exception
data = {'a': 123, 'b': 456}
data.get('a')
>>> 123
data.get('b')
>>> 456
data.get('c')
>>> None
data.get('c', 'default value')
>>> 'default value'
FYI, thereโs no set
for dicts, values to be changes with the []
notation only (e.g. data["key"] = "val"
)
๐ซ Error-prone code:
data = {'a': {'b': {'c': 123}}}
element = data['a']['b']['c']
element
>>> 123
data = {'a': {'d': 456}}
element = data['a']['b']['c']
>>> Key Error!
โ Robust way to inspect a dictionary:
data = {'a': {'d': 456}}
element = data.get('a', {}).get('b', {}).get('c')
element
>>> None
europe = {'Madrid': 'Spain', 'Rome': 'Italy'}
asia = {'Tokyo': 'Japan', 'Manila': 'Philippines'}
Before Python 3.9:
{**europe, **asia}
>>> {'Madrid': 'Spain', 'Rome': 'Italy', 'Tokyo': 'Japan', 'Manila': 'Philippines'}
Starting Python 3.9:
europe | asia
>>> {'Madrid': 'Spain', 'Rome': 'Italy', 'Tokyo': 'Japan', 'Manila': 'Philippines'}
Tuple is an immutable, ordered array of values:
data = (1, 2, 3)
data[1]
>>> 2
data[1] = 10
>>> TypeError: 'tuple' object does not support item assignment
Tuples can be implicit:
a = 1, 2
a[0]
>>> 1
# the same is:
a = (1, 2)
Set is a unordered array of unique values:
data = {1, 42, -1, 1}
>>> {1, 42, -1}
data[0]
>>> TypeError: 'set' object is not subscriptable
Converting a list to a set removes duplicates:
data = [5, 2, 3, 2, 4, 3, 1]
unique = list(set(data))
unique
>>> [1, 2, 3, 4, 5]
data = [
{"city": "Berlin", "country": "DE"},
{"city": "Sydney", "country": "AU"},
{"city": "Stockholm", "country": "SE"}
]
search = next((item for item in data if item["city"] == "Sydney"), None)
search["country"]
>>> 'AU'
search = next((item for item in data if item["city"] == "Paris"), None)
search is None
>>> True
๐ซ
if operation == "READ" or operation == "WRITE":
โ
if operation in ["READ", "WRITE"]:
๐ซ Using a temporary variable:
a = 5; b = 4
tmp = a
a = b
b = tmp
a
>>> 4
b
>>> 5
โ Cut to the chase:
a = 5; b = 4
a, b = b, a # same as a, b = (b, a)
a
>>> 4
b
>>> 5
๐ซ
data = ['one', 'two']
a = data[0]
b = data[1]
print(a)
>>> one
print(b)
>>> two
โ
data = ['one', 'two']
a, b = data
a
>>> one
b
>>> two
Also works for tuples:
data = ('one', 'two', 'many', 'things')
a, b, *c = data
c
>>> ['many', 'things']
a, b = 123, 456 # same as a, b = (123, 456)
a
>>> 123
b
>>> 456
๐ซ Describing objects can be done with dicts:
d1 = {'name': 'Moabit', 'city': 'Berlin', 'country': 'DE', 'area': 7.72}
d2 = {'name': 'Greenwich', 'city': 'London', 'country': 'UK', 'area': 47.3}
d2['city']
>>> 'London'
Problem: no type hinting possible (e.g. that name
is str
and area
should be a float
) and there are no object structure restrictions in place.
๐ซ We can use classes for solving it but thatโs bit too verbose:
class District:
def __init__(self, name: str, city: str, country: str, area: float):
self.name: str = name
self.city: str = city
self.country: str = country
self.area: str = area
d1 = District(name='Moabit', city='Berlin', country='DE', area=7.72)
d2 = District(name='Greenwich', city='London', country='UK', area=47.3)
d2.city
>>> 'London'
โ Using data classes (added in Python 3.7):
from dataclasses import dataclass
@dataclass
class District:
name: str
city: str
country: str
area: float = 0.0
d1 = District(name='Moabit', city='Berlin', country='DE', area=7.72)
d2 = District(name='Greenwich', city='London', country='UK', area=47.3)
d3 = District(name='Brooklyn', city='New York', country='US')
d2.city
>>> 'London'
d3.area
>>> 0.0
Thereโs no run-time type checking, but code with type hints allows:
๐ซ
def sum_values(a, b):
return a + b
sum_values(10, 3)
>>> 13
sum_values(10, "x")
>>> TypeError: unsupported operand type(s) for +: 'int' and 'str'
โ
def sum_values(a: int, b: int) -> int:
return a + b
sum_values(10, "x") # IDE will highlight an error
Before Python 3.10:
from typing import Union
def sum_values(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
# a and b can be either int or float
return a + b
Starting Python 3.10:
def sum_values(a: int | float, b: int | float) -> int | float:
return a + b
Before Python 3.10:
from typing import Dict, List
def make_list(a: str, b: str) -> List[str]:
# return type is a list of strings
return [a, b]
def make_dict(k: str, v: str) -> Dict[str, str]:
# return type is a dict with string keys and string values
return {k: v}
make_list("hello", "world")
>>> ['hello', 'world']
make_dict("hello", "world")
>>> {'hello': 'world'}
Starting Python 3.10:
def make_list(a: str, b: str) -> list[str]:
return [a, b]
def make_dict(k: str, v: str) -> dict[str, str]:
return {k: v}
Before Python 3.10:
from dataclasses import dataclass
from typing import List
@dataclass
class City:
name: str
country: str
def sort_cities(cities: List[City]) -> List[City]:
return sorted(cities, key=lambda x: x.name)
cities = [
City(name='Madrid', country='ES'),
City(name='Berlin', country='DE'),
City(name='Edinburgh', country='UK')
]
sort_cities(cities)
>>> [City(name='Berlin', country='DE'), City(name='Edinburgh', country='UK'), City(name='Madrid', country='ES')]
Starting Python 3.10:
from dataclasses import dataclass
@dataclass
class City:
name: str
country: str
def sort_cities(cities: list[City]) -> list[City]:
return sorted(cities, key=lambda x: x.name)
Not only limited to inputs/outputs of a function:
Before Python 3.10:
from typing import List
result: List[str] = [] # not just a list of anything!
Starting Python 3.10:
result: list[str] = []
๐ซ
if value > 0 and value < 100:
โ
if 0 < value < 100:
Added in Python 3.10. Similar to switch
statements in other languages, on steroids:
def parse_command(command: str) -> str:
match command.split():
case [action, direction]:
return f"Parsed: {action=}, {direction=}"
case ["help"]:
return "Help message goes here"
case _:
return "Wrong command, 2 words expected"
parse_command("go north")
>>> "Parsed: action='go', direction='north'"
parse_command("look up")
>>> "Parsed: action='look', direction='up'"
parse_command("go")
>>> "Wrong command, 2 words expected"
parse_command("help")
>>> "Help message goes here"
Alias matching with as
, OR
matching with |
and conditional matching:
def parse_command(command: str) -> str:
match command.split():
case ["go", ("north" | "south") as direction]:
return f"Going {direction}"
case ["go", _]:
return "Sorry, can't go there!"
case (["pick", obj, "up"] | ["pick", "up", obj]) if obj in ['shovel', 'rock']:
return f"Picking up {obj}"
case ["pick", _, "up"] | ["pick", "up", _]:
return "Sorry, can't pick this up!"
case _:
return "Wrong command, 2 words expected"
parse_command("go south")
>>> "Going south"
parse_command("go left")
>>> "Sorry, can't go there!"
parse_command("pick shovel up")
>>> "Picking up shovel"
parse_command("pick phone up")
>>> "Sorry, can't pick this up!"
Adapting to different structure types:
from dataclasses import dataclass
from datetime import datetime
@dataclass
class User:
age: int
def get_age(user: dict | User) -> int:
match user:
case User(age):
return age
case {"dob": {"age": int(age) | float(age)}}:
return int(age)
case {"dob": dob}:
now = datetime.now()
dob_date = datetime.strptime(dob, "%Y-%m-%d %H:%M:%S")
return now.year - dob_date.year
get_age({"dob": "1966-04-17 11:57:01"})
>>> 56
get_age({"dob": {"date": "1957-05-20T08:36:09.083Z", "age": 64}})
>>> 64
get_age({"dob": {"age": 39.6}})
>>> 39
get_age(User(age=40))
>>> 40
if a == 5:
result = "Five!"
else:
result = "Not five..."
Shorter way to write the same:
result = "Five!" if a == 5 else "Not five..."
:=
Added in Python 3.8:
value = 123
print(value)
>>> 123
# can be written as:
print(value := 123)
>>> 123
value
>>> 123
๐ Can be fine, but not the most concise way:
numbers = [2, 8, 0, 1, 1, 9, 7, 7]
# get some stats on the list: length, sum, mean values
num_length = len(numbers)
num_sum = sum(numbers)
stats = {
"length": num_length,
"sum": num_sum,
"mean": num_sum / num_length
}
>>> stats
{'length': 8, 'sum': 35, 'mean': 4.375}
โ Doing the same with less lines of code:
numbers = [2, 8, 0, 1, 1, 9, 7, 7]
stats = {
"length": (num_length := len(numbers)),
"sum": (num_sum := sum(numbers)),
"mean": num_sum / num_length
}
>>> stats
{'length': 8, 'sum': 35, 'mean': 4.375}
==
vs is
==
: do two objects have the same contents?is
: are two objects the same thing (point to the same address in memory)?a = [1, 2, 3]
b = [1, 2, 3]
a == b
>>> True
id(a) # Python id of object a
>>> 4435362944
id(b) # Python id of object b
>>> 4435377344
a is b # same as id(a) == id(b)
>>> False
a = b
a is b
>>> True
โน๏ธ As a consequence:
a = [1, 2, 3]
b = [1, 2, 3]
a[0] = 4
print(a, b)
>>> [4, 2, 3] [1, 2, 3]
a = b # make a point to the same object as b, not copying contents of b!
a[0] = 4 # also changes b now as a and b point to the same address in memory
print(a, b)
>>> [4, 2, 3] [4, 2, 3]
๐ซ Looks cryptic, and only works for lists but not for e.g. dicts:
a = [1, 2, 3]
b = a[:]
a == b
>>> True
a is b
>>> False
โ
a = [1, 2, 3]
b = a.copy()
a == b
>>> True
a is b
>>> False
โน๏ธ Thereโs only one global None object
c = None
d = None
c == d
>>> True
c is d # c and d and not "copies" of None, they point to it
>>> True
def print_issue_info(issue_id: str, issue_title: str)
print(f"Issue id: {issue_id}, title: {issue_title}")
๐ Can be fine:
print_issue_info("1234", "Create new thing")
>>> "Issue id: 1234, title: Create new thing"
โ More explicit and human-readable:
print_issue_info(issue_id="1234", issue_title="Create new thing")
>>> "Issue id: 1234, title: Create new thing"
print_issue_info(issue_title="Create new thing", issue_id="1234")
>>> "Issue id: 1234, title: Create new thing"
๐ซ Handle errors yourself:
f = open('data.txt', 'w')
try:
f.write('hello, world')
finally:
f.close()
โ
Use a context manager with
:
with open("data.txt", "r") as f:
data = f.read()
with open("data2.txt", "w") as f:
f.write(data)
json
is a built-in Python module:
import json
data = {"a": 123, "b": None}
data_json = json.dumps(data) # dumps = dump string
print(data_json)
>>> {"a": 123, "b": null}
data_parsed = json.loads(data_json) # loads = load string
print(data_parsed == data)
>>> True
$ cat file.json
{
"a": {"b": 123}
}
import json
data = json.load(open("file.json")) # data will be a dict
print(data["a"]["b"])
>>> 123
โ Use the requests library:
import requests
url = 'https://api.github.com/some/endpoint'
headers = {'Authentication': 'Bearer mytoken'}
r = requests.get(url, headers=headers)
print(r.status_code)
>>> 200
print(r.json())
>>> {"status": "OK", "message": "hi from the API"}
REPL = read-eval-print loop
Can be used to quickly try things out in a terminal:
$ python3
Python 3.10.6 (main, Aug 11 2022, 13:49:25) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> sum(range(0, 10))
45
breakpoint()
stops execution of the program at the given line and runs an interactive debugger (added in Python 3.7):
value = 123
breakpoint()
(Pdb) value
>>> 123