inputs = []
while (curr := input()) != "q":
inputs.append(curr)Conditional Operator
a if condition else b
# or as a switch expression
x = (a if condition else
b if condition else
c)For-else
for i in [0, 1]:
if i == 2:
return True
else:
return Falseassert expression1, expression2
# is the same as
if __debug__:
if not expression1: raise AssertionError(expression2)Everything is an object. No primitive types. All Python objects have:
id() corresponds to the memory address of the
objecttype(x), x is type(x),
isinstance(x, type(x))The type and identity cannot change during the lifetime of an object.
In Python, variables are names bound to objects (in C, a variable is a named memory location).
These are also called underscore methods or dunder methods (double-underscore methods).
__new__()
__init__()
__del__()
__repr__()
__str__()
__eq__(self, other) # lt, le, eq, ne, gt, ge
__hash__()
__bool__()
# Emulating callable objects
# Allows the object to be called as a function
__call__()
# eg
p1 = Polynomial(1,2) # 1 + 2x
p1(3) # 7
# Emulating container types
__len__()
__getitem__() # self[key]
__setitem__()
__delitem__()
__iter__() # returns an iterator type
__contains__() # `in`
# Emulating numeric types
# +, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |
__add__() # x+y
# Context manager
__enter__()
__exit__()True, False
Operators
not
and
orif A: is the same as if bool(A):. Calls
A.__nonzero__(), and if not defined, calls
A.__len__(), else returns True.
Comparisons can be chained arbitrarily. e.g.,
x < y <= z is equivalent to
x < y and y <= z, except that y is
evaluated only once (but in both cases short-circuiting applies).
Note that a op1 b op2 c doesn’t imply any kind of
comparison between a and c, so that, e.g.,
x < y > z is perfectly legal.
if A is None compares the reference A with
None. Favour if A not None vs
if A.
type(None) # <class 'NoneType'>
Integer Bitwise Operators
x << y # left shift
x >> y # right shift
x & y # and
x | y # or
x ^ y # xor
~ x # complement, same as -x - 1. Built-ins
divmod(a, b) # (a // b, a % b); only fast if a>2^64
pow(base, exp[, mod]) # pow(base, exp) % modFinite ordered sets, indexed by non-negative integers. Support
slicing, len etc.
An object supports the sequence protocol if it has a
__getitem__() method with integer arguments starting at
0.
An immutable sequence of values that represent Unicode code points.
str()
len()
ord('c') # gives the ascii value of a char/str
chr(asciiCode) # the char associated with an ascii code
s.replace("x","why", count=-1) # replaces all occurrences if count=-1 (default)
s.startswith("begin")
s.endswith("end")
s.find("this" [, start[, end]]) # -1 if not found, else index
s.index("this") # ValueError if not found
s.rfind("this" [, start[, end]]) # -1 if not found, else index
s.count("this")
s.partition("this") # "what this is" -> ("what ", "this", " is")
" eg... ".strip(" .,") # removes specified chars. Default ' '.
s.lstrip()
s.rstrip()
table = str.maketrans(frm, to, remove) # "xy." -> "yxz"
s.translate(table) # does it char by char
# case
s.upper()
s.lower()
s.title()
s.casefold()
s.swapcase()
# iterables
s.join(iterable)
s.split(sep=None,maxsplit=-1) # -> List
s.splitlines() # -> List
# classification
s.isdigit() # e.g. s = "1234"
s.isalpha()
# formatting
s.center(width=?[,fillchar=" "])Features:
t = ("a", "b", "c")
a, b, c = t
empty = ()
singleton = ("a",)
n = tuple(iterable)
(1,2) + (3,4) == (1, 2, 3, 4)Features:
ls = [1,2]
ls.append(3) # [1,2,3]
ls.append([3,4]) # [1,2,[3,4]]
ls.extend(3) # [1,2,3]
ls.extend([3,4]) # [1,2,3,4]
# note extend will try do this for any iterable!
ls += [3,4] # same as extend (but not in-place)
ls = [5] * 100 # creates a list of 100 5s. Can use any immutable element.
x in ls # -> bool
ls.count(x)
ls.insert(index, x) # shifts other elements to the right
idx = ls.index(x [, start, end]) # index of first matching instance (/throws ValueError)
ls.remove(x) # removes the element's first instance (/throws ValueError)
ls.sort(reverse=True|False, key=myFunc) # in-place
ls.reverse() # in-place
x = ls.pop(index=None) # removes from the given index (/returns the rightmost element)
y = x.copy()
# comprehension
ls = [i for i in iterable]
# nested list comprehensions follow the structure of the nested loop
matrix = [[-1,2], [-3,4]]
positives = [el
for vec in matrix
for el in vec
if el > 0]This construct is used extensively in the Python internals eg. class attributes and variables in a stack frame.
Preserve insertion order (>= v3.7).
d = {} # init an empty dict
d = {'a': 1, 'b': 2}
d = dict([["a", 1], ["b", 2]])
d = dict(a = 1, b = 2)
d['a'] # access a value with a key
d['b'] = 3 # modify
d['c'] # KeyError
d['c'] = 4 # add
d.get('z') # returns None if key DNE
d.get('z', 0) # returns 0 if key DNE
d.pop('a') # removes key, returns value
d.update({'a': 'I', 'c': 3}) # {'a': 'I', 'b': 2, 'c': 3}
len(d) # number of keys in d
'a' in d # test if key exists in d
d.items() # returns (key, value) iterator `ItemsView`
d.keys() # returns keys iterator `KeysView`
d.values() # returns values iterator
# comprehension
d = {k: v for k, v in iterable}
d1 == d2 # True, if both have the same items (order indep; deep comparison)
d1.keys() ^ d2.keys() # symmetric difference also works for 'Views'A set is an unordered “bag” of unique values. A single set can contain values of any immutable datatype. Once you have two sets, you can do standard set operations like union, intersection, and set difference. Implemented as a class.
s = {1, 2}
s = set(['a', 'hi', True, 42]) # constructor
# s will be unordered
empty = set()
s.add(3)
s.update(iterable) # adds each element of iterable to set
s.discard(6)
s.remove(5) # raises `KeyError` exception if the value doesn't exist
s.pop() # removes and returns one value
s.clear() # empties the set
# these return a new set
s.union(t) # s.union(*x) if x is iterable of sets
s | t
s.intersection(t)
s & t
s.difference(t) # a minus whatever's in b
s - t
s.symmetric_difference(t) # returns elements in exactly one of the sets
s ^ t
s.isdisjoint(t)
s.issubset(t)
s <= t
# s is proper subset of t (s <=t and s != t)
s < t
t.issuperset(s)
s >= t
# with update
s |= t # update
s &= t # intersection_update
s -= t # difference_update
s ^= t # symmetric_difference_update
# comprehension
s = {v for v in iterable}Immutable sets. Can be used as keys to a dict (i.e. is hashable).
fset = frozenset()An iterator type has the following methods by protocol.
__iter__() # returns self
__next__() # next item of StopIteration exceptionA collection is iterable if its __iter__ returns an
iterator object i.e. supports iteration protocol.
* iterable unpacking** dictionary unpackingdef f(a, b): return a + b
it = [1, 2]
f(*it) # 3
it2 = [3, 4]
it3 = [*it, *it2, 4] # [1, 2, 3, 4, 5]
a, *b, c = it3 # a = 1, c = 5, b = [2, 3, 4]
c = [*"hi"] # ['h', 'i']
head, *tail = [1, 2, 3]__doc____name____code____self____func__def func(x, *args, **kwargs):
# x is a required param
# args is a tuple of args
# kwargs is a dict of named argssum(iterable, start)
filter(function: x -> bool, iterator) -> iterator
map(function, iterator, ...) -> iterator
reversed(seq) -> iterator
sorted(iterable,key=None,reverse=False) # returns a new stable sorted list
iter(iterable), iter(sequence), iter(callable, sentinel) # returns an iterator
max(iterable, key=callable) # returns the max item in iterable ordered by key
callable(object)
isinstance(object)
type(object|name)
# flush will clear the buffer, similar to sys.stdout.flush()
print(arg1, arg2, sep=' ', end='\n', flush=False, file=f)
# interactive
help(string or object)
vars(object) # object.__dict__
dir(object)
getattr(object, "name") # hasattr, delattr
globals() # returns the dictionary (`__dict__`) of the module namespace
locals() # returns a dictionary (`__dict__`) of the current namespaceFor floats
f'{value:{width}.{precision}f}'
def infinite_sequence():
num = 0
while True:
yield num
num += 1
for n in infinite_sequence:
pass
# OR
seq = infinite_sequence()
n = next(seq)
from itertools import count
c = count()
next(c) == 0
next(c) == 1
# comprehension
g = (i for i in iterable) # returns a generator object
# Instead of using
for x in generator: yield x
# rather use `yield from` like in below.
def flatten(seq):
# e.g. flatten([1, [2], [3, [4]]]) -> [1, 2, 3, 4]
for el in seq:
if hasattr(el, '__iter__'):
yield from flatten(el)
else:
yield elEasy way to add functionality to a function
def double(func):
def decorated_func(*args, **kwargs):
return 2 * func(*args, **kwargs)
return decorated_func
@double
def square(x):
return x*x
# equiv to
# square = double(square)
square(3) # -> 18Can store variables.
def f(*args):
f.level = 0
# ...
f.level += 1class ClassName(BaseClass): # inherits from `object` by default
"""docstring for ClassName"""
def __init__(self, arg):
super(ClassName, self).__init__()
# also `super().__init__(*args)`
self._x = arg
def instancemethod(self, arg):
# can modify object state
return func(self._x, arg)
@classmethod
def classmethod(cls):
# can modify class state
return 'class method called', cls
@staticmethod
def staticmethod(arg):
# called on class or on instance
return func(arg)
@property # getter
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._xCPython stores classes as dicts. Ensure that all attributes are
initialized (even if to None) in the constructor. This
allows the runtime to use key-sharing (PEP 412): we store a reference to
the key array, and create a new array for the values.
All the variables in an instance of a class can be seen by using
vars(instance).
To simulate immutability in a class, one should override attribute setting and deletion to raise exceptions.
Classes usually have a __dict__ attribute. This dynamic
dict allows adding attributes to objects at anytime The special
attribute __slots__ allows you to explicitly state which
instance attributes you expect your object instances to have. This is a
static structure which does not allow additions after creation
Use cases:
__dict__ a new attribute is
createdThe space savings is from
__dict__.__dict__ and __weakref__ creation
if parent classes deny them and you declare __slots__.class Test:
__slots__ = ['a']
def __init__(self, a = 1):
self.a = aasync def asleep(n):
...
async for data in stream:
...
await asleep(1);Since you can’t use await inside the body of a regular function, one has to either make the whole program async, or use an async library that provides a sync method that runs async methods.
await,
async with and async for inside asynchronous
code.async def
declaration (or one other place we’ll cover in the next article)__await__ magic method.
What happens when it is awaited is defined by that method.https://bbc.github.io/cloudfit-public-docs/asyncio/asyncio-part-2
The with statement simplifies exception handling by
encapsulating common setup and tear-down tasks in so-called context
managers.
Modes
r+: read and write, though the stream will be at the
end of file after read so any writes will appendrb: read file as binarywith open(filepath, mode='r', encoding="utf-8") as file:
'''r,w (overwrite),rb,wb '''
file.read(size=-1) # -> str; bytes, default whole file
# returns '' at EOF
file.readline(size=-1) # -> str; characters, default whole line
file.readlines() # -> list(str); reads remaining lines, has '\n' in each line
lines = [x.rstrip() for x in file]
for line in file:
print(line, end='')
with open(outfile, 'w') as out:
out.write(string)
out.writelines(stringlist) # doesn't add '\n', use str.join()Exception attributes:
__cause__: set if used with from. Shown as
“this exception was the direct cause of the following exception”__context__: set if raised in an exception handler.
Shown as “during handling another exception occurred”.__suppress_context__: set if the cause is set. The
context is ignored when printing the traceback.__traceback__:try:
# something
except (RuntimeError, TypeError):
# something else
except ValueError:
# will say that ValueError occurred
raise
except ValueError:
# will say that while handling ValueError, IndexError occurred
raise IndexError("bad")
except ValueError as err:
# will say ValueError caused IndexError
raise IndexError("bad") from err
except ValueError as err:
# will say that IndexError occurred
raise IndexError("bad") from None
except NameError as err:
print(err.args) # args to the exception instance
except:
# default exception clause
else:
# runs if `try` does not produce an exception
finally:
# runs whether or not `try` produces an exceptionraise ValueError # using default ctor
raise NameError("The name doesn't exist")
err = OSError("This happened.")
raise err
Exception: base for all non-system-exit built-in and
user-defined exceptionsArithmeticError: OverflowError,
ZeroDivisionError, FloatingPointErrorLookupError: IndexError,
KeyErrorOSError: FileNotFoundError,
PermissionError, …Warning# When creating a module that can raise several distinct errors,
# it is common practice to create a base class for its exceptions,
# and subclass that to create specific exception classes
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
# expression -- input expression in which the error occurred
def __init__(self, expression, message):
self.expression = expression
self.message = messaged1
-- f1.py
-- f2.py
d2
-- f3.py
To use f1 in f2, use
import f1.
A block is a piece of code executed as a unit in an execution frame. Examples:
Name binding, or assignment occurs in a block. Examples:
with ... as f : f is the
name binding to the ... objectNames bound to a block are local to that block. So global variables are simply names bound to the module.
Scopes define visibility of a name in a block. The scope of a variable includes the block it is defined in, as well as all blocks contained inside the defining block.
x = "x"
y = "y"
def fn():
global x
x = "changes x"
y = "new y"python -m compileall script.py # syntax check w/o running file
https://sadh.life/post/builtins/#hash-and-id-the-equality-fundamentals