ToC = {
'1. Collections': [List, Dictionary, Set, Tuple, Range, Enumerate, Iterator, Generator],
'2. Types': [Type, String, Regular_Exp, Format, Numbers, Combinatorics, Datetime],
'3. Syntax': [Function, Inline, Import, Decorator, Class, Duck_Type, Enum, Except],
'4. System': [Exit, Print, Input, Command_Line_Arguments, Open, Path, OS_Commands],
'5. Data': [JSON, Pickle, CSV, SQLite, Bytes, Struct, Array, Memory_View, Deque],
'6. Advanced': [Operator, Match_Stmt, Logging, Introspection, Threading, Coroutines],
'7. Libraries': [Progress_Bar, Plot, Table, Console_App, GUI, Scraping, Web, Profile],
'8. Multimedia': [NumPy, Image, Animation, Audio, Synthesizer, Pygame, Pandas, Plotly]
}
#Main
if __name__ == '__main__':
main()
#List
<list> = [<el_1>, <el_2>, ...]
<el> = <list>[index]
<list> = <list>[<slice>]
<list>.append(<el>)
<list>.extend(<collection>)
<list>.sort()
<list>.reverse()
<list> = sorted(<collection>)
<iter> = reversed(<list>)
<el> = max(<collection>)
<num> = sum(<collection>)
elementwise_sum = [sum(pair) for pair in zip(list_a, list_b)]
sorted_by_second = sorted(<collection>, key=lambda el: el[1])
sorted_by_both = sorted(<collection>, key=lambda el: (el[1], el[0]))
flatter_list = list(itertools.chain.from_iterable(<list>))
<int> = len(<list>)
<int> = <list>.count(<el>)
<int> = <list>.index(<el>)
<el> = <list>.pop()
<list>.insert(<int>, <el>)
<list>.remove(<el>)
<list>.clear()
#Dictionary
<dict> = {key_1: val_1, key_2: val_2, ...}
<view> = <dict>.keys()
<view> = <dict>.values()
<view> = <dict>.items()
value = <dict>.get(key, default=None)
value = <dict>.setdefault(key, default=None)
<dict> = collections.defaultdict(<type>)
<dict> = collections.defaultdict(lambda: 1)
<dict> = dict(<collection>)
<dict> = dict(zip(keys, values))
<dict> = dict.fromkeys(keys [, value])
<dict>.update(<dict>)
value = <dict>.pop(key)
{k for k, v in <dict>.items() if v == value}
{k: v for k, v in <dict>.items() if k in keys}
Counter
>>> from collections import Counter
>>> counter = Counter(['blue', 'blue', 'blue', 'red', 'red'])
>>> counter['yellow'] += 1
>>> print(counter.most_common())
[('blue', 3), ('red', 2), ('yellow', 1)]
#Set
<set> = {<el_1>, <el_2>, ...}
<set>.add(<el>)
<set>.update(<collection> [, ...])
<set> = <set>.union(<coll.>)
<set> = <set>.intersection(<coll.>)
<set> = <set>.difference(<coll.>)
<set> = <set>.symmetric_difference(<coll.>)
<bool> = <set>.issubset(<coll.>)
<bool> = <set>.issuperset(<coll.>)
<el> = <set>.pop()
<set>.remove(<el>)
<set>.discard(<el>)
Frozen Set
<frozenset> = frozenset(<collection>)
#Tuple
Tuple is an immutable and hashable list.
<tuple> = ()
<tuple> = (<el>,)
<tuple> = (<el_1>, <el_2> [, ...])
Named Tuple
Tuple's subclass with named elements.
>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y')
>>> p = Point(1, y=2)
>>> print(p)
Point(x=1, y=2)
>>> p.x, p[1]
(1, 2)
#Range
Immutable and hashable sequence of integers.
<range> = range(stop)
<range> = range(start, stop)
<range> = range(start, stop, ±step)
>>> [i for i in range(3)]
[0, 1, 2]
#Enumerate
for i, el in enumerate(<coll>, start=0):
...
#Iterator
Potentially endless stream of elements.
<iter> = iter(<collection>)
<iter> = iter(<function>, to_exclusive)
<el> = next(<iter> [, default])
<list> = list(<iter>)
Itertools
import itertools as it
<iter> = it.count(start=0, step=1)
<iter> = it.repeat(<el> [, times])
<iter> = it.cycle(<collection>)
<iter> = it.chain(<coll>, <coll> [, ...])
<iter> = it.chain.from_iterable(<coll>)
<iter> = it.islice(<coll>, to_exclusive)
<iter> = it.islice(<coll>, from_inc, …)
#Generator
def count(start, step):
while True:
yield start
start += step
>>> counter = count(10, 2)
>>> next(counter), next(counter), next(counter)
(10, 12, 14)
#Type
<type> = type(<el>)
<bool> = isinstance(<el>, <type>)
>>> type('a'), 'a'.__class__, str
(<class 'str'>, <class 'str'>, <class 'str'>)
Some types do not have built-in names, so they must be imported:
from types import FunctionType, MethodType, LambdaType, GeneratorType, ModuleType
Abstract Base Classes
Each abstract base class specifies a set of virtual subclasses. These classes are then recognized by isinstance() and issubclass() as subclasses of the ABC, although they are really not. ABC can also manually decide whether or not a specific class is its virtual subclass, usually based on which methods the class has implemented. For instance, Iterable ABC looks for method iter(), while Collection ABC looks for iter(), contains() and len().
>>> from collections.abc import Iterable, Collection, Sequence
>>> isinstance([1, 2, 3], Iterable)
True
┏━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┓
┃ │ Iterable │ Collection │ Sequence ┃
┠──────────────────┼────────────┼────────────┼────────────┨
┃ list, range, str │ ✓ │ ✓ │ ✓ ┃
┃ dict, set │ ✓ │ ✓ │ ┃
┃ iter │ ✓ │ │ ┃
┗━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┛
>>> from numbers import Number, Complex, Real, Rational, Integral
>>> isinstance(123, Number)
True
┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━┓
┃ │ Number │ Complex │ Real │ Rational │ Integral ┃
┠────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┨
┃ int │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ ┃
┃ fractions.Fraction │ ✓ │ ✓ │ ✓ │ ✓ │ ┃
┃ float │ ✓ │ ✓ │ ✓ │ │ ┃
┃ complex │ ✓ │ ✓ │ │ │ ┃
┃ decimal.Decimal │ ✓ │ │ │ │ ┃
┗━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━┛
#String
Immutable sequence of characters.
<str> = <str>.strip()
<str> = <str>.strip('<chars>')
<list> = <str>.split()
<list> = <str>.split(sep=None, maxsplit=-1)
<list> = <str>.splitlines(keepends=False)
<str> = <str>.join(<coll_of_strings>)
<bool> = <sub_str> in <str>
<bool> = <str>.startswith(<sub_str>)
<int> = <str>.find(<sub_str>)
<str> = <str>.lower()
<str> = <str>.casefold()
<str> = <str>.replace(old, new [, count])
<str> = <str>.translate(<table>)
<str> = chr(<int>)
<int> = ord(<str>)
'unicodedata.normalize("NFC", <str>)'
on strings like 'Motörhead'
before comparing them to other strings, because 'ö'
can be stored as one or two characters.'NFC'
converts such characters to a single character, while 'NFD'
converts them to two.<bool> = <str>.isdecimal()
<bool> = <str>.isdigit()
<bool> = <str>.isnumeric()
<bool> = <str>.isalnum()
<bool> = <str>.isprintable()
<bool> = <str>.isspace()
#Regex
Functions for regular expression matching.
import re
<str> = re.sub(r'<regex>', new, text, count=0)
<list> = re.findall(r'<regex>', text)
<list> = re.split(r'<regex>', text, maxsplit=0)
<Match> = re.search(r'<regex>', text)
<Match> = re.match(r'<regex>', text)
<iter> = re.finditer(r'<regex>', text)
'flags=re.IGNORECASE'
can be used with all functions that are listed above.'flags=re.MULTILINE'
makes '^'
and '$'
match the start/end of each line.'flags=re.DOTALL'
makes '.'
also accept the '\n'
(besides all other chars).'re.compile(<regex>)'
returns a Pattern object with methods sub(), findall(), etc.<str> = <Match>.group()
<str> = <Match>.group(1)
<tuple> = <Match>.groups()
<int> = <Match>.start()
<int> = <Match>.end()
Special Sequences
'\d' == '[0-9]'
'\w' == '[a-zA-Z0-9_]'
'\s' == '[ \t\n\r\f\v]'
'flags=re.ASCII'
is used. It restricts special sequence matches to the first 128 Unicode characters and also prevents '\s'
from accepting '\x1c'
, '\x1d'
, '\x1e'
and '\x1f'
(non-printable characters that divide text into files, tables, rows and fields, respectively).<str> = f'{<el_1>}, {<el_2>}'
<str> = '{}, {}'.format(<el_1>, <el_2>)
<str> = '%s, %s' % (<el_1>, <el_2>)
Example
>>> Person = collections.namedtuple('Person', 'name height')
>>> person = Person('Jean-Luc', 187)
>>> f'{person.name} is {person.height / 100} meters tall.'
'Jean-Luc is 1.87 meters tall.'
General Options
{<el>:<10}
{<el>:^10}
{<el>:>10}
{<el>:.<10}
{<el>:0}
'format(<el>, "<options>")'
function.f'{<el>:{<str/int>}[…]}'
.'='
to the expression prepends it to the output: f'{1+1=}'
returns '1+1=2'
.'!r'
to the expression converts object to string by calling its repr() method.{'abcde':10}
{'abcde':10.3}
{'abcde':.3}
{'abcde'!r:10}
Numbers
{123456:10}
{123456:10,}
{123456:10_}
{123456:+10}
{123456:=+10}
{123456: }
{-123456: }
Floats
{1.23456:10.3}
{1.23456:10.3f}
{1.23456:10.3e}
{1.23456:10.3%}
Comparison of presentation types:
┏━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓
┃ │ {<float>} │ {<float>:f} │ {<float>:e} │ {<float>:%} ┃
┠──────────────┼────────────────┼────────────────┼────────────────┼────────────────┨
┃ 0.000056789 │ '5.6789e-05' │ '0.000057' │ '5.678900e-05' │ '0.005679%' ┃
┃ 0.00056789 │ '0.00056789' │ '0.000568' │ '5.678900e-04' │ '0.056789%' ┃
┃ 0.0056789 │ '0.0056789' │ '0.005679' │ '5.678900e-03' │ '0.567890%' ┃
┃ 0.056789 │ '0.056789' │ '0.056789' │ '5.678900e-02' │ '5.678900%' ┃
┃ 0.56789 │ '0.56789' │ '0.567890' │ '5.678900e-01' │ '56.789000%' ┃
┃ 5.6789 │ '5.6789' │ '5.678900' │ '5.678900e+00' │ '567.890000%' ┃
┃ 56.789 │ '56.789' │ '56.789000' │ '5.678900e+01' │ '5678.900000%' ┃
┗━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓
┃ │ {<float>:.2} │ {<float>:.2f} │ {<float>:.2e} │ {<float>:.2%} ┃
┠──────────────┼────────────────┼────────────────┼────────────────┼────────────────┨
┃ 0.000056789 │ '5.7e-05' │ '0.00' │ '5.68e-05' │ '0.01%' ┃
┃ 0.00056789 │ '0.00057' │ '0.00' │ '5.68e-04' │ '0.06%' ┃
┃ 0.0056789 │ '0.0057' │ '0.01' │ '5.68e-03' │ '0.57%' ┃
┃ 0.056789 │ '0.057' │ '0.06' │ '5.68e-02' │ '5.68%' ┃
┃ 0.56789 │ '0.57' │ '0.57' │ '5.68e-01' │ '56.79%' ┃
┃ 5.6789 │ '5.7' │ '5.68' │ '5.68e+00' │ '567.89%' ┃
┃ 56.789 │ '5.7e+01' │ '56.79' │ '5.68e+01' │ '5678.90%' ┃
┗━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛
'{<float>:g}'
is '{<float>:.6}'
with stripped zeros, exponent starting at '1e+06'
.'{6.5:.0f}'
a '6'
and '{7.5:.0f}'
an '8'
..5
, .25
, …).{90:c}
{90:b}
{90:X}
#Numbers
<int> = int(<float/str/bool>)
<float> = float(<int/str/bool>)
<complex> = complex(real=0, imag=0)
<Fraction> = fractions.Fraction(<int>, <int>)
<Decimal> = decimal.Decimal(<str/int/tuple>)
'int(<str>)'
and 'float(<str>)'
raise ValueError if passed string is malformed.'1.1 + 2.2 != 3.3'
.'math.isclose(<float>, <float>, rel_tol=1e-09)'
.'decimal.getcontext().prec = <int>'
.'True + 1 == 2'
.<num> = pow(<num>, <num>)
<num> = abs(<num>)
<num> = round(<num> [, ±ndigits])
<num> = min(<collection>)
<num> = sum(<collection>)
Math
from math import floor, ceil, trunc
from math import pi, inf, nan, isnan
from math import sqrt, factorial
from math import sin, cos, tan
from math import log, log10, log2
Statistics
from statistics import mean, median, mode
from statistics import variance, stdev
Random
from random import random, randint, uniform
<float> = random()
<num> = randint/uniform(a, b)
<float> = gauss(mean, stdev)
<el> = choice(<sequence>)
shuffle(<list>)
Hexadecimal Numbers
<int> = 0x<hex>
<int> = int('±<hex>', 16)
<str> = hex(<int>)
Bitwise Operators
<int> = <int> & <int>
<int> = <int> | <int>
<int> = <int> ^ <int>
<int> = <int> << n_bits
<int> = ~<int>
#Combinatorics
import itertools as it
>>> list(it.product('abc', repeat=2))
[('a', 'a'), ('a', 'b'), ('a', 'c'),
('b', 'a'), ('b', 'b'), ('b', 'c'),
('c', 'a'), ('c', 'b'), ('c', 'c')]
>>> list(it.permutations('abc', 2))
[('a', 'b'), ('a', 'c'),
('b', 'a'), ('b', 'c'),
('c', 'a'), ('c', 'b')]
>>> list(it.combinations('abc', 2))
[('a', 'b'), ('a', 'c'),
('b', 'c')
]
#Datetime
Provides 'date', 'time', 'datetime' and 'timedelta' classes. All are immutable and hashable.
from datetime import date, time, datetime, timedelta, timezone
import zoneinfo, dateutil.tz
<D> = date(year, month, day)
<T> = time(hour=0, minute=0, second=0)
<DT> = datetime(year, month, day, hour=0)
<TD> = timedelta(weeks=0, days=0, hours=0)
'fold=1'
means the second pass in case of time jumping back (usually for one hour).'[±D, ]H:MM:SS[.…]'
and total_seconds() a float of all seconds.'<D/DT>.weekday()'
to get the day of the week as an integer, with Monday being 0.<D/DTn> = D/DT.today()
<DTa> = DT.now(<tzinfo>)
'<DTn>.time()'
, '<DTa>.time()'
or '<DTa>.timetz()'
.<tzinfo> = timezone.utc
<tzinfo> = timezone(<timedelta>)
<tzinfo> = dateutil.tz.tzlocal()
<tzinfo> = zoneinfo.ZoneInfo('<iana_key>')
<DTa> = <DT>.astimezone([<tzinfo>])
<Ta/DTa> = <T/DT>.replace(tzinfo=<tzinfo>)
'> pip3 install tzdata'
.<D/T/DT> = D/T/DT.fromisoformat(<str>)
<DT> = DT.strptime(<str>, '<format>')
<D/DTn> = D/DT.fromordinal(<int>)
<DTn> = DT.fromtimestamp(<float>)
<DTa> = DT.fromtimestamp(<float>, <tz>)
'YYYY-MM-DD'
, 'HH:MM:SS.mmmuuu[±HH:MM]'
, or both separated by an arbitrary character. All parts following the hours are optional.'1970-01-01 00:00 UTC'
, '1970-01-01 01:00 CET'
, …<str> = <D/T/DT>.isoformat(sep='T')
<str> = <D/T/DT>.strftime('<format>')
<int> = <D/DT>.toordinal()
<float> = <DTn>.timestamp()
<float> = <DTa>.timestamp()
Format
>>> dt = datetime.strptime('2025-08-14 23:39:00.00 +0200', '%Y-%m-%d %H:%M:%S.%f %z')
>>> dt.strftime("%dth of %B '%y (%a), %I:%M %p %Z")
"14th of August '25 (Thu), 11:39 PM UTC+02:00"
'%z'
accepts '±HH[:]MM'
and returns '±HHMM'
or empty string if object is naive.'%Z'
accepts 'UTC/GMT'
and local timezone's code and returns timezone's name, 'UTC[±HH:MM]'
if timezone is nameless, or an empty string if object is naive.<bool> = <D/T/DTn> > <D/T/DTn>
<bool> = <DTa> > <DTa>
<TD> = <D/DTn> - <D/DTn>
<TD> = <DTa> - <DTa>
<D/DT> = <D/DT> ± <TD>
<TD> = <TD> * <float>
<float> = <TD> / <TD>
#Function
Independent block of code that returns a value when called.
def <func_name>(<nondefault_args>): ...
def <func_name>(<default_args>): ...
def <func_name>(<nondefault_args>, <default_args>): ...
'return <obj/exp>'
statement.'global <var_name>'
inside the function before assigning to global variable.<obj> = <function>(<positional_args>)
<obj> = <function>(<keyword_args>)
<obj> = <function>(<positional_args>, <keyword_args>)
#Splat Operator
Splat expands a collection into positional arguments, while splatty-splat expands a dictionary into keyword arguments.
args, kwargs = (1, 2), {'z': 3}
func(*args, **kwargs)
Is the same as:
func(1, 2, z=3)
Inside Function Definition
Splat combines zero or more positional arguments into a tuple, while splatty-splat combines zero or more keyword arguments into a dictionary.
def add(*a):
return sum(a)
>>> add(1, 2, 3)
6
Allowed compositions of arguments and the ways they can be called:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓
┃ │ func(1, 2) │ func(1, y=2) │ func(x=1, y=2) ┃
┠───────────────────────────┼──────────────┼──────────────┼────────────────┨
┃ func(x, *args, **kwargs): │ ✓ │ ✓ │ ✓ ┃
┃ func(*args, y, **kwargs): │ │ ✓ │ ✓ ┃
┃ func(*, x, **kwargs): │ │ │ ✓ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛
Other Uses
<list> = [*<collection> [, ...]]
<tuple> = (*<collection>, [...])
<set> = {*<collection> [, ...]}
<dict> = {**<dict> [, ...]}
head, *body, tail = <collection>
#Inline Lambda
<func> = lambda: <return_value>
<func> = lambda <arg_1>, <arg_2>: <return_value>
Comprehensions
<list> = [i+1 for i in range(10)]
<iter> = (i for i in range(10) if i > 5)
<set> = {i+5 for i in range(10)}
<dict> = {i: i*2 for i in range(10)}
>>> [l+r for l in 'abc' for r in 'abc']
['aa', 'ab', 'ac', ..., 'cc']
Map, Filter, Reduce
from functools import reduce
<iter> = map(lambda x: x + 1, range(10))
<iter> = filter(lambda x: x > 5, range(10))
<obj> = reduce(lambda out, x: out + x, range(10))
Any, All
<bool> = any(<collection>)
<bool> = all(<collection>)
Conditional Expression
<obj> = <exp> if <condition> else <exp>
>>> [i if i else 'zero' for i in (0, 1, 2, 3)]
['zero', 1, 2, 3]
And, Or
<obj> = <exp> and <exp> [and ...]
<obj> = <exp> or <exp> [or ...]
Walrus Operator
>>> [i for ch in '0123' if (i := int(ch)) > 0]
[1, 2, 3]
Named Tuple, Enum, Dataclass
from collections import namedtuple
Point = namedtuple('Point', 'x y')
point = Point(0, 0)
from enum import Enum
Direction = Enum('Direction', 'N E S W')
direction = Direction.N
from dataclasses import make_dataclass
Player = make_dataclass('Player', ['loc', 'dir'])
player = Player(point, direction)
#Imports
Mechanism that makes code in one file available to another file.
import <module>
import <package>
import <package>.<module>
'import <package>'
does not automatically provide access to the package's modules unless they are explicitly imported in the '<package>/__init__.py'
script.'from .[…][<pkg/module>[.…]] import <obj>'
.We have/get a closure in Python when a nested function references a value of its enclosing function and then the enclosing function returns its nested function.
def get_multiplier(a):
def out(b):
return a * b
return out
>>> multiply_by_3 = get_multiplier(3)
>>> multiply_by_3(10)
30
from functools import partial
<function> = partial(<function> [, <arg_1> [, ...]])
>>> def multiply(a, b):
... return a * b
>>> multiply_by_3 = partial(multiply, 3)
>>> multiply_by_3(10)
30
'collections.defaultdict(<func>)'
, 'iter(<func>, to_exc)'
and 'dataclasses.field(default_factory=<func>)'
).If variable is being assigned to anywhere in the scope, it is regarded as a local variable, unless it is declared as a 'global' or a 'nonlocal'.
def get_counter():
i = 0
def out():
nonlocal i
i += 1
return i
return out
>>> counter = get_counter()
>>> counter(), counter(), counter()
(1, 2, 3)
#Decorator
A decorator takes a function, adds some functionality and returns it. It can be any callable, but is usually implemented as a function that returns a closure.
@decorator_name
def function_that_gets_passed_to_decorator():
...
Debugger Example
Decorator that prints function's name every time the function is called.
from functools import wraps
def debug(func):
@wraps(func)
def out(*args, **kwargs):
print(func.__name__)
return func(*args, **kwargs)
return out
@debug
def add(x, y):
return x + y
'add.__name__'
would return 'out'
.Decorator that caches function's return values. All function's arguments must be hashable.
from functools import cache
@cache
def fib(n):
return n if n < 2 else fib(n-2) + fib(n-1)
'<func>.cache_clear()'
, or use '@lru_cache(maxsize=<int>)'
decorator instead.'sys.setrecursionlimit(<int>)'
.A decorator that accepts arguments and returns a normal decorator that accepts a function.
from functools import wraps
def debug(print_result=False):
def decorator(func):
@wraps(func)
def out(*args, **kwargs):
result = func(*args, **kwargs)
print(func.__name__, result if print_result else '')
return result
return out
return decorator
@debug(print_result=True)
def add(x, y):
return x + y
'@debug'
to decorate the add() function would not work here, because debug would then receive the add() function as a 'print_result' argument. Decorators can however manually check if the argument they received is a function and act accordingly.A template for creating user-defined objects.
class MyClass:
def __init__(self, a):
self.a = a
def __str__(self):
return str(self.a)
def __repr__(self):
class_name = self.__class__.__name__
return f'{class_name}({self.a!r})'
@classmethod
def get_class_name(cls):
return cls.__name__
>>> obj = MyClass(1)
>>> obj.a, str(obj), repr(obj)
(1, '1', 'MyClass(1)')
'print(a)'
calls 'a.__str__()'
and 'a + b'
calls 'a.__add__(b)'
.'@staticmethod'
receive neither 'self' nor 'cls' argument.print(<obj>)
f'{<obj>}'
logging.warning(<obj>)
csv.writer(<file>).writerow([<obj>])
Expressions that call the repr() method:
print/str/repr([<obj>])
print/str/repr({<obj>: <obj>})
f'{<obj>!r}'
Z = make_dataclass('Z', ['a']); print/str/repr(Z(<obj>))
Subclass
class Person:
def __init__(self, name):
self.name = name
def __repr__(self):
return f'Person({self.name!r})'
def __lt__(self, other):
return self.name < other.name
class Employee(Person):
def __init__(self, name, staff_num):
super().__init__(name)
self.staff_num = staff_num
def __repr__(self):
return f'Employee({self.name!r}, {self.staff_num})'
>>> people = {Person('Ann'), Employee('Bob', 0)}
>>> sorted(people)
[Person('Ann'), Employee('Bob', 0)]
Type Annotations
'def f() -> <type>:'
).from collections import abc
<name>: <type> [| ...] [= <obj>]
<name>: list/set/abc.Iterable/abc.Sequence[<type>] [= <obj>]
<name>: tuple/dict[<type>, ...] [= <obj>]
Dataclass
Decorator that uses class variables to generate init(), repr() and eq() special methods.
from dataclasses import dataclass, field, make_dataclass
@dataclass(order=False, frozen=False)
class <class_name>:
<attr_name>: <type>
<attr_name>: <type> = <default_value>
<attr_name>: list/dict/set = field(default_factory=list/dict/set)
'order=True'
and immutable with 'frozen=True'
.'<attr_name>: list = []'
would make a list that is shared among all instances. Its 'default_factory' argument accepts any callable object.'typing.Any'
.P = make_dataclass('P', ['x', 'y'])
P = make_dataclass('P', [('x', float), ('y', float)])
P = make_dataclass('P', [('x', float, 0), ('y', float, 0)])
Property
Pythonic way of implementing getters and setters.
class Person:
@property
def name(self):
return ' '.join(self._name)
@name.setter
def name(self, value):
self._name = value.split()
>>> person = Person()
>>> person.name = '\t Guido van Rossum \n'
>>> person.name
'Guido van Rossum'
Slots
Mechanism that restricts objects to attributes listed in 'slots'.
class MyClassWithSlots:
__slots__ = ['a']
def __init__(self):
self.a = 1
Copy
from copy import copy, deepcopy
<object> = copy/deepcopy(<object>)
#Duck Types
A duck type is an implicit type that prescribes a set of special methods. Any object that has those methods defined is considered a member of that duck type.
Comparable'id(self) == id(other)'
, which is the same as 'self is other'
. That means all user-defined objects compare not equal by default (because id() returns object's memory address that is guaranteed to be unique).class MyComparable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
Hashable
'id(self)'
will not do. That is why Python automatically makes classes unhashable if you only implement eq().class MyHashable:
def __init__(self, a):
self._a = a
@property
def a(self):
return self._a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __hash__(self):
return hash(self.a)
Sortable
'key=locale.strxfrm'
to sorted() after running 'locale.setlocale(locale.LC_COLLATE, "en_US.UTF-8")'
.from functools import total_ordering
@total_ordering
class MySortable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __lt__(self, other):
if isinstance(other, type(self)):
return self.a < other.a
return NotImplemented
Iterator
class Counter:
def __init__(self):
self.i = 0
def __next__(self):
self.i += 1
return self.i
def __iter__(self):
return self
>>> counter = Counter()
>>> next(counter), next(counter), next(counter)
(1, 2, 3)
Python has many different iterator objects:
'callable(<obj>)'
or 'isinstance(<obj>, collections.abc.Callable)'
to check if object is callable. You can also call the object and check if it raised TypeError.'<function>'
as an argument, it means '<callable>'
.class Counter:
def __init__(self):
self.i = 0
def __call__(self):
self.i += 1
return self.i
>>> counter = Counter()
>>> counter(), counter(), counter()
(1, 2, 3)
Context Manager
class MyOpen:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename)
return self.file
def __exit__(self, exc_type, exception, traceback):
self.file.close()
>>> with open('test.txt', 'w') as file:
... file.write('Hello World!')
>>> with MyOpen('test.txt') as file:
... print(file.read())
Hello World!
#Iterable Duck Types Iterable
class MyIterable:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
>>> obj = MyIterable([1, 2, 3])
>>> [el for el in obj]
[1, 2, 3]
>>> 1 in obj
True
Collection
'<iterable>'
when it uses the '<collection>'
.class MyCollection:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
Sequence
class MySequence:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]
def __reversed__(self):
return reversed(self.a)
Discrepancies between glossary definitions and abstract base classes:
'abc.Iterable'
and 'abc.Collection'
, it is not a duck type. That is why 'issubclass(MySequence, abc.Sequence)'
would return False even if MySequence had all the methods defined. It however recognizes list, tuple, range, str, bytes, bytearray, array, memoryview and deque, since they are registered as Sequence's virtual subclasses.from collections import abc
class MyAbcSequence(abc.Sequence):
def __init__(self, a):
self.a = a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]
Table of required and automatically available special methods:
┏━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
┃ │ Iterable │ Collection │ Sequence │ abc.Sequence ┃
┠────────────┼────────────┼────────────┼────────────┼──────────────┨
┃ iter() │ ! │ ! │ ✓ │ ✓ ┃
┃ contains() │ ✓ │ ✓ │ ✓ │ ✓ ┃
┃ len() │ │ ! │ ! │ ! ┃
┃ getitem() │ │ │ ! │ ! ┃
┃ reversed() │ │ │ ✓ │ ✓ ┃
┃ index() │ │ │ │ ✓ ┃
┃ count() │ │ │ │ ✓ ┃
┗━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
'isinstance(<obj>, abc.Iterable)'
to return True, however any object with getitem() will work with any code expecting an iterable.'<abc>.__abstractmethods__'
to get names of required methods.Class of named constants called members.
from enum import Enum, auto
class <enum_name>(Enum):
<member_name> = auto()
<member_name> = <value>
<member_name> = <el_1>, <el_2>
<member> = <enum>.<member_name>
<member> = <enum>['<member_name>']
<member> = <enum>(<value>)
<str> = <member>.name
<obj> = <member>.value
<list> = list(<enum>)
<list> = <enum>._member_names_
<list> = [m.value for m in <enum>]
<enum> = type(<member>)
<iter> = itertools.cycle(<enum>)
<member> = random.choice(list(<enum>))
Inline
Cutlery = Enum('Cutlery', 'FORK KNIFE SPOON')
Cutlery = Enum('Cutlery', ['FORK', 'KNIFE', 'SPOON'])
Cutlery = Enum('Cutlery', {'FORK': 1, 'KNIFE': 2, 'SPOON': 3})
User-defined functions cannot be values, so they must be wrapped:
from functools import partial
LogicOp = Enum('LogicOp', {'AND': partial(lambda l, r: l and r),
'OR': partial(lambda l, r: l or r)})
#Exceptions
try:
<code>
except <exception>:
<code>
Complex Example
try:
<code_1>
except <exception_a>:
<code_2_a>
except <exception_b>:
<code_2_b>
else:
<code_2_c>
finally:
<code_3>
'else'
block will only be executed if 'try'
block had no exceptions.'finally'
block will always be executed (unless a signal is received).'signal.signal(signal_number, handler_function)'
.except <exception>: ...
except <exception> as <name>: ...
except (<exception>, [...]): ...
except (<exception>, [...]) as <name>: ...
'IndexError'
is caught by 'except LookupError:'
.'traceback.print_exc()'
to print the full error message to standard error stream.'print(<name>)'
to print just the cause of the exception (that is, its arguments).'logging.exception(<str>)'
to log the passed message, followed by the full error message of the caught exception. For details about setting up the logger see Logging.'sys.exc_info()'
to get exception type, object, and traceback of caught exception.raise <exception>
raise <exception>()
raise <exception>(<obj> [, ...])
Re-raising caught exception:
except <exception> [as <name>]:
...
raise
Exception Object
arguments = <name>.args
exc_type = <name>.__class__
filename = <name>.__traceback__.tb_frame.f_code.co_filename
func_name = <name>.__traceback__.tb_frame.f_code.co_name
line_str = linecache.getline(filename, <name>.__traceback__.tb_lineno)
trace_str = ''.join(traceback.format_tb(<name>.__traceback__))
error_msg = ''.join(traceback.format_exception(type(<name>), <name>, <name>.__traceback__))
Built-in Exceptions
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── ArithmeticError
├── AssertionError
├── AttributeError
├── EOFError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ └── ConnectionError
├── RuntimeError
│ ├── NotImplementedEr…
│ └── RecursionError
├── StopIteration
├── TypeError
└── ValueError
Collections and their exceptions:
┏━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┓
┃ │ List │ Set │ Dict ┃
┠───────────┼────────────┼────────────┼────────────┨
┃ getitem() │ IndexError │ │ KeyError ┃
┃ pop() │ IndexError │ KeyError │ KeyError ┃
┃ remove() │ ValueError │ KeyError │ ┃
┃ index() │ ValueError │ │ ┃
┗━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┛
Useful built-in exceptions:
raise TypeError('Argument is of the wrong type!')
raise ValueError('Argument has the right type but an inappropriate value!')
raise RuntimeError('I am too lazy to define my own exception!')
User-defined Exceptions
class MyError(Exception): pass
class MyInputError(MyError): pass
#Exit
Exits the interpreter by raising SystemExit exception.
import sys
sys.exit()
sys.exit(<int>)
sys.exit(<obj>)
#Print
print(<el_1>, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
'file=sys.stderr'
for messages about errors so they can be processed separately.'flush=True'
is used, or program exits.from pprint import pprint
pprint(<collection>, width=80, depth=None, compact=False, sort_dicts=True)
import sys
scripts_path = sys.argv[0]
arguments = sys.argv[1:]
Argument Parser
from argparse import ArgumentParser, FileType
p = ArgumentParser(description=<str>)
p.add_argument('-<short_name>', '--<name>', action='store_true')
p.add_argument('-<short_name>', '--<name>', type=<type>)
p.add_argument('<name>', type=<type>, nargs=1)
p.add_argument('<name>', type=<type>, nargs='+')
p.add_argument('<name>', type=<type>, nargs='?/*')
args = p.parse_args()
<obj> = args.<name>
'help=<str>'
to set argument description that will be displayed in help message.'default=<obj>'
to override None as option's or optional argument's default value.'type=FileType(<mode>)'
for files. It accepts 'encoding', but 'newline' is None.Opens a file and returns the corresponding file object.
<file> = open(<path>, mode='r', encoding=None, newline=None)
'encoding=None'
means that the default encoding is used, which is platform dependent. Best practice is to use 'encoding="utf-8"'
until it becomes the default (Python 3.15).'newline=None'
means all different end of line combinations are converted to '\n' on read, while on write all '\n' characters are converted to system's default line separator.'newline=""'
means no conversions take place, but input is still broken into chunks by readline() and readlines() on every '\n', '\r' and '\r\n'.'r'
- Read. Used by default.'w'
- Write. Deletes existing contents.'x'
- Write or fail if the file already exists.'a'
- Append. Creates new file if it doesn't exist.'w+'
- Read and write. Deletes existing contents.'r+'
- Read and write from the start.'a+'
- Read and write from the end.'b'
- Binary mode ('rb'
, 'wb'
, 'xb'
, …).'FileNotFoundError'
can be raised when reading with 'r'
or 'r+'
.'FileExistsError'
can be raised when writing with 'x'
.'IsADirectoryError'
and 'PermissionError'
can be raised by any.'OSError'
is the parent class of all listed exceptions.<file>.seek(0)
<file>.seek(offset)
<file>.seek(0, 2)
<bin_file>.seek(±offset, origin)
<str/bytes> = <file>.read(size=-1)
<str/bytes> = <file>.readline()
<list> = <file>.readlines()
<str/bytes> = next(<file>)
<file>.write(<str/bytes>)
<file>.writelines(<collection>)
<file>.flush()
<file>.close()
def read_file(filename):
with open(filename, encoding='utf-8') as file:
return file.readlines()
Write Text to File
def write_to_file(filename, text):
with open(filename, 'w', encoding='utf-8') as file:
file.write(text)
#Paths
import os, glob
from pathlib import Path
<str> = os.getcwd()
<str> = os.path.join(<path>, ...)
<str> = os.path.realpath(<path>)
<str> = os.path.basename(<path>)
<str> = os.path.dirname(<path>)
<tup.> = os.path.splitext(<path>)
<list> = os.listdir(path='.')
<list> = glob.glob('<pattern>')
<bool> = os.path.exists(<path>)
<bool> = os.path.isfile(<path>)
<bool> = os.path.isdir(<path>)
<stat> = os.stat(<path>)
<num> = <stat>.st_mtime/st_size/…
DirEntry
Unlike listdir(), scandir() returns DirEntry objects that cache isfile, isdir, and on Windows also stat information, thus significantly increasing the performance of code that requires it.
<iter> = os.scandir(path='.')
<str> = <DirEntry>.path
<str> = <DirEntry>.name
<file> = open(<DirEntry>)
Path Object
<Path> = Path(<path> [, ...])
<Path> = <path> / <path> [/ ...]
<Path> = <Path>.resolve()
<Path> = Path()
<Path> = Path.cwd()
<Path> = Path.home()
<Path> = Path(__file__).resolve()
<Path> = <Path>.parent
<str> = <Path>.name
<str> = <Path>.suffix
<str> = <Path>.stem
<tup.> = <Path>.parts
<iter> = <Path>.iterdir()
<iter> = <Path>.glob('<pattern>')
<str> = str(<Path>)
<file> = open(<Path>)
#OS Commands
import os, shutil, subprocess
os.chdir(<path>)
os.mkdir(<path>, mode=0o777)
os.makedirs(<path>, mode=0o777)
shutil.copy(from, to)
shutil.copy2(from, to)
shutil.copytree(from, to)
os.rename(from, to)
os.replace(from, to)
shutil.move(from, to)
os.remove(<path>)
os.rmdir(<path>)
shutil.rmtree(<path>)
<pipe> = os.popen('<commands>')
<str> = <pipe>.read(size=-1)
<int> = <pipe>.close()
Sends "1 + 1" to the basic calculator and captures its output:
>>> subprocess.run('bc', input='1 + 1\n', capture_output=True, text=True)
CompletedProcess(args='bc', returncode=0, stdout='2\n', stderr='')
Sends test.in to the basic calculator running in standard mode and saves its output to test.out:
>>> from shlex import split
>>> os.popen('echo 1 + 1 > test.in')
>>> subprocess.run(split('bc -s'), stdin=open('test.in'), stdout=open('test.out', 'w'))
CompletedProcess(args=['bc', '-s'], returncode=0)
>>> open('test.out').read()
'2\n'
#JSON
Text file format for storing collections of strings and numbers.
import json
<str> = json.dumps(<list/dict>)
<coll> = json.loads(<str>)
Read Collection from JSON File
def read_json_file(filename):
with open(filename, encoding='utf-8') as file:
return json.load(file)
Write Collection to JSON File
def write_to_json_file(filename, collection):
with open(filename, 'w', encoding='utf-8') as file:
json.dump(collection, file, ensure_ascii=False, indent=2)
#Pickle
Binary file format for storing Python objects.
import pickle
<bytes> = pickle.dumps(<object>)
<object> = pickle.loads(<bytes>)
Read Object from Pickle File
def read_pickle_file(filename):
with open(filename, 'rb') as file:
return pickle.load(file)
Write Object to Pickle File
def write_to_pickle_file(filename, an_object):
with open(filename, 'wb') as file:
pickle.dump(an_object, file)
#CSV
Text file format for storing spreadsheets.
import csv
<file> = open(<path>, newline='')
<reader> = csv.reader(<file>)
<list> = next(<reader>)
<list> = list(<reader>)
'newline=""'
argument, every '\r\n' sequence that is embedded inside a quoted field will get converted to '\n'! For details about newline argument see Open.<file> = open(<path>, 'w', newline='')
<writer> = csv.writer(<file>)
<writer>.writerow(<collection>)
<writer>.writerows(<coll_of_coll>)
'newline=""'
argument, '\r' will be added in front of every '\n' on platforms that use '\r\n' line endings (i.e., newlines may get doubled on Windows)!'mode="a"'
to append to it or 'mode="w"'
to overwrite it.'dialect'
- Master parameter that sets the default values. String or a 'csv.Dialect' object.'delimiter'
- A one-character string that separates fields (comma, tab, semicolon, etc.).'lineterminator'
- How writer terminates rows. Reader looks for '\n', '\r' and '\r\n'.'quotechar'
- Character for quoting fields containing delimiters, quotechars, '\n' or '\r'.'escapechar'
- Character for escaping quotechars (not needed if doublequote is True).'doublequote'
- Whether quotechars inside fields are/get doubled instead of escaped.'quoting'
- 0: As necessary, 1: All, 2: All but numbers which are read as floats, 3: None.'skipinitialspace'
- Is space character at the start of the field stripped by the reader.┏━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
┃ │ excel │ excel-tab │ unix ┃
┠──────────────────┼──────────────┼──────────────┼──────────────┨
┃ delimiter │ ',' │ '\t' │ ',' ┃
┃ lineterminator │ '\r\n' │ '\r\n' │ '\n' ┃
┃ quotechar │ '"' │ '"' │ '"' ┃
┃ escapechar │ None │ None │ None ┃
┃ doublequote │ True │ True │ True ┃
┃ quoting │ 0 │ 0 │ 1 ┃
┃ skipinitialspace │ False │ False │ False ┃
┗━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
Read Rows from CSV File
def read_csv_file(filename, **csv_params):
with open(filename, encoding='utf-8', newline='') as file:
return list(csv.reader(file, **csv_params))
Write Rows to CSV File
def write_to_csv_file(filename, rows, mode='w', **csv_params):
with open(filename, mode, encoding='utf-8', newline='') as file:
writer = csv.writer(file, **csv_params)
writer.writerows(rows)
#SQLite
A server-less database engine that stores each database into its own file.
import sqlite3
<conn> = sqlite3.connect(<path>)
<conn>.close()
Read
<cursor> = <conn>.execute('<query>')
<tuple> = <cursor>.fetchone()
<list> = <cursor>.fetchall()
Write
<conn>.execute('<query>')
<conn>.commit()
<conn>.rollback()
Or:
with <conn>:
<conn>.execute('<query>')
Placeholders
<conn>.execute('<query>', <list/tuple>)
<conn>.execute('<query>', <dict/namedtuple>)
<conn>.executemany('<query>', <coll_of_coll>)
Values are not actually saved in this example because 'conn.commit()'
is omitted!
>>> conn = sqlite3.connect('test.db')
>>> conn.execute('CREATE TABLE person (name TEXT, height INTEGER) STRICT')
>>> conn.execute('INSERT INTO person VALUES (?, ?)', ('Jean-Luc', 187))
>>> conn.execute('SELECT rowid, * FROM person').fetchall()
[(1, 'Jean-Luc', 187)]
SQLAlchemy
Library for interacting with various DB systems via SQL, method chaining, or ORM.
from sqlalchemy import create_engine, text
<engine> = create_engine('<url>')
<conn> = <engine>.connect()
<cursor> = <conn>.execute(text('<query>'), …)
with <conn>.begin(): ...
┏━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Dialect │ pip3 install │ Dependencies ┃
┠─────────────────┼──────────────┼──────────────────────────────────┨
┃ mysql │ mysqlclient │ www.pypi.org/project/mysqlclient ┃
┃ postgresql │ psycopg2 │ www.pypi.org/project/psycopg2 ┃
┃ mssql │ pyodbc │ www.pypi.org/project/pyodbc ┃
┃ oracle+oracledb │ oracledb │ www.pypi.org/project/oracledb ┃
┗━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
#Bytes
A bytes object is an immutable sequence of single bytes. Mutable version is called bytearray.
<bytes> = b'<str>'
<int> = <bytes>[index]
<bytes> = <bytes>[<slice>]
<bytes> = <bytes>.join(<coll_of_bytes>)
Encode
<bytes> = bytes(<coll_of_ints>)
<bytes> = bytes(<str>, 'utf-8')
<bytes> = bytes.fromhex('<hex>')
<bytes> = <int>.to_bytes(n_bytes, …)
Decode
<list> = list(<bytes>)
<str> = str(<bytes>, 'utf-8')
<str> = <bytes>.hex()
<int> = int.from_bytes(<bytes>, …)
Read Bytes from File
def read_bytes(filename):
with open(filename, 'rb') as file:
return file.read()
Write Bytes to File
def write_bytes(filename, bytes_obj):
with open(filename, 'wb') as file:
file.write(bytes_obj)
#Struct
from struct import pack, unpack
<bytes> = pack('<format>', <el_1> [, ...])
<tuple> = unpack('<format>', <bytes>)
>>> pack('>hhl', 1, 2, 3)
b'\x00\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('>hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
Format For standard type sizes and manual alignment (padding) start format string with:
'='
- System's byte order (usually little-endian).'<'
- Little-endian (i.e. least significant byte first).'>'
- Big-endian (also '!'
).'c'
- A bytes object with a single element. For pad byte use 'x'
.'<n>s'
- A bytes object with n elements (not effected by byte order).'b'
- char (1/1)'h'
- short (2/2)'i'
- int (2/4)'l'
- long (4/4)'q'
- long long (8/8)'f'
- float (4/4)'d'
- double (8/8)List that can only hold numbers of a predefined type. Available types and their minimum sizes in bytes are listed above. Type sizes and byte order are always determined by the system, however bytes of each element can be reversed (by calling the byteswap() method).
from array import array
<array> = array('<typecode>', <coll_of_nums>)
<array> = array('<typecode>', <bytes>)
<array> = array('<typecode>', <array>)
<array>.fromfile(<file>, n_items)
<bytes> = bytes(<array>)
<file>.write(<array>)
#Memory View
A sequence object that points to the memory of another bytes-like object. Each element can reference a single or multiple consecutive bytes, depending on format. Order and number of elements can be changed with slicing.
<mview> = memoryview(<bytes/bytearray/array>)
<obj> = <mview>[index]
<mview> = <mview>[<slice>]
<mview> = <mview>.cast('<typecode>')
<mview>.release()
<bytes> = bytes(<mview>)
<bytes> = <bytes>.join(<coll_of_mviews>)
<array> = array('<typecode>', <mview>)
<file>.write(<mview>)
<list> = list(<mview>)
<str> = str(<mview>, 'utf-8')
<str> = <mview>.hex()
#Deque
List with efficient appends and pops from either side.
from collections import deque
<deque> = deque(<collection>)
<deque>.appendleft(<el>)
<deque>.extendleft(<collection>)
<deque>.rotate(n=1)
<el> = <deque>.popleft()
#Operator
Module of functions that provide the functionality of operators. Functions are grouped by operator precedence, from least to most binding. Functions and operators in first, third and fifth line are also ordered by precedence within a group.
import operator as op
<bool> = op.not_(<obj>)
<bool> = op.eq/ne/lt/ge/is_/is_not/contains(<obj>, <obj>)
<obj> = op.or_/xor/and_(<int/set>, <int/set>)
<int> = op.lshift/rshift(<int>, <int>)
<obj> = op.add/sub/mul/truediv/floordiv/mod(<obj>, <obj>)
<num> = op.neg/invert(<num>)
<num> = op.pow(<num>, <num>)
<func> = op.itemgetter/attrgetter/methodcaller(<obj> [, ...])
elementwise_sum = map(op.add, list_a, list_b)
sorted_by_second = sorted(<coll>, key=op.itemgetter(1))
sorted_by_both = sorted(<coll>, key=op.itemgetter(1, 0))
first_element = op.methodcaller('pop', 0)(<list>)
'x < y < z'
gets converted to '(x < y) and (y < z)
'.Executes the first block with matching pattern.
match <object/expression>:
case <pattern> [if <condition>]:
<code>
...
Patterns
<value_pattern> = 1/'abc'/True/None/math.pi
<class_pattern> = <type>()
<wildcard_patt> = _
<capture_patt> = <name>
<as_pattern> = <pattern> as <name>
<or_pattern> = <pattern> | <pattern> [| ...]
<sequence_patt> = [<pattern>, ...]
<mapping_patt> = {<value_pattern>: <patt>, ...}
<class_pattern> = <type>(<attr_name>=<patt>, ...)
'*<name>'
and '**<name>'
in sequence/mapping patterns to bind remaining items.'|'
> 'as'
> ','
. For example, '[1, 2]'
is matched by the 'case 1|2, 2|3 as x if x == 2:'
block.>>> from pathlib import Path
>>> match Path('/home/gto/python-cheatsheet/README.md'):
... case Path(
... parts=['/', 'home', user, *_]
... ) as p if p.name.lower().startswith('readme') and p.is_file():
... print(f'{p.name} is a readme file that belongs to user {user}.')
README.md is a readme file that belongs to user gto.
#Logging
import logging as log
log.basicConfig(filename=<path>, level='DEBUG')
log.debug/info/warning/error/critical(<str>)
<Logger> = log.getLogger(__name__)
<Logger>.<level>(<str>)
<Logger>.exception(<str>)
Setup
log.basicConfig(
filename=None,
format='%(levelname)s:%(name)s:%(message)s',
level=log.WARNING,
handlers=[log.StreamHandler(sys.stderr)]
)
<Formatter> = log.Formatter('<format>')
<Handler> = log.FileHandler(<path>, mode='a')
<Handler>.setFormatter(<Formatter>)
<Handler>.setLevel(<int/str>)
<Logger>.addHandler(<Handler>)
<Logger>.setLevel(<int/str>)
<Logger>.propagate = <bool>
'<parent>.<name>'
.'filter(<LogRecord>)'
method (or the method itself) can be added to loggers and handlers via addFilter(). Message is dropped if filter() returns a false value.>>> logger = log.getLogger('my_module')
>>> handler = log.FileHandler('test.log', encoding='utf-8')
>>> handler.setFormatter(log.Formatter('%(asctime)s %(levelname)s:%(name)s:%(message)s'))
>>> logger.addHandler(handler)
>>> logger.setLevel('DEBUG')
>>> log.basicConfig()
>>> log.root.handlers[0].setLevel('WARNING')
>>> logger.critical('Running out of disk space.')
CRITICAL:my_module:Running out of disk space.
>>> print(open('test.log').read())
2023-02-07 23:21:01,430 CRITICAL:my_module:Running out of disk space.
#Introspection
<list> = dir()
<dict> = vars()
<dict> = globals()
<list> = dir(<obj>)
<dict> = vars(<obj>)
<bool> = hasattr(<obj>, '<name>')
value = getattr(<obj>, '<name>')
setattr(<obj>, '<name>', value)
delattr(<obj>, '<name>')
<Sig> = inspect.signature(<func>)
<dict> = <Sig>.parameters
<memb> = <Param>.kind
<type> = <Param>.annotation
#Threading
CPython interpreter can only run a single thread at a time. Using multiple threads won't result in a faster execution, unless at least one of the threads contains an I/O operation.
from threading import Thread, Lock, RLock, Semaphore, Event, Barrier
from concurrent.futures import ThreadPoolExecutor, as_completed
Thread
<Thread> = Thread(target=<function>)
<Thread>.start()
<Thread>.join()
'kwargs=<dict>'
to pass keyword arguments to the function.'daemon=True'
, or the program won't be able to exit while the thread is alive.<lock> = Lock/RLock()
<lock>.acquire()
<lock>.release()
Semaphore, Event, Barrier
<Semaphore> = Semaphore(value=1)
<Event> = Event()
<Barrier> = Barrier(n_times)
Queue
<Queue> = queue.Queue(maxsize=0)
<Queue>.put(<el>)
<Queue>.put_nowait(<el>)
<el> = <Queue>.get()
<el> = <Queue>.get_nowait()
Thread Pool Executor
<Exec> = ThreadPoolExecutor(max_workers=None)
<iter> = <Exec>.map(<func>, <args_1>, ...)
<Futr> = <Exec>.submit(<func>, <arg_1>, ...)
<Exec>.shutdown()
<bool> = <Future>.done()
<obj> = <Future>.result(timeout=None)
<bool> = <Future>.cancel()
<iter> = as_completed(<coll_of_Futures>)
'if __name__ == "__main__": ...'
.'async'
and its call with 'await'
.'asyncio.run(<coroutine>)'
to start the first/main coroutine.import asyncio as aio
<coro> = <async_function>(<args>)
<obj> = await <coroutine>
<task> = aio.create_task(<coroutine>)
<obj> = await <task>
<coro> = aio.gather(<coro/task>, ...)
<coro> = aio.wait(<tasks>, return_when=…)
<iter> = aio.as_completed(<coros/tasks>)
Runs a terminal game where you control an asterisk that must avoid numbers:
import asyncio, collections, curses, curses.textpad, enum, random
P = collections.namedtuple('P', 'x y')
D = enum.Enum('D', 'n e s w')
W, H = 15, 7
def main(screen):
curses.curs_set(0)
screen.nodelay(True)
asyncio.run(main_coroutine(screen))
async def main_coroutine(screen):
moves = asyncio.Queue()
state = {'*': P(0, 0)} | {id_: P(W//2, H//2) for id_ in range(10)}
ai = [random_controller(id_, moves) for id_ in range(10)]
mvc = [human_controller(screen, moves), model(moves, state), view(state, screen)]
tasks = [asyncio.create_task(coro) for coro in ai + mvc]
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
async def random_controller(id_, moves):
while True:
d = random.choice(list(D))
moves.put_nowait((id_, d))
await asyncio.sleep(random.triangular(0.01, 0.65))
async def human_controller(screen, moves):
while True:
key_mappings = {258: D.s, 259: D.n, 260: D.w, 261: D.e}
if d := key_mappings.get(screen.getch()):
moves.put_nowait(('*', d))
await asyncio.sleep(0.005)
async def model(moves, state):
while state['*'] not in (state[id_] for id_ in range(10)):
id_, d = await moves.get()
deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}
state[id_] = P((state[id_].x + deltas[d].x) % W, (state[id_].y + deltas[d].y) % H)
async def view(state, screen):
offset = P(curses.COLS//2 - W//2, curses.LINES//2 - H//2)
while True:
screen.erase()
curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W)
for id_, p in state.items():
screen.addstr(offset.y + (p.y - state['*'].y + H//2) % H,
offset.x + (p.x - state['*'].x + W//2) % W, str(id_))
screen.refresh()
await asyncio.sleep(0.005)
if __name__ == '__main__':
curses.wrapper(main)
Libraries #Progress Bar
>>> import tqdm, time
>>> for el in tqdm.tqdm([1, 2, 3], desc='Processing'):
... time.sleep(1)
Processing: 100%|████████████████████| 3/3 [00:03<00:00, 1.00s/it]
#Plot
import matplotlib.pyplot as plt
plt.plot/bar/scatter(x_data, y_data [, label=<str>])
plt.legend()
plt.title/xlabel/ylabel(<str>)
plt.show()
plt.clf()
#Table Prints a CSV spreadsheet to the console:
import csv, tabulate
with open('test.csv', encoding='utf-8', newline='') as file:
rows = list(csv.reader(file))
print(tabulate.tabulate(rows, headers='firstrow'))
#Console App Runs a basic file explorer in the console:
import curses, os
from curses import A_REVERSE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT
def main(screen):
ch, first, selected, paths = 0, 0, 0, os.listdir()
while ch != ord('q'):
height, width = screen.getmaxyx()
screen.erase()
for y, filename in enumerate(paths[first : first+height]):
color = A_REVERSE if filename == paths[selected] else 0
screen.addnstr(y, 0, filename, width-1, color)
ch = screen.getch()
selected -= (ch == KEY_UP) and (selected > 0)
selected += (ch == KEY_DOWN) and (selected < len(paths)-1)
first -= (first > selected)
first += (first < selected-(height-1))
if ch in [KEY_LEFT, KEY_RIGHT, ord('\n')]:
new_dir = '..' if ch == KEY_LEFT else paths[selected]
if os.path.isdir(new_dir):
os.chdir(new_dir)
first, selected, paths = 0, 0, os.listdir()
if __name__ == '__main__':
curses.wrapper(main)
#GUI App A weight converter GUI application:
import PySimpleGUI as sg
text_box = sg.Input(default_text='100', enable_events=True, key='QUANTITY')
dropdown = sg.InputCombo(['g', 'kg', 't'], 'kg', readonly=True, enable_events=True, k='UNIT')
label = sg.Text('100 kg is 220.462 lbs.', key='OUTPUT')
button = sg.Button('Close')
window = sg.Window('Weight Converter', [[text_box, dropdown], [label], [button]])
while True:
event, values = window.read()
if event in [sg.WIN_CLOSED, 'Close']:
break
try:
quantity = float(values['QUANTITY'])
except ValueError:
continue
unit = values['UNIT']
factors = {'g': 0.001, 'kg': 1, 't': 1000}
lbs = quantity * factors[unit] / 0.45359237
window['OUTPUT'].update(value=f'{quantity} {unit} is {lbs:g} lbs.')
window.close()
#Scraping Scrapes Python's URL and logo from its Wikipedia page:
import requests, bs4, os
response = requests.get('https://en.wikipedia.org/wiki/Python_(programming_language)')
document = bs4.BeautifulSoup(response.text, 'html.parser')
table = document.find('table', class_='infobox vevent')
python_url = table.find('th', text='Website').next_sibling.a['href']
logo_url = table.find('img')['src']
filename = os.path.basename(logo_url)
with open(filename, 'wb') as file:
file.write(requests.get(f'https:{logo_url}').content)
print(f'{python_url}, file://{os.path.abspath(filename)}')
Selenium
Library for scraping websites with dynamic content.
from selenium import webdriver
<WebDrv> = webdriver.Chrome/Firefox/Safari/Edge()
<WebDrv>.get('<url>')
<str> = <WebDrv>.page_source
<El> = <WebDrv/El>.find_element('css selector', …)
<list> = <WebDrv/El>.find_elements('xpath', …)
<str> = <El>.get_attribute(<str>)
<El>.click/clear()
XPath — also available in lxml, Scrapy, and browser's console via '$x("<xpath>")'
:
<xpath> = //<element>[/ or // <element>]
<xpath> = //<element>/following::<element>
<element> = <tag><conditions><index>
<condition> = [<sub_cond> [and/or <sub_cond>]]
<sub_cond> = @<attr>[="<val>"]
<sub_cond> = contains(@<attr>, "<val>")
<sub_cond> = [//]<element>
#Web App
Flask is a micro web framework/server. If you just want to open a html file in a web browser use 'webbrowser.open(<path>)'
instead.
import flask as fl
app = fl.Flask(__name__)
app.run(host=None, port=None, debug=None)
'http://localhost:5000'
. Use 'host="0.0.0.0"'
to run externally.@app.route('/img/<path:filename>')
def serve_file(filename):
return fl.send_from_directory('DIRNAME', filename)
Serving HTML
@app.route('/<sport>')
def serve_html(sport):
return fl.render_template_string('<h1>{{title}}</h1>', title=sport)
'fl.render_template(filename, <kwargs>)'
renders a file located in 'templates' dir.'fl.abort(<int>)'
returns error code and 'return fl.redirect(<url>)'
redirects.'fl.request.args[<str>]'
returns parameter from query string (URL part right of '?').'fl.session[<str>] = <obj>'
stores session data. It requires secret key to be set at the startup with 'app.secret_key = <str>'
.@app.post('/<sport>/odds')
def serve_json(sport):
team = fl.request.form['team']
return {'team': team, 'odds': [2.09, 3.74, 3.68]}
Starts the app in its own thread and queries its REST API:
>>> import threading, requests
>>> threading.Thread(target=app.run, daemon=True).start()
>>> url = 'http://localhost:5000/football/odds'
>>> response = requests.post(url, data={'team': 'arsenal f.c.'})
>>> response.json()
{'team': 'arsenal f.c.', 'odds': [2.09, 3.74, 3.68]}
#Profiling
from time import perf_counter
start_time = perf_counter()
...
duration_in_seconds = perf_counter() - start_time
Timing a Snippet
>>> from timeit import timeit
>>> timeit('list(range(10000))', number=1000, globals=globals(), setup='pass')
0.19373
Profiling by Line
$ pip3 install line_profiler
$ echo '@profile
def main():
a = list(range(10000))
b = set(range(10000))
main()' > test.py
$ kernprof -lv test.py
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 @profile
2 def main():
3 1 253.4 253.4 32.2 a = list(range(10000))
4 1 534.1 534.1 67.8 b = set(range(10000))
Call and Flame Graphs
$ apt/brew install graphviz && pip3 install gprof2dot snakeviz
$ tail --lines=+2 test.py > test.py
$ python3 -m cProfile -o test.prof test.py
$ gprof2dot --format=pstats test.prof | dot -T png -o test.png
$ xdg-open/open test.png
$ snakeviz test.prof
Sampling and Memory Profilers
┏━━━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━┓
┃ pip3 install │ Target │ How to run │ Lines │ Live ┃
┠──────────────┼────────────┼───────────────────────────────┼───────┼──────┨
┃ pyinstrument │ CPU │ pyinstrument test.py │ × │ × ┃
┃ py-spy │ CPU │ py-spy top -- python3 test.py │ × │ ✓ ┃
┃ scalene │ CPU+Memory │ scalene test.py │ ✓ │ × ┃
┃ memray │ Memory │ memray run --live test.py │ ✓ │ ✓ ┃
┗━━━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━┛
#NumPy
Array manipulation mini-language. It can run up to one hundred times faster than the equivalent Python code. An even faster alternative that runs on a GPU is called CuPy.
import numpy as np
<array> = np.array(<list/list_of_lists/…>)
<array> = np.zeros/ones/empty(<shape>)
<array> = np.arange(from_inc, to_exc, ±step)
<array> = np.random.randint(from_inc, to_exc, <shape>)
<view> = <array>.reshape(<shape>)
<array> = <array>.flatten()
<view> = <array>.transpose()
<array> = np.copy/abs/sqrt/log/int64(<array>)
<array> = <array>.sum/max/mean/argmax/all(axis)
<array> = np.apply_along_axis(<func>, axis, <array>)
<array> = np.concatenate(<list_of_arrays>, axis=0)
<array> = np.vstack/column_stack(<list_of_arrays>)
<array> = np.tile/repeat(<array>, <int/list> [, axis])
<el> = <2d>[row_index, col_index]
<1d_view> = <2d>[row_index]
<1d_view> = <2d>[:, col_index]
<2d_view> = <2d>[from:to_row_i, from:to_col_i]
<1d_array> = <2d>[row_indices, col_indices]
<2d_array> = <2d>[row_indices]
<2d_array> = <2d>[:, col_indices]
<2d_array> = <2d>[np.ix_(row_indices, col_indices)]
<2d_bools> = <2d> > <el/1d/2d>
<1/2d_arr> = <2d>[<2d/1d_bools>]
':'
returns a slice of all dimension's indices. Omitted dimensions default to ':'
.'obj[i, j]'
to 'obj[(i, j)]'
. This makes '<2d>[row_i, col_i]'
and '<2d>[row_indices]'
indistinguishable to NumPy if tuple of two indices is passed!'ix_([1, 2], [3, 4])'
returns '[[1], [2]]'
and '[[3, 4]]'
. Due to broadcasting rules, this is the same as using '[[1, 1], [2, 2]]'
and '[[3, 4], [3, 4]]'
.A set of rules by which NumPy functions operate on arrays of different shapes.
left = np.array([0.1, 0.6, 0.8])
right = np.array([[0.1], [0.6], [0.8]])
1. If array shapes differ in length, left-pad the shorter shape with ones:
left = np.array([[0.1, 0.6, 0.8]])
right = np.array([[0.1], [0.6], [0.8]])
2. If any dimensions differ in size, expand the ones that have size 1 by duplicating their elements:
left = np.array([[0.1, 0.6, 0.8],
[0.1, 0.6, 0.8],
[0.1, 0.6, 0.8]])
right = np.array([[0.1, 0.1, 0.1],
[0.6, 0.6, 0.6],
[0.8, 0.8, 0.8]])
Example For each point returns index of its nearest point ([0.1, 0.6, 0.8] => [1, 2, 1]
):
>>> print(points := np.array([0.1, 0.6, 0.8]))
[0.1 0.6 0.8]
>>> print(wrapped_points := points.reshape(3, 1))
[[0.1]
[0.6]
[0.8]]
>>> print(deltas := points - wrapped_points)
[[ 0. 0.5 0.7]
[-0.5 0. 0.2]
[-0.7 -0.2 0. ]]
>>> deltas[range(3), range(3)] = np.inf
>>> print(distances := np.abs(deltas))
[[inf 0.5 0.7]
[0.5 inf 0.2]
[0.7 0.2 inf]]
>>> print(distances.argmin(axis=1))
[1 2 1]
#Image
from PIL import Image
<Image> = Image.new('<mode>', (width, height))
<Image> = Image.open(<path>)
<Image> = <Image>.convert('<mode>')
<Image>.save(<path>)
<Image>.show()
<int/tup> = <Image>.getpixel((x, y))
<ImgCore> = <Image>.getdata()
<Image>.putpixel((x, y), <int/tuple>)
<Image>.putdata(<list/ImgCore>)
<Image>.paste(<Image>, (x, y))
<Image> = <Image>.filter(<Filter>)
<Image> = <Enhance>.enhance(<float>)
<array> = np.array(<Image>)
<Image> = Image.fromarray(np.uint8(<array>))
Modes
'L'
- Lightness (greyscale image). Each pixel is an integer between 0 and 255.'RGB'
- Red, green, blue (true color image). Each pixel is a tuple of three integers.'RGBA'
- RGB with alpha. Low alpha (i.e. fourth int) makes pixel more transparent.'HSV'
- Hue, saturation, value. Three ints representing color in HSV color space.WIDTH, HEIGHT = 100, 100
n_pixels = WIDTH * HEIGHT
hues = (255 * i/n_pixels for i in range(n_pixels))
img = Image.new('HSV', (WIDTH, HEIGHT))
img.putdata([(int(h), 255, 255) for h in hues])
img.convert('RGB').save('test.png')
Adds noise to the PNG image and displays it:
from random import randint
add_noise = lambda value: max(0, min(255, value + randint(-20, 20)))
img = Image.open('test.png').convert('HSV')
img.putdata([(add_noise(h), s, v) for h, s, v in img.getdata()])
img.show()
Image Draw
from PIL import ImageDraw
<Draw> = ImageDraw.Draw(<Image>)
<Draw>.point((x, y))
<Draw>.line((x1, y1, x2, y2 [, ...]))
<Draw>.arc((x1, y1, x2, y2), deg1, deg2)
<Draw>.rectangle((x1, y1, x2, y2))
<Draw>.polygon((x1, y1, x2, y2, ...))
<Draw>.ellipse((x1, y1, x2, y2))
<Draw>.text((x, y), <str>, font=<Font>)
'fill=<color>'
to set the primary color.'width=<int>'
to set the width of lines or contours.'outline=<color>'
to set the color of the contours.'#rrggbb[aa]'
string or a color name.
from PIL import Image, ImageDraw
import imageio
WIDTH, HEIGHT, R = 126, 126, 10
frames = []
for velocity in range(1, 16):
y = sum(range(velocity))
frame = Image.new('L', (WIDTH, HEIGHT))
draw = ImageDraw.Draw(frame)
draw.ellipse((WIDTH/2-R, y, WIDTH/2+R, y+R*2), fill='white')
frames.append(frame)
frames += reversed(frames[1:-1])
imageio.mimsave('test.gif', frames, duration=0.03)
<Wave> = wave.open('<path>')
<int> = <Wave>.getframerate()
<int> = <Wave>.getnchannels()
<int> = <Wave>.getsampwidth()
<tuple> = <Wave>.getparams()
<bytes> = <Wave>.readframes(nframes)
<Wave> = wave.open('<path>', 'wb')
<Wave>.setframerate(<int>)
<Wave>.setnchannels(<int>)
<Wave>.setsampwidth(<int>)
<Wave>.setparams(<tuple>)
<Wave>.writeframes(<bytes>)
┏━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━┯━━━━━━━━━━━┓
┃ sampwidth │ min │ zero │ max ┃
┠───────────┼───────────┼──────┼───────────┨
┃ 1 │ 0 │ 128 │ 255 ┃
┃ 2 │ -32768 │ 0 │ 32767 ┃
┃ 3 │ -8388608 │ 0 │ 8388607 ┃
┗━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━┷━━━━━━━━━━━┛
Read Float Samples from WAV File
def read_wav_file(filename):
def get_int(bytes_obj):
an_int = int.from_bytes(bytes_obj, 'little', signed=(p.sampwidth != 1))
return an_int - 128 * (p.sampwidth == 1)
with wave.open(filename) as file:
p = file.getparams()
frames = file.readframes(-1)
bytes_samples = (frames[i : i + p.sampwidth] for i in range(0, len(frames), p.sampwidth))
return [get_int(b) / pow(2, (p.sampwidth * 8) - 1) for b in bytes_samples], p
Write Float Samples to WAV File
def write_to_wav_file(filename, samples_f, p=None, nchannels=1, sampwidth=2, framerate=44100):
def get_bytes(a_float):
a_float = max(-1, min(1 - 2e-16, a_float))
a_float += (p.sampwidth == 1)
a_float *= pow(2, (p.sampwidth * 8) - 1)
return int(a_float).to_bytes(p.sampwidth, 'little', signed=(p.sampwidth != 1))
if p is None:
p = wave._wave_params(nchannels, sampwidth, framerate, 0, 'NONE', 'not compressed')
with wave.open(filename, 'wb') as file:
file.setparams(p)
file.writeframes(b''.join(get_bytes(f) for f in samples_f))
Examples Saves a 440 Hz sine wave to a mono WAV file:
from math import pi, sin
samples_f = (sin(i * 2 * pi * 440 / 44100) for i in range(100_000))
write_to_wav_file('test.wav', samples_f)
Adds noise to the WAV file:
from random import uniform
samples_f, params = read_wav_file('test.wav')
samples_f = (f + uniform(-0.05, 0.05) for f in samples_f)
write_to_wav_file('test.wav', samples_f, p=params)
Plays the WAV file:
from simpleaudio import play_buffer
with wave.open('test.wav') as file:
frames, p = file.readframes(-1), file.getparams()
play_buffer(frames, p.nchannels, p.sampwidth, p.framerate).wait_done()
Text to Speech
import pyttsx3
engine = pyttsx3.init()
engine.say('Sally sells seashells by the seashore.')
engine.runAndWait()
#Synthesizer Plays Popcorn by Gershon Kingsley:
import itertools as it, math, array, simpleaudio
def play_notes(notes, bpm=132, f=44100):
get_pause = lambda n_beats: it.repeat(0, int(n_beats * 60/bpm * f))
sin_f = lambda i, hz: math.sin(i * 2 * math.pi * hz / f)
get_wave = lambda hz, n_beats: (sin_f(i, hz) for i in range(int(n_beats * 60/bpm * f)))
get_hz = lambda note: 440 * 2 ** ((int(note[:2]) - 69) / 12)
get_nbeats = lambda note: 1/2 if '♩' in note else 1/4 if '♪' in note else 1
get_samples = lambda n: get_wave(get_hz(n), get_nbeats(n)) if n else get_pause(1/4)
samples_f = it.chain.from_iterable(get_samples(n) for n in notes.split(','))
samples_i = array.array('h', (int(fl * 5000) for fl in samples_f))
simpleaudio.play_buffer(samples_i, 1, 2, f).wait_done()
play_notes('83♩,81♪,,83♪,,78♪,,74♪,,78♪,,71♪,,,,83♪,,81♪,,83♪,,78♪,,74♪,,78♪,,71♪,,,,'
'83♩,85♪,,86♪,,85♪,,86♪,,83♪,,85♩,83♪,,85♪,,81♪,,83♪,,81♪,,83♪,,79♪,,83♪,,,,')
#Pygame Opes a window and draws a square that can be moved with arrow keys:
import pygame as pg
pg.init()
screen = pg.display.set_mode((500, 500))
rect = pg.Rect(240, 240, 20, 20)
while not pg.event.get(pg.QUIT):
for event in pg.event.get(pg.KEYDOWN):
dx = (event.key == pg.K_RIGHT) - (event.key == pg.K_LEFT)
dy = (event.key == pg.K_DOWN) - (event.key == pg.K_UP)
rect = rect.move((dx * 20, dy * 20))
screen.fill(pg.Color('black'))
pg.draw.rect(screen, pg.Color('white'), rect)
pg.display.flip()
pg.quit()
Rect
Object for storing rectangular coordinates.
<Rect> = pg.Rect(x, y, width, height)
<int> = <Rect>.x/y/centerx/centery/…
<tup.> = <Rect>.topleft/center/…
<Rect> = <Rect>.move((delta_x, delta_y))
<bool> = <Rect>.collidepoint((x, y))
<bool> = <Rect>.colliderect(<Rect>)
<int> = <Rect>.collidelist(<list_of_Rect>)
<list> = <Rect>.collidelistall(<list_of_Rect>)
Surface
Object for representing images.
<Surf> = pg.display.set_mode((width, height))
<Surf> = pg.Surface((width, height))
<Surf> = pg.image.load(<path/file>)
<Surf> = pg.surfarray.make_surface(<np_array>)
<Surf> = <Surf>.subsurface(<Rect>)
<Surf>.fill(color)
<Surf>.set_at((x, y), color)
<Surf>.blit(<Surf>, (x, y))
from pygame.transform import scale, rotate
<Surf> = scale(<Surf>, (width, height))
<Surf> = rotate(<Surf>, angle)
<Surf> = flip(<Surf>, flip_x=False)
from pygame.draw import line, arc, rect
line(<Surf>, color, (x1, y1), (x2, y2))
arc(<Surf>, color, <Rect>, from_rad, to_rad)
rect(<Surf>, color, <Rect>, width=0)
<Font> = pg.font.Font(<path/file>, size)
<Surf> = <Font>.render(text, antialias, color)
Sound
<Sound> = pg.mixer.Sound(<path/file/bytes>)
<Sound>.play/stop()
Basic Mario Brothers Example
import collections, dataclasses, enum, io, itertools as it, pygame as pg, urllib.request
from random import randint
P = collections.namedtuple('P', 'x y')
D = enum.Enum('D', 'n e s w')
W, H, MAX_S = 50, 50, P(5, 10)
def main():
def get_screen():
pg.init()
return pg.display.set_mode((W*16, H*16))
def get_images():
url = 'https://gto76.github.io/python-cheatsheet/web/mario_bros.png'
img = pg.image.load(io.BytesIO(urllib.request.urlopen(url).read()))
return [img.subsurface(get_rect(x, 0)) for x in range(img.get_width() // 16)]
def get_mario():
Mario = dataclasses.make_dataclass('Mario', 'rect spd facing_left frame_cycle'.split())
return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3)))
def get_tiles():
border = [(x, y) for x in range(W) for y in range(H) if x in [0, W-1] or y in [0, H-1]]
platforms = [(randint(1, W-2), randint(2, H-2)) for _ in range(W*H // 10)]
return [get_rect(x, y) for x, y in border + platforms]
def get_rect(x, y):
return pg.Rect(x*16, y*16, 16, 16)
run(get_screen(), get_images(), get_mario(), get_tiles())
def run(screen, images, mario, tiles):
clock = pg.time.Clock()
pressed = set()
while not pg.event.get(pg.QUIT):
clock.tick(28)
pressed |= {e.key for e in pg.event.get(pg.KEYDOWN)}
pressed -= {e.key for e in pg.event.get(pg.KEYUP)}
update_speed(mario, tiles, pressed)
update_position(mario, tiles)
draw(screen, images, mario, tiles)
def update_speed(mario, tiles, pressed):
x, y = mario.spd
x += 2 * ((pg.K_RIGHT in pressed) - (pg.K_LEFT in pressed))
x += (x < 0) - (x > 0)
y += 1 if D.s not in get_boundaries(mario.rect, tiles) else (pg.K_UP in pressed) * -10
mario.spd = P(x=max(-MAX_S.x, min(MAX_S.x, x)), y=max(-MAX_S.y, min(MAX_S.y, y)))
def update_position(mario, tiles):
x, y = mario.rect.topleft
n_steps = max(abs(s) for s in mario.spd)
for _ in range(n_steps):
mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect, tiles))
x, y = x + (mario.spd.x / n_steps), y + (mario.spd.y / n_steps)
mario.rect.topleft = x, y
def get_boundaries(rect, tiles):
deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}
return {d for d, delta in deltas.items() if rect.move(delta).collidelist(tiles) != -1}
def stop_on_collision(spd, bounds):
return P(x=0 if (D.w in bounds and spd.x < 0) or (D.e in bounds and spd.x > 0) else spd.x,
y=0 if (D.n in bounds and spd.y < 0) or (D.s in bounds and spd.y > 0) else spd.y)
def draw(screen, images, mario, tiles):
screen.fill((85, 168, 255))
mario.facing_left = mario.spd.x < 0 if mario.spd.x else mario.facing_left
is_airborne = D.s not in get_boundaries(mario.rect, tiles)
image_index = 4 if is_airborne else next(mario.frame_cycle) if mario.spd.x else 6
screen.blit(images[image_index + (mario.facing_left * 9)], mario.rect)
for t in tiles:
is_border = t.x in [0, (W-1)*16] or t.y in [0, (H-1)*16]
screen.blit(images[18 if is_border else 19], t)
pg.display.flip()
if __name__ == '__main__':
main()
#Pandas
Data analysis library. For examples see Plotly.
import pandas as pd, matplotlib.pyplot as plt
Series
Ordered dictionary with a name.
>>> s = pd.Series([1, 2], index=['x', 'y'], name='a'); s
x 1
y 2
Name: a, dtype: int64
<S> = pd.Series(<list>)
<S> = pd.Series(<dict>)
<el> = <S>.loc[key]
<S> = <S>.loc[coll_of_keys]
<S> = <S>.loc[from_key : to_key_inc]
<el> = <S>[key/i]
<S> = <S>[coll_of_keys/coll_of_i]
<S> = <S>[<S_of_bools>]
<S> = <S> > <el/S>
<S> = <S> + <el/S>
<S> = <S>.head/describe/sort_values()
<S> = <S>.str.strip/lower/contains/replace()
<S> = <S>.dt.year/month/day/hour
<S> = <S>.dt.to_period('y/m/d/h')
<S>.plot.line/area/bar/pie/hist()
plt.show()
'print(<S>.to_string())'
to print a Series that has more than sixty items.'<S>.index'
to get collection of keys and '<S>.index = <coll>'
to update them.'obj[x, y]'
is converted to 'obj[(x, y)]'
and '<S>.loc[key_1, key_2]'
is how you retrieve a value from a multi-indexed Series.'np.int64'
. Series is converted to 'float64'
if np.nan is assigned to any item. Use '<S>.astype(<str/type>)'
to get converted Series.<el> = <S>.sum/max/mean/std/idxmax/count()
<S> = <S>.rank/diff/cumsum/ffill/interpol…()
<S> = <S>.isna/fillna/isin([<el/coll>])
┏━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┓
┃ │ 'sum' │ ['sum'] │ {'s': 'sum'} ┃
┠──────────────┼─────────────┼─────────────┼───────────────┨
┃ s.apply(…) │ 3 │ sum 3 │ s 3 ┃
┃ s.agg(…) │ │ │ ┃
┗━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┓
┃ │ 'rank' │ ['rank'] │ {'r': 'rank'} ┃
┠──────────────┼─────────────┼─────────────┼───────────────┨
┃ s.apply(…) │ │ rank │ ┃
┃ s.agg(…) │ x 1.0 │ x 1.0 │ r x 1.0 ┃
┃ │ y 2.0 │ y 2.0 │ y 2.0 ┃
┗━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┛
DataFrame
Table with labeled rows and columns.
>>> df = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y']); df
x y
a 1 2
b 3 4
<DF> = pd.DataFrame(<list_of_rows>)
<DF> = pd.DataFrame(<dict_of_columns>)
<el> = <DF>.loc[row_key, col_key]
<S/DF> = <DF>.loc[row_key/s]
<S/DF> = <DF>.loc[:, col_key/s]
<DF> = <DF>.loc[row_bools, col_bools]
<S/DF> = <DF>[col_key/s]
<DF> = <DF>[<S_of_bools>]
<DF> = <DF>[<DF_of_bools>]
<DF> = <DF> > <el/S/DF>
<DF> = <DF> + <el/S/DF>
<DF> = <DF>.set_index(col_key)
<DF> = <DF>.reset_index(drop=False)
<DF> = <DF>.sort_index(ascending=True)
<DF> = <DF>.sort_values(col_key/s)
<DF> = <DF>.head/tail/sample(<int>)
<DF> = <DF>.describe()
<DF> = <DF>.query('<query>')
<DF>.plot.line/area/bar/scatter(x=col_key, …)
plt.show()
DataFrame — Merge, Join, Concat:
>>> df_2 = pd.DataFrame([[4, 5], [6, 7]], index=['b', 'c'], columns=['y', 'z']); df_2
y z
b 4 5
c 6 7
┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ │ 'outer' │ 'inner' │ 'left' │ Description ┃
┠───────────────────────┼───────────────┼────────────┼────────────┼───────────────────────────┨
┃ df.merge(df_2, │ x y z │ x y z │ x y z │ Merges on column if 'on' ┃
┃ on='y', │ 0 1 2 . │ 3 4 5 │ 1 2 . │ or 'left_on/right_on' are ┃
┃ how=…) │ 1 3 4 5 │ │ 3 4 5 │ set, else on shared cols. ┃
┃ │ 2 . 6 7 │ │ │ Uses 'inner' by default. ┃
┠───────────────────────┼───────────────┼────────────┼────────────┼───────────────────────────┨
┃ df.join(df_2, │ x yl yr z │ │ x yl yr z │ Merges on row keys. ┃
┃ lsuffix='l', │ a 1 2 . . │ x yl yr z │ 1 2 . . │ Uses 'left' by default. ┃
┃ rsuffix='r', │ b 3 4 4 5 │ 3 4 4 5 │ 3 4 4 5 │ If Series is passed, it ┃
┃ how=…) │ c . . 6 7 │ │ │ is treated as a column. ┃
┠───────────────────────┼───────────────┼────────────┼────────────┼───────────────────────────┨
┃ pd.concat([df, df_2], │ x y z │ y │ │ Adds rows at the bottom. ┃
┃ axis=0, │ a 1 2 . │ 2 │ │ Uses 'outer' by default. ┃
┃ join=…) │ b 3 4 . │ 4 │ │ A Series is treated as a ┃
┃ │ b . 4 5 │ 4 │ │ column. To add a row use ┃
┃ │ c . 6 7 │ 6 │ │ pd.concat([df, DF([s])]). ┃
┠───────────────────────┼───────────────┼────────────┼────────────┼───────────────────────────┨
┃ pd.concat([df, df_2], │ x y y z │ │ │ Adds columns at the ┃
┃ axis=1, │ a 1 2 . . │ x y y z │ │ right end. Uses 'outer' ┃
┃ join=…) │ b 3 4 4 5 │ 3 4 4 5 │ │ by default. A Series is ┃
┃ │ c . . 6 7 │ │ │ treated as a column. ┃
┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
DataFrame — Aggregate, Transform, Map:
<S> = <DF>.sum/max/mean/std/idxmax/count()
<DF> = <DF>.rank/diff/cumsum/ffill/interpo…()
<DF> = <DF>.isna/fillna/isin([<el/coll>])
┏━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┓
┃ │ 'sum' │ ['sum'] │ {'x': 'sum'} ┃
┠─────────────────┼───────────────┼───────────────┼───────────────┨
┃ df.apply(…) │ x 4 │ x y │ x 4 ┃
┃ df.agg(…) │ y 6 │ sum 4 6 │ ┃
┗━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┓
┃ │ 'rank' │ ['rank'] │ {'x': 'rank'} ┃
┠─────────────────┼───────────────┼───────────────┼───────────────┨
┃ df.apply(…) │ │ x y │ ┃
┃ df.agg(…) │ x y │ rank rank │ x ┃
┃ df.transform(…) │ a 1.0 1.0 │ a 1.0 1.0 │ a 1.0 ┃
┃ │ b 2.0 2.0 │ b 2.0 2.0 │ b 2.0 ┃
┗━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┛
'axis=1'
. Exceptions to this rule are '<DF>.dropna()'
, '<DF>.drop(row_key/s)'
and '<DF>.rename(<dict/func>)'
.'<DF>.loc[row_key, (col_key_1, col_key_2)]'
.<DF> = <DF>.loc[row_key_1]
<DF> = <DF>.loc[:, (slice(None), col_key_2)]
<DF> = <DF>.set_index(col_keys)
<DF> = <DF>.pivot_table(index=col_key/s)
<S> = <DF>.stack/unstack(level=-1)
File Formats
<S/DF> = pd.read_json/pickle(<path/url/file>)
<DF> = pd.read_csv/excel(<path/url/file>)
<list> = pd.read_html(<path/url/file>)
<S/DF> = pd.read_parquet/feather/hdf(<path…>)
<DF> = pd.read_sql('<table/query>', <conn>)
<DF>.to_json/csv/html/latex/parquet(<path>)
<DF>.to_pickle/excel/feather/hdf(<path>)
<DF>.to_sql('<table_name>', <connection>)
'$ pip3 install "pandas[excel]" odfpy lxml pyarrow'
installs dependencies.'sep=","'
).'resample("y/m/d/h")'
method returns Resampler object that is similar to GroupBy.Object that groups together rows of a dataframe based on the value of the passed column.
<GB> = <DF>.groupby(col_key/s)
<DF> = <GB>.apply/filter(<func>)
<DF> = <GB>.get_group(<el>)
<S> = <GB>.size()
<GB> = <GB>[col_key]
<DF> = <GB>.sum/max/mean/std/idxmax/count()
<DF> = <GB>.rank/diff/cumsum/ffill()
<DF> = <GB>.fillna(<el>)
Divides rows into groups and sums their columns. Result has a named index that creates column 'z'
on reset_index():
>>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 6]], list('abc'), list('xyz'))
>>> gb = df.groupby('z'); gb.apply(print)
x y z
a 1 2 3
x y z
b 4 5 6
c 7 8 6
>>> gb.sum()
x y
z
3 1 2
6 11 13
Rolling
Object for rolling window calculations.
<RS/RDF/RGB> = <S/DF/GB>.rolling(win_size)
<RS/RDF/RGB> = <RDF/RGB>[col_key/s]
<S/DF> = <R>.mean/sum/max()
#Plotly
import plotly.express as px, pandas as pd
<Fig> = px.line(<DF> [, y=col_key/s [, x=col_key]])
<Fig>.update_layout(paper_bgcolor='#rrggbb')
<Fig>.write_html/json/image('<path>')
<Fig> = px.area/bar/box(<DF>, x=col_key, y=col_keys)
<Fig> = px.scatter(<DF>, x=col_key, y=col_keys)
<Fig> = px.scatter_3d(<DF>, x=col_key, y=col_key, …)
<Fig> = px.histogram(<DF>, x=col_keys, y=col_key)
Displays a line chart of total COVID-19 deaths per million grouped by continent:
covid = pd.read_csv('https://raw.githubusercontent.com/owid/covid-19-data/8dde8ca49b'
'6e648c17dd420b2726ca0779402651/public/data/owid-covid-data.csv',
usecols=['iso_code', 'date', 'population', 'total_deaths'])
continents = pd.read_csv('https://gto76.github.io/python-cheatsheet/web/continents.csv',
usecols=['Three_Letter_Country_Code', 'Continent_Name'])
df = pd.merge(covid, continents, left_on='iso_code', right_on='Three_Letter_Country_Code')
df = df.groupby(['Continent_Name', 'date']).sum().reset_index()
df['Total Deaths per Million'] = df.total_deaths * 1e6 / df.population
df = df[df.date > '2020-03-14']
df = df.rename({'date': 'Date', 'Continent_Name': 'Continent'}, axis='columns')
px.line(df, x='Date', y='Total Deaths per Million', color='Continent')
Displays a multi-axis line chart of total COVID-19 cases and changes in prices of Bitcoin, Dow Jones and gold:
import pandas as pd, selenium.webdriver, io, plotly.graph_objects as go
def main():
covid, (bitcoin, gold, dow) = get_covid_cases(), get_tickers()
df = wrangle_data(covid, bitcoin, gold, dow)
display_data(df)
def get_covid_cases():
url = 'https://catalog.ourworldindata.org/garden/covid/latest/compact/compact.csv'
df = pd.read_csv(url, parse_dates=['date'])
df = df[df.country == 'World']
s = df.set_index('date').total_cases
return s.rename('Total Cases')
def get_tickers():
with selenium.webdriver.Chrome() as driver:
driver.implicitly_wait(10)
symbols = {'Bitcoin': 'BTC-USD', 'Gold': 'GC=F', 'Dow Jones': '%5EDJI'}
return [get_ticker(driver, name, symbol) for name, symbol in symbols.items()]
def get_ticker(driver, name, symbol):
url = f'https://finance.yahoo.com/quote/{symbol}/history/'
driver.get(url + '?period1=1579651200&period2=9999999999')
if buttons := driver.find_elements('xpath', '//button[@name="reject"]'):
buttons[0].click()
html = io.StringIO(driver.page_source)
dataframes = pd.read_html(html, parse_dates=['Date'])
s = dataframes[0].set_index('Date').Open
return s.rename(name)
def wrangle_data(covid, bitcoin, gold, dow):
df = pd.concat([bitcoin, gold, dow], axis=1)
df = df.sort_index().interpolate()
df = df.loc['2020-02-23':'2021-12-20']
df = (df / df.iloc[0]) * 100
df = df.join(covid)
return df.sort_values(df.index[-1], axis=1)
def display_data(df):
figure = go.Figure()
for col_name in reversed(df.columns):
yaxis = 'y1' if col_name == 'Total Cases' else 'y2'
trace = go.Scatter(x=df.index, y=df[col_name], yaxis=yaxis, name=col_name)
figure.add_trace(trace)
figure.update_layout(
width=944,
height=423,
yaxis1=dict(title='Total Cases', rangemode='tozero'),
yaxis2=dict(title='%', rangemode='tozero', overlaying='y', side='right'),
colorway=['#EF553B', '#636EFA', '#00CC96', '#FFA152'],
legend=dict(x=1.08)
)
figure.show()
if __name__ == '__main__':
main()
#Appendix Cython
Library that compiles Python-like code into C.
import pyximport; pyximport.install()
import <cython_script>
All 'cdef'
definitions are optional, but they contribute to the speed-up:
cdef <type> <var_name> [= <obj/var>]
cdef <ctype> *<pointer_name> [= &<var>]
cdef <ctype>[size] <array_name> [= <coll/array>]
cdef <ctype> *<array_name> [= <coll/array/pointer>]
cdef <type> <func_name>(<type> [*]<arg_name>): ...
cdef class <class_name>:
cdef public <type> [*]<attr_name>
def __init__(self, <type> <arg_name>):
self.<attr_name> = <arg_name>
Virtual Environments
System for installing libraries directly into project's directory.
$ python3 -m venv NAME
$ source NAME/bin/activate
$ pip3 install LIBRARY
$ python3 FILE
$ deactivate
Basic Script Template
Run the script with '$ python3 FILE'
or '$ chmod u+x FILE; ./FILE'
. To automatically start the debugger when uncaught exception occurs run '$ python3 -m pdb -cc FILE'
.
from sys import argv, exit
from collections import defaultdict, namedtuple
from dataclasses import make_dataclass
from enum import Enum
import functools as ft, itertools as it, operator as op, re
def main():
pass
def read_file(filename):
with open(filename, encoding='utf-8') as file:
return file.readlines()
if __name__ == '__main__':
main()
#Index
'#<title>'
will limit the search to the titles.'#'
to get a link to its section.RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4