๐ฝ Recording
- ๐ Python: Tips & Tricks
- ๐ชท Zen of Python
- ๐ Python History
- ๐ป String Formatting
- ๐ฑ Basic Data Structures
- ๐งโ๐ง Data Structures: Examples
- ๐ฆ Values Unpacking
- ๐พ Data Classes
- ๐ฆ Type Hinting
- ๐ทโโ๏ธ Operators
- ๐งฎ Named Parameters
- ๐ Practical Examples
- ๐จ Tools
- ๐ Outro: Key Advice
- ๐โโ๏ธ Thank You!
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!
- 3.6 (Dec 23, 2016): f-strings
- 3.7 (Jun 27, 2018): data classes
- 3.8 (Oct 14, 2019): walrus operator
- 3.9 (Oct 5, 2020): simpler dictionary updates/merges
- 3.10 (Oct 4, 2021): pattern matching
- 3.11 (Oct 3, 2022): performance increases (~25%)
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!'
i๏ธ 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}
๐ซ
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)
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!'
i๏ธ 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
i๏ธ 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'}
- Lists
- Strings
- Dicts
- Tuples
- Sets
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
i๏ธ 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=0if not specifiedstop_index=len(array)if not specified- it's exclusive:
stop_indexvalue is not included in the slice result
- it's exclusive:
step_index=1if not specified
data = [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]
๐ซ 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]
๐ซ 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']
๐ซ
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'}
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:
- IDEs (e.g. PyCharm) and static type checkers (e.g. mypy) to catch errors before runtime
- to have a better, self-documented code
๐ซ
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}
==: 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
i๏ธ 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
i๏ธ 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-1316021.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
- Write as short and lean code as possible
- Use the most recent version of Python
- Try ideas with REPL quickly
- Use type hinting
- Know your basic data structures
- Use list & dict comprehensions
- Use f-strings
- Use data classes
- RealPython.com is a great source of guide on specific topics (example)