Skip to content

Python Notes

Basics

Naming Convention

Class -> PascalCase
Method, Function -> snake_case
Variable -> snake_case

Python
# standard comment
'''multiline comment'''
"""DOCSTRING"""

help(object.method) # return method explanation
dir(object) # return an alphabetized list of names comprising (some of) the attributes of the given object

import sys # import module
from sys import argv # import single item from a module
from sys import * # import all elements of a module (no module syntax.method needed)
import sys as alias # import the module with an alias, I use alias.method

# CHARACTER SET
import string
string.ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
string.asci_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
string.asci_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
string.digits = '0123456789'
string.hexdigits = '0123456789abcdefABCDEF'
string.octdigits = '01234567'
string.punctuation
string.whitespace

# SPECIAL CHARACTERS
# (\a, \b, \f, \n, \r, \t, \u, \U, \v, \x, \\)

Assignment Operation

Python
1
2
3
4
5
6
7
8
9
"""instructions to the right of = executed before instructions to the left of ="""
variable = expression # the type of the variable is dynamically decided by python based on the content
var_1, var_2 = value1, value2 # parallel assignment
var_1, var_2 = var_2, var_1 # swap values

# conditional assignment
x = a if condition else b
x = a or b # If bool (a) returns False, then x is assigned the value of b
# a series of OR expressions has the effect of returning the first item that evaluates True, or the last item (last item should be a literal).

Variable Type Conversion

type(expression)

Expression Assignment

Python
(var: = expression) # assign an expression to a variable to avoid repeating the expression

Variable Comparison (== vs is)

== compares the values ​​of objects is compares the identities of objects

On Screen Output

Python
print() # print blank line and wrap
print('string' * n) # print string n times
print('string1 \ n string2') # wrap with \ n
print(variable) # print variable content
print('string', end = '') # print without wrapping

# FORMATTING
name = 'Alex'
marks = 94.5
print(name, marks)
print('Name is', name, '\ nMarks are', marks)
# expand the rest of the expression and write tense before = in output
print(f '{name =}, {marks =}') # OUTPUT: name = Alex, marks = 94.5

# USE OF PLACEHOLDERS
print('Name is% s, Marks are% 3.2f'%(name, marks)) # method inherited from C. Variable is substituted for% ..
print("Name is {}, Marks are {}". format(name, marks))
print("Name is {1}, Marks are {2}". format(marks, name)) # indices in brackets sort elements in .format
print("Name is {n}, Marks are {m}". format(m = '94 .5 ', n =' Alex ')) # indices in brackets sort elements in .format
print(f'Name is {name}, Marks are {marks} ') # formatting with f-strings

Format Specification Mini-Language

{value:width.precision symbol}

Format: [[fill]align] [sign] [#] [width] [grouping] [.precision] [type]

[align] Alignment
:< left alignment
:> right alignment
:= padding after the mark
:^ centered
[sign] NUMBER SIGNS
:+ sign for both positive and negative numbers
:- sign only for negative numbers
: space for num > 0, '-' for num < 0
:# alternative form:prefix integers type (0x, 0b, 0o), floats and complexes always have at least one decimal place
[grouping] GROUPING
:, use comma to separate thousands
:_ use underscore to separate thousands
[type] OUTPUT TYPE
:s output is string
:b output is binary
:c output is character
:d output is a decimal integer (base 10)
:or output is octal integer (base 8)
:x output is hexadecimal integer (base 16)
:X output is hexadecimal integer (base 16) with uppercase
:e output is exponential notation (6-digit base precision)
:E output is exponential notation (6-digit base precision) uppercase separator
:f output is float (6-digit base precision)
:% output is percentage (multiplies * 100, displays as:f)

Keyboard Input

Python
1
2
3
4
5
6
7
8
# input always returns a STRING
s = input() # input request without message
s = input('Prompt') # request input
i = int(input('prompt')) # request input with type conversion

# MULTIPLE INPUTS
list = [int(x) for x in input('prompt'). split('separator')]
# save multiple inputs in a list(.split separates values ​​and defines separator

Numeric Types

Python
a = 77
b = 1_000_000 # underscore can be used to separate groups of digits
c = -69

# float numbers
x = 3.15
y = 2.71
z = 25.0

d = 6 + 9j # complex number
# returns a complex number starting with two reals
complex(real, imag) # -> complex #(real + imag * 1j)

e = 0B1101 # BINARY TYPE(0B ...)
f = 0xFF # EXADECIMAL TYPE(0X ...)
o = 0o77 # OCTAL TYPE
g = True # BOOLEAN TYPE

# VARIABLE TYPE CONVERSION
h = int(y)
i = float('22 .5 ')

# NUMERIC BASIC CONVERSION
bin(3616544)
hex(589)
oct(265846)

# UNICODE CONVERSION
ord(c) # Given a string representing one Unicode character, return an integer representing the Unicode code point of that character
chr(i) # Return the string representing a character whose Unicode code point is the integer i


pow(x, y) # x ^ y
abs(num) # returns absolute value of num(| num |)
round(num, precision) # rounds number to given precision, does not convert float to int

Comparison of Decimal Numbers

Do not use == or ! = To compare floating point numbers. They are approximations or have several digits. It is worth checking if the difference between the numbers is small enough.

Strings

Python
string = 'string content' # assignment and creation of string variable
string = '''multi
line
string'''

string3 = string1 + string2 # string concatenation(operator polymorphism +)

# INDEXING(selection of a character in the string)
string[0]
string[2]
string[-3] # selection starting from the bottom(negative index)

# REPETITION (repeat string output)
print(string * n)

len(string) # show the length of a string

# SLICING (extraction of sub-strings, does not include the position of the last index)
string[0: 5]
string[: 6]
string[-3: -1]

# SLICING WITH STEP
string[0: 12: 3]
string[15 :: - 1]
string[:: - 1] # selection in reverse order (negative step)

# STRIPPING (elimination of spaces before and after string)
string = 'stripping test'
string.strip()
string.lstrip() # only left spaces removed
string.rstrip() # only right spaces removed
string.removeprefix(prefix) # If the string starts with the prefix string, return string [len (prefix):]
string.removesuffix(suffix) # If the string ends with the suffix string and that suffix is ​​not empty, return string [: - len (suffix)]

# SUBSTRING IDENTIFICATION
#returns starting index of the substring or -1 if it is not present
string.find('substring', 0, len (string)) # you can specify the start and end index of the search

# COUNT OF APPARITIONS
string.count('t')

# REPLACEMENT
string.replace('multi', 'multiple')

# UPPER CASE CONVERSION
string.upper()
string.lower()
string.title()
string.capitalize()

# SEPARATION IN LIST ELEMENTS
string.split()
string.split('separator') # separate using separator (separator omitted in list)
string.partition('char') # -> tuple # separates the string from the 3 parts at the first occurrence of separator

# IS_CHECK METHODS -> bool
string.isalnum()
string.isalpha()
string.islower()
string.isspace()
string.istitle()
string.isupper()
string.endswith('char')

# JOIN INSTRUCTION()
''.join(iterable) # merges all elements of the iterable into the new string

# FORMATTING
string.center(width, 'char') # stretch the string with char to width
'...\t...'.expandtabs() # transform tabs into spaces

Lists

Python
list = [9, 11, 'WTC', -5.6, True] # lists can contain data of different types

list[3] # indexing
list[3: 5] # slicing
list * 3 # repetition
len(list) # length
list3 = list1 + list2 # list concatenation (operator + polymorphism)
list[index] = value # modify list element
del (list [1]) # remove by index (INBUILT IN PYTHON)
# modify the list between the start and stop indices by reassigning the elements of the iterable
list[start: stop] = iterable

# LIST METHODS
list.append(object) # add object to background
list.count(item) # counts the number of occurrences of item
list.extend(sequence) # add sequence elements to the list
list.insert(position, object) # insert object in list [position]
list.index(item) # returns the index of item
list.remove(item) # remove item
poplist(item) # delete item and return it
list.clear() # remove all elements

list.sort() # sorts in ascending order (in place)
list.sort(reverse = True) # sorts in descending order (in place)
list.reverse() # invert the string (in place)

# CLONING
list1 = [...]
list2 = list1 # list2 points to the same object of list 1 (changes are shared)
list3 = list1 [:] # list3 is a clone of list1 (no shared changes)

# NESTED LISTS (MATRICES)
list_1 = [1, 2, 3]
list_2 = [4, 5, 6]
list_3 = [7, 8, 9]

matrix = [list_1, list_2, list_3]
matrix [i][j] # identify element of list_i index j

# MAXIMUM AND MINIMUM
max(list)
min(list)

# ALL () & ANY ()
all(sequence) # returns TRUE if all elements of the sequence are true
any(sequence) # returns TRUE if at least one element of the sequence has the value True

# MAP INSTRUCTION
# apply function to iterable and create new list (map object)
# function can be lambda
map(function, iterable) # -> map object

# FILTER INSTRUCTION ()
# create a new list composed of the iterable elements for which the function returns TRUE
filter(function, iterable) # -> filter object

# ZIP INSTRUCTION ()
# create a tuple generator by joining two or more iterables
# [(seq_1 [0], seq_2 [0], ...), (seq_1 [1], seq_2 [1], ...), ...]
# truncate the sequence to the length of the shortest input sequence
zip(seq_1, seq_2, ...) # -> zip object (tuple generator)

# LIST COMPREHENSIONS
var = [expression for element in sequence if condition] # create list from pre-existing list (instead of map, filter, reduce) applying any manipulations
# expression can be lambda, if is optional
var = [expression if condition else statement for element in sequence] # list comprehension with IF-ELSE
var = [expression_1 for element in [expression_2 for element in sequence]] # nested list comprehension
var = [(exp_1, exp_2) for item_1 in seq_1 for item_2 in seq_2] # -> [(..., ...), (..., ...), ...]

Tuple

Python
# TUPLES CANNOT BE MODIFIED
tuple = (69, 420, 69, 'abc') # tuple assignment
tuple = (44,) # single element tuples need a comma

tuple[3] # indexing
tuple * 3 # repetition
tuple.count(69) # counting
tuple.index(420) # find index
len(tuple) # length tuple

# CONVERSION FROM TUPLE TO LIST
tuple = tuple(list)

# TUPLE UNPACKING
tup = (item_1, item_2, etc)
var_1, var_2, etc = tup
# var_1 = item_1, var_2 = item_2, ...

tup = (item_1, (item_2, item_3))
var_1, (var_2, var_3) = tup
# var_1 = item_1, var_2 = item_2, var_3 = item_3

#OPERATOR * VAR (tuple unpacking)
var_1, var_2, * rest = sequence # var_1 = seq [0], var_2 = seq [1], rest = seq [2:]
var_1, * body, var_2, var_3 = sequence # var_1 = seq [0], body = seq [1: -2], var_2 = sequence [-2], var_3 = seq [-1]
# * var retrieves the excess items, if in parallel assignment usable max once but in any position

Set

Python
# SETS MAY NOT CONTAIN REPEATED ELEMENTS (THEY ARE OMITTED)
# THE ORDER DOES NOT MATTER (NO SLICING, INDEXING, REPETITION, ...)
set = {10, 20, 30, 'abc', 20}
len(set) # length set
set() # create empty set ({} create empty dictionary)
# FREEZING SETS (no longer editable)
fset = frozenset(set)

# OPERATORS
set_1 - set_2 # elements in set_1 but not in set_2
set_1 | set_2 # elements in set_1 or set_2
set_1 & set_2 # elements in set_1 and set_2
set_1 ^ set_1 # elements in either set_1 or set_2
set_1 <= set_2 # elements set_1 also in set_2
set_1 < set_2 # set_1 <= set_2 and set_1! = set_2
set_1 >= set_2 # elements set_2 also in set_1
set_1 > set_2 # set_1> = set_2 and set_1! = set_2

# METHODS SET
set.pop(item) # remove and return item
set.add(item) # add item to set

set.copy() # -> set # returns a copy of the set
set.clear() # remove all elements from the set
set.remove(item) # remove item from set if present, otherwise raise KeyError
set.discard(item) # remove item from set if present, otherwise do nothing
set.difference(* sets) # -> set # returns elements in set that are absent in * sets
set.difference_update(* sets) # remove differences from set_2
set.union(* sets) # -> set # returns all elements of sets
set.update(* sets) # add * sets elements to set
set.intersection(* sets) # -> set # returns the elements common to sets
set.intersection_update(* sets) # remove all elements except those common to sets
set.symmetric_difference(* sets) # -> set # returns elements not common to sets
set.symmetric_difference_update(* sets) # remove all elements common to sets (leave only uncommon elements)

set_1.isdisjoint(set_2) # -> bool # True if there are no common elements (intersection is empty)
set_1.issubset(set_2) # -> bool # True if every element of set_1 is also in set_2
set_1.issuperset(set_2) # -> bool # True if every element of set_2 is also in set_1

# SET COMPREHENSIONS
var = {expression for element in sequence if condition}

# SLICE OBJECT
# [start: stop: step] -> slice object (start, stop, step)
var_1 = slice(start, stop, step) # assignment to variable
var_2[var_1] # same as var_2 [start: stop: step]

# ELLIPSIS OBJECT
var[i, ...] # -> shortcut for var [i,:,:,:,]
# used for multidimensional slices (NumPy, ...)

Bytes e Bytearray

Python
# THE BYTES CANNOT BE MODIFIED OR INDEXED
# THE BYTEARRAYS CAN BE MODIFIED AND INDEXED
# YOU CANNOT DO REPETITION AND SLICING ON BYTE OR BYTEARRAY

b = bytes(list)
ba = bytearray(list)

# item of bytes and bytearray is always integer between 0 and 255
# slice of bytes and bytearray is binary sequence (even if len = 1)

# BYTES AND BYTEARRAY METHODS
bytes.fromhex(pair_hex_digits) # -> byte literal
b'bite_literal'.hex() # -> str # returns a string containing hex digit pairs
bytearray.fromhex(pair_hex_digits) # -> byte literal
bytes.count(subseq, start, end) # returns subseq appearance count between start and end positions
bytearray.count(subseq, start, end) # returns subseq appearance count between start and end positions

Encoding-Decoding & Unicode

Unicode Literals:

  • \u0041 → 'A'
  • \U00000041 → 'A'
  • \x41 → 'A'
Python
# ENCODING
# transform string into literal byte
# UnicodeEncodeError on error
# errors = ignore -> skip error-causing characters
# errors = replace -> replace? to characters causing error
# errors = xmlcharrefreplace -> substitutes XML entities for error-causing characters
string.encode('utf-8', errors = 'replace') # -> b'byte literals'

# BOM (BYTE ORDER MARK)
# byte literal given to indicate byte ordering (little-endian vs big-endian)
# in little-endian the least significant bytes come first (e.g. U + 0045 -> DEC 069 -> encoded as 69 and 0)
# U + FEFF (ZERO WIDTH NO-BREAK SPACE) -> b '\ xff \ xfe' indicates little-endian

# DECODING
# transform byte literal to string
# error = 'replace' replaces errors (byte literals not belonging to decoding format) with U + FFFD "REPLACEMENT CHARACTER"
bytes.decode ('utf-8', errors = 'replace') # -> str

# UNICODE NORMALIZATION
# handling canonical unicode equivalents (e.g. é, and \ u0301 are equivalent for unicode)
unicodedata.normalize(form, unicode_string) # FORM: NFC, NFD, NFCK, NFDK
# NFC -> "Normalization Form C" -> produces the shortest equivalent string
# NFD -> "Normalization Form D" -> produces the longest equivalent string

# CASE FOLDING UNICODE
# transform to lowercase with some differences (116 differences, 0.11% of Unicode 6.3)
string.casefold()

# USEFUL FUNCTIONS FOR NORMALIZED EQUIVALENCE (Source: Fluent Python p. 121, Luciano Ramalho)
from unicodedata import normalize

def nfc_eual(str_1, str_2):
    return (normalize('NFC', str1) == normalize('NFC', str2))
def fold_equal (str_1, str_2):
    return (normalize('NFC', str_1).casefold() ==
            normalize('NFC', st_2).casefold())

Memoryview

Python
# memoryview objects allow python to access the data inside the object
# without copy if it supports the buffer protocol
v = memoryview(object) # create a memoryview with reference to object
# slice of memoryview produces new memoryview

# MEMORYVIEW METHODS
v.tobytes() # return data as bytestring, equivalent to bytes (v)
v.hex() # returns string containing two hex digits for each byte in the buffer
v.tolist() # returns the data in the buffer as a list of elements
v.toreadonly()
v.release() # release the buffer below
v.cast(format, shape) # change the format or shape of the memoryview
see object # object of the memoryview
v.format # format of the memoryview
v.itemsize # size in bytes of each element of the memoryview
v.ndim # integer indicating the size of the multidimensional array represented
v.shape # tuple of integers indicating the shape of the memoryview
Format String C Type Python Type Standard Size
x pad byte no value
c char bytes 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
n ssize_t integer
N size_t integer
f float float 4
F double float 8
s char[] bytes
P char[] bytes

Dictionaries

Python
# SET OF KEY-VALUE PAIRS
d = {1: 'Alex', 2: 'Bob', 3: 'Carl'}
d = dict (one = 'Alex', two = 'Bob', three = 'Carl')
d = dict (zip ([1,2,3], ['Alex', 'Bob', 'Carl']))
d = dict ([(1, 'Alex'), (2, 'Bob'), (3, 'Carl')])

d[key] # returns value associated with key
d[4] = 'Dan' # add or change element
list(d) # returns a list of all elements
len(d) # returns the number of elements
del(d[2]) # delete element

# DICTIONARY METHODS
d.clear() # remove all elements
d.copy() # shallow copy of the dictionary
d.get(key) # returns the value associated with key
d.items() # return key-value pairs (view object)
d.keys() # return dictionary keys (view object)
d.values​​() # returns dictionary values ​​(view object)
d.pop(key) # remove and return the value associated with key
d.popitem() # remove and return the last key-value pair
d.setdefault(key, default) # if the key is present in the dictionary it returns it, otherwise it inserts it with the default value and returns default

d.update(iterable) # add or modify dictionary elements, argument must be key-value pair

# DICT UNION
d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}

d | e # {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
e | d # {'aardvark': 'Ethel', 'spam': 1, 'eggs': 2, 'cheese': 3}
d |= e # {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

# NESTED DICTIONARIES (it is possible to nest dictionaries within dictionaries)
my_dict = {'key_1': 123, 'key_2': [12, 23, 33], 'key_3': ['item_0', 'item_1', 'item_2']}
my_dict ['key'][0] # returns nested element

# DICT COMPREHENSIONS
var = {key: value for element in sequence}

Operators

Mathematical Operators

Operator Operation
x + y addition, string concatenation
x - y subtraction
x * y multiplication
x *+ y exponentiation
x / y division (result always float)
x // y integer division
x % y modulo, remainder

Relational Operators

Operator Operation
x < y less than
x <= y less or equal to
x > y greater than
x >= y greater or equal to
x == y equality
x != y inequality

Assignment

Operator Operation
x += y x = x + y
x -= y x = x - y
x *= y x = x * y
x /= y x = x / y
x //= y x = x // y
x %= y x = x % y
x <<= y x = x << y
x >>= y x = x >> y
x &= y x = x & y
x | = y x = x
x ^= y x = x ^ y

Bitwise Operators

Operator Operation
~x bitwise NOT
x & y bitwise AND
x ^ y bitwise XOR
x | y bitwise OR
x << y left bit shift
x >> y right bit shift

Logical Operators

Operator Operation
and logical AND
or logical OR
not logical NOT

Identity Operators

Operator Operation
is reference equality
is not reference inequality

Membership Operators

Operator Operation
in item in collection
not in item not in collection

OPerator Precedence

  1. assignment operators +=, -=, *=, /=, %=, **=, //=
  2. binary arithmetic operators *, /, %, // (floor division)
  3. binary arithmetic operators +, -
  4. boolean operators <, >, <=, >=
  5. boolean operators ==, !=
  6. boolean operator and
  7. boolean operator or
  8. boolean operator not

Conditional Statements

Any object can be tested for truth value for use in an if or while condition or as operand of the Boolean operations.

built-in objects considered false:

  • constants defined to be false: None and False.
  • zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • empty sequences and collections: '', (), [], {}, set(), range(0)

if-else

Python
1
2
3
4
5
6
if (condition):
    # code here
elif (condition):
    # code here
else:
    # code here

Context Manager

Python
with resource as target:
     # code here

# start context manager and bind resource returned by method to target using as operator
contextmanager.__enter__(self)

# exit runtime context
# returns exc_type, exc_value, traceback
contextmanager.__exit__(self, exc_type, exc_value, traceback)
# exc_type: exception class
# exc_value: exception instance
# traceback: traceback object
# NO EXCEPTION -> returns None, None, None
# SUPPRESSION EXCEPTION: Must return True value

Loops

while

Python
1
2
3
4
5
6
while(condition):
     # code here
else:
     # executed only if condition becomes False
     # break, continue, return in block while do not perform else block
     # code here

for

Python
for index in sequence: # sequence can be a list, set, tuple, etc ..
     # code here
else:
     # executed only if for reaches the end of the loop
     # break, continue, return in block for do not perform else block
     # code here

for index in range (start, end, step):
     # code here

for key, value in dict.items ():
     # code here

break & continue

break: causes the loop to exit immediately without executing subsequent iterations continue: skip the remaining iteration statements and continue the loop

range

Python
range(start, end, step) # generate sequence num integers (does not include num stops) with possible step
list(range(start, end, step)) # return sequence of integers in a list

enumerate

Python
enumerate(iterable) # iterable of item & index pairs
list(enumerate(iterable)) # returns list of tuples [(1, iterable [0]), (2, iterable [1]), (3, iterable [2])]

zip

Python
1
2
3
4
5
list_1 = [1, 2, 3, 4, 5]
list_2 = ['a', 'b', 'c', 'd', 'e']

zip(list_1, list_2) # return zip object
list(zip(list_1, list_2)) # returns list of tuples by merging list [(list_1 [0], list_2 [0]), (list_1 [1], list_2 [1]), ...]

shuffle & randint

Python
1
2
3
from random import shuffle, randint
shuffle(iterable) # shuffle the list
randint(start, end) # returns a random integer between start and end

in

Python
item in iterable # check for the presence of item in iterable (returns True or False)

Functions

Function Definition

Python
1
2
3
4
def function_name (parameters):
     "" "DOCSTRING" ""
     # code here
     return expression # if return id missing the function returns None

Specify Type Parameters In Functions

  • parameters before / can only be positional
  • parameters between / and * can be positional or keyworded
  • parameters after * can only be keyworded
Python
def func (a, b, /, c, d, *, e, f):
     # code here

Docstring Style

Python
"""function description

Args:
     argument: Type - description of the parameter

Returns:
     Type - description of <expr>

Raises:
     Exception: Cause of the exception
"""

args *kwargs

*args allows the function to accept a variable number of parameters (parameters stored in a tuple) **kwargs allows the function to accept a variable number of key-value parameters (parameters stored in a dictionary)

When used in combination *args always goes before**kwargs (in def function and in function call)

Python
def func(*args, **kwargs):
    # code here

Function with default parameters

Python
1
2
3
4
5
def function(parameter1 = value1, parameter2 = value3): # default values in case of omitted use of arguments in the call
     # code here
     return expression

function(parameter2 = value2, parameter1 = value1) # arguments passed with keyword to enforce the order of reference

Global And Local Variables

Python
1
2
3
4
5
6
7
# global scope

def external_func():
     # enclosing local scope

     def internal_func():
         # local scope

LEGB Rule:

  • L - Local: Names assigned in any way within a function (def or lambda), and not declared global in that function.
  • E - Enclosing function locals: Names in the local scope of any and all enclosing functions (def or lambda), from inner to outer.
  • G - Global (module): Names assigned at the top-level of a module file, or declared global in a def within the file.
  • B - Built-in (Python): Names preassigned in the built-in names module : open, range, SyntaxError,...

Note: variables declared inside a function are not usable outside

Python
1
2
3
4
5
def function():
     # global statement makes a variable global
     # actions on global variable within the function also have an effect outside

     global variable

Iterables, Iterators & Generators

Iterable: object implementing __iter __(), sequences and objects supporting __getitem__ with index 0

Iterator: object implementing __next__ and __iter__ (iterator protocol), when entirely consumed by next() it becomes unusable. Returns StopIteration when next() has returned all elements.

Generator Function: function with keyword yield (if present also return causes StopIteration), returns a generator that produces the values ​​one at a time.

Generator Factory: generator returning function (may not contain yield).

Operation iter():

  • calls __iter__()
  • in the absence of it python uses __getitem__() (if present) to create an iterator that tries to retrieve the items in order, starting from the index 0
  • on failure it returns TypeError

Note: abc.Iterable does not check for the presence of __getitem__ to decide if a sub-object is a member therefore the best test for iterability is to use iter() and handle exceptions.

next() & iter()

Python
1
2
3
4
5
6
next(iterable) # next item of the iterable or error StopIteration

iter(object) # get an iterator from an object
# call callable_onj.next () with no arguments as long as it returns non-sentinel values

iter(callable_obj, sentinel)

Customs Generators

Used to generate a sequence of values to be used once (they are not stored)

Python
1
2
3
4
5
6
7
def custom_generator(parameters):
     while condition: # or for loop
         yield variable # returns the value without terminating the function, values passed to the caller without storing in a variable

# generator implementation
for item in custom_generator(parameters):
     # code here

Termination Generator And Exception Handling

Python
1
2
3
4
5
6
7
8
9
# raise exception at the suspension point and return generator value
# if the generator terminates without returning values it raises StopIteration
# if an exception is not handled it is propagated to the caller
generator.throw(ExceptionType, exception_value, traceback)

# raises GeneratorExit to the point of suspension
# if generator returns a value -> RuntimeError
# if an exception is raised it propagates to the caller
generator.close()

Generator Comprehensions

Python
1
2
3
4
5
6
# zero-length sequence (instantaneously generated values)
var = (for expression iterable in sequence if condition)
# EDUCATION ENUMERATE ()
# returns a list of tuples associating a position index to each element of the sequence
# [(0, sequence [0]), (1, sequence [1]), (2, sequence [2]), ...)
enumerate(sequence) # -> enumerate object

Coroutines

Python
def simple_coroutine():
    """coroutine defined as a generator: yield in block"""

    # yield in expression to receive data
    # returns None (no variables on the right of yield)
    var = yield value # returns value and then suspends coroutine waiting for input
    # instructions to the right of = executed before instructions to the left of =

gen_obj = simple_coroutine() # returns generator object
next(gen_obj) # start coroutine (PRIMING)
gen_obj.send(None) # start coroutine (PRIMING)
gen_obj.send(value) # send value to the coroutine (only possible in suspended state)

# STATES OF COROUTINE
inspect.generatorstate() # returns the status of the coroutine
# GEN_CREATED: waiting to start execution
# GEN_RUNNING: currently run by the interpreter (visible if multithreaded)
# GEN_SUSPENDED: currently suspended by yield statement
# GEN_CLOSED: execution completed successfully

# COROUTINE PRIMING
from functools import wraps

def coroutine(func):
    "Decorator: primes 'func' by advancing to first 'yield'"

    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer

# COROUTINE TERMINATION AND EXCEPTION HANDLING
# exceptions in unhandled coroutines propagate to subsequent iterations
# an exception causes the coroutine to terminate which it cannot resume

# yield raises exception, if handled loop continues
# throw() returns value of the generator
coroutine.throw(exc_type, exc_value, traceback)

# yield raises GeneratorExit to the suspension point
# if the generator yields a value -> RuntimeError
# if there are other exceptions they are propagated to the caller
coroutine.close()
# coroutine state becomes GEN_CLOSED

yield from <iterabile>

Note: auto-priming generators incompatible with yield from

DELEGATING GENERATOR: generator function containing yield from SUBGENERATOR: generator obtained from yield from CALLER-CLIENT: code calling delegating generator

The main function of yield from is to open a bidirectional channel between the external caller (client) and the internal subgenerator so that values and exceptions can pass between the two.

  1. client calls delegating generator, delegating generator calls subgenerator
  2. exhausted subgenerator returns value to yield from <expr> (return <result> statement)
  3. delegating generator returns <expr> to client

  4. Any values that the subgenerator yields are passed directly to the caller of the delegating generator (i.e., the client code).

  5. Any values sent to the delegating generator using send() are passed directly to the subgenerator.

  6. If the sent value is None, the subgenerator's __next__() method is called.
  7. If the sent value is not None, the subgenerator's send() method is called.
  8. If the call raises StopIteration, the delegating generator is resumed.
  9. Any other exception is propagated to the delegating generator.

  10. return <expr> in a generator (or subgenerator) causes StopIteration(<expr>) to be raised upon exit from the generator.

  11. The value of the yield from expression is the first argument to the StopIteration exception raised by the subgenerator when it terminates.

  12. Exceptions other than GeneratorExit thrown into the delegating generator are passed to the throw() method of the subgenerator.

  13. If the call raises StopIteration, the delegating generator is resumed.
  14. Any other exception is propagated to the delegating generator.

  15. If a GeneratorExit exception is thrown into the delegating generator, or the close() method of the delegating generator is called, then the close() method of the subgenerator is called if it has one.

  16. If this call results in an exception, it is propagated to the delegating generator.
  17. Otherwise, GeneratorExit is raised in the delegating generator
Python
def sub_gen():
     sent_input = yield
     # result of sub_gen() returned to delegating_gen()
     # result of yield from <expr>

     return result

def delegating_gen(var):
     var = yield from sub_gen() # get values from sub_gen

def client():
     result = delegating_gen() # use delegating_gen
     result.send(None) # terminate sub_gen instance (IMPORTANT)

LAMBDA Functions

Possible use within functions. Useful for replacing functions if the logic is simple.

Python
var = lambda argument_list: <expression>

Object Oriented Programming

Class Definition

Python
class Class:

    static_var = expression

    def __init__(self, value_1, value_2): # parameterized default constructor
        self.variable = value_1 # create instance variables
        self.__private = value_2 # private, accessed via NAME MANGLING

    def method(self, parameters):
        ...

    @staticmethod
    def static_method(parameters): # static methods do not affect instance variables (SELF not needed)
        ...

    @classmethod # method acting on the class and not on the object (useful for alternative constructors)
    def class_method(cls, parameters):
        ...

    object = Class(parameters) # creation of an object
    object.variable = expression # edit public variable
    object.method(parameters) # invocation method of instance
    object._Class__private # access to variable specifying the membership class (NAME MANGLING)
    Class.method(parameters) # static method invocation

Setter & Getter with @Property

Python
class Class:
     def __init__(self, parameter):
         self.__parameter = parameter

     @property # getter
     def parameter(self):
         return self.__parameter

     @<parameter>.setter
     def parameter(self, value):
         self.__parameter = value

__slots__

The __slots__ attribute implements the Flyweight Design Pattern: it saves the instance attributes in a tuple and can be used to decrease the cost in memory by inserting only the instance variables into it (suppress the instance dictionary).

Default: attributes saved in a dictionary (object .__ dict__) Usage: __slots_ = [attributes]

__slots__ is not inherited by subclasses, it prevents dynamically adding attributes.

Inner Classes

Python
class Class:
     def __init__(self, parameters):
         ...

     class InnerClass:
         def __init__(self, parameters):
             ...

         def method(self):
             ...

object_1 = Class(arguments) # create 'external' class
object_2 = Class.InnerClass(arguments) # inner class created as object of the 'external' class

Special Methods

Special methods are defined by the use of double underscores; they allow the use of specific functions (possibly adapted) on the objects defined by the class.

Python
class Class():

     def __init__(self, parameters):
         instructions

     # used by str() and print() method
     # handle requests for impersonation as a string
     def __str__ (self):
         return expression # return required

     def __len__ (self):
         return expression # must return as len requires a length / size

     def __del__ (self): # delete the class instance
         instruction # any instructions that occur on deletion

object = Class()
len(object) # special function applied to an object
del object # delete object

Special Methods List

Note: if the operator cannot be applied, returns NotImplemented

Python
# arithmetic operators
__add__(self, other)         # +
__sub__(self, other)         # -
__mul__(self, other)         # *
__matmul__(self, other)      # (@) matrix multiplication
__truediv__(self, other)     # /
__floordiv__(self, other)  # //
__mod__(self, other)         # %
__divmod__(self, other)      # divmod()
__pow__(self, other)         # **, pow()
__lshift__(self, other)      # <<
__rshift__(self, other)      # >>
__and__(self, other)         # &
__xor__(self, other)         # ^
__or__(self, other)          # |

# reflex arithmetic operators
# if self.__ dunder __(other) fails, other.__ dunder__(self) is called
__radd__(self, other)         # reverse +
__rsub__(self, other)         # reverse -
__rmul__(self, other)         # reverse *
__rmatmul__(self, other)      # reverse @
__rtruediv__(self, other)     # reverse /
__rfloordiv__(self, other)    # reverse //
__rmod__(self, other)         # reverse %
__rdivmod__(self, other)      # reverse divmod()
__rpow__(self, other)         # reverse **, pow()
__rlshift__(self, other)      # reverse <<
__rrshift__(self, other)      # reverse >>
__rand__(self, other)         # reverse &
__rxor__(self, other)         # reverse ^
__ror__(self, other)          # reverse |

# in-place arithmetic operators
# base implementation (built-in) like self = self <operator> other
#! not to be implemented for immutable objects!
#! in-place operators return self!
__iadd__(self, other)         # +=
__isub__(self, other)         # -=
__imul__(self, other)         # *=
__imatmul__(self, other)      # @=
__itruediv__(self, other)     # /=
__ifloordiv__(self, other)    # //=
__imod__(self, other)         # %=
__ipow__(self, other)         # **=
__ilshift__(self, other)      # <<=
__irshift__(self, other)      # >>=
__iand__(self, other)         # &=
__ixor__(self, other)         # ^=
__ior__(self, other)          # |=

# unary mathematical operators (-, +, abs (), ~)
__neg__(self)  # (-) negazione matematica unaria [if x = 2 then -x = 2]
__pos__(self)  # (+) addizione unaria [x = +x]
__abs__(self)  # [abs()] valore assoluto [|-x| = x]
__invert__(self)  # (~) inversione binaria di un intero [~x == -(x + 1)]

# numeric type conversion
__complex__(self)
__int__(self)  # if not defined fall-back on __trunc__()
__float__(self)
__index__(self)  # conversion in bin(), hex(), oct() e slicing

# operations round() math.trunc(), math.floor(), math.ceil()
__round__(self)
__trunc__(self)
__floor__(self)
__ceil__(self)

# equality operators
self.__eq__(other)  # self == other
self.__ne__(other) # self != other
self.__gt__(other) # self > other
self.__ge__(other) # self >= other
self.__lt__(other) # self < other
self.__le__(other) # self <= other

# reflected equality operators
other.__eq__(self)  # other == self,   fall-back id(self) == id(other)
other.__ne__(self)  # other != self,   fall-back not (self == other)
other.__gt__(self)  # reverse self < other,   fall-back TypeError
other.__ge__(self)  # reverse self <= other,   fall-back TypeError
other.__lt__(self)  # reverse self > other,   fall-back TypeError
other.__le__(self)  # reverse self >= other,   fall-back TypeError

# called when the instance is "called" as a function
# x (arg1, arg2, arg3) is short for x .__ call __ (arg1, arg2, arg3)
__call__(self, args)

# string object representation for the developer
__repr__(self)

# string object representation for user (used by print)
__str__(self)

# specify formatting for format ), str.format() [format_spec = format-mini-language]
__format__(format_spec)

# returns unique (integer) value for objects that have equal value
# __EQ__ MUST EXIST IN THE CLASS, usually hash((self.param_1, self.param_2, ...))
__hash__(self)

# makes object iterable:
# - returning self (in the iterator)
# - returning an iterator (in the iterable)
# - using yield (in the __iter__ generator)
__iter__(self)

# returns next available element, StopIteration otherwise (iterator scrolls)
__next__()

# returns truth value
__bool__()

# returns item associated with key of a sequence (self [key])
# IndexError if key is not appropriate
__getitem__(self, key)

# item assignment operation in sequence (self [key] = value)
# IndexError if key is not appropriate
__setitem__(self, key, value)

# operation deleting item in sequence (del self [key])
# IndexError if key is not appropriate
__delitem__(self, key)

# called by dict.__getitem__() to implement self [key] if key is not in the dictionary
__missing__(self, key)

# implement container iteration
__iter__(self)

# implement membership test
__contains__(self, item)

# implementation issublass (instance, class)
__instancecheck__(self, instance)

# implementation issubclass (subclass, class)
__subclasscheck__(self, subclass)

# implement attribute access (obj.name)
# called if AttributeError happens or if called by __getattribute __()
__getattr__(self, name)

# implement value assignment to attribute (obj.name = value)
__setattr__(self, name, value)

Note: Itearbility is tricky.

To make an object directly iterable (for i in object) __iter__() and __next__() are needed. To make an iterable through an index (for i in range(len(object)): object[i]) __getitem()__ is needed.

Some of the mixin methods, such as __iter__(), __reversed__() and index(), make repeated calls to the underlying __getitem__() method. Consequently, if __getitem__() is implemented with constant access speed, the mixin methods will have linear performance; however, if the underlying method is linear (as it would be with a linked list), the mixins will have quadratic performance and will likely need to be overridden.

Inheritance

Python
class Parent ():
    def __init __ (self, parameters):
        ...

    def method_1(self):
        ...

    def method_2(self):
        ...

class Child(Parent): # parent class in parentheses to inherit variables and methods

    def __init__(self, parameters, parent_parameters):
        Parent.__init__(self, parent_parameters) # inherit parent variables
        ...

    def method (self):
        ...

    def method_parent_1 (self): # override method (child class with homonymous method to parent class)
        ...

class Child(Parent): # parent class in brackets to inherit properties

    def __init__(self, parameters, parent_parameters):
        super().__init__(parent_parameters) # different method to inherit parent variables (SELF not needed) using SUPER()
        super(Parent, self).__init__(parent_parameters) # parent constructor invoked separately

    def method(self):
        ...

    def method_2(self): # parent method updated
        super().method_2() # invoke parent method as is
        ...

Polymorphism

Note: python does not support method overloading

Python
# DUCKTYPING
# Working with objects regardless of their type, as long as they implement certain protocols

class Class1:
    def method_1(self):
        ...

class Class2:
    def method_1(self):
        ...

# since python is a dynamic language it doesn't matter what type (class) the object passed is
# the function invokes the object method passed regardless of the object class
def polymorph_method(object):
    object.method_1()

# DEPENDENCY INJECTION WITH DUCKTYPING
class Class:
    def __init__(self, object):
        self.dependency = object

    def method_1(self): # the function invokes the method of the object passed
        self.dependency.method_1()

Operator Overloading

Operators fundamental rule: always return an object, if operation fails return NotImplemented

Limitations of operator overloading:

  • no overloading of built-in types
  • no creation of new operators
  • no overloading operators is, and, or, not

Astrazione

The interfaces are abstract classes with all abstract methods, they are used to indicate which methods such as child classes must have. Interfaces have only a list of abstract methods.

abstract classes have at least one abstract method; child classes that inherit from an abstract class must implement abstract methods. Abstract classes cannot be instantiated.

Virtual subclasses are used to include third-party classes as subclasses of a class of their own. They are recognized as belonging to the parent class without however having to implement their methods.

The @Class.register or Class.register(subclass) decorators are used to mark subclasses.

Python
from abc import abstractmethod, ABC

class Abstract(ABC): # abstract class MUST INHERIT from parent class ABC
    def __init__(self, parameters):
        ...

    def parent_method (self):
        ...

    @abstractmethod # abstract method MUST be marked with @abstractmethod decorator
    def abstract_method (self):
        pass
        # abstract method MUST be overridden (can be non-empty)
        # super() to invoke it in the concrete class

class Child(Abstract):

    def __init__(self, parameters, parent_parameters):
        parent_class.__init__(self, parent_parameters)

    def method (self):
        ...

    def parent_method (self): # override method (child class with homonymous method to parent class)
        ...

    def abstract_method (self): # implementation of abstract method inherited from abstract class (NECESSARY) by override
        ...

Exception Handling

Python
# CHECK ASERATIONS
assert condition, 'error message' # if the assertion is false show an error message

# particular errors are objects of a particular class of exceptions which in turn is a child of the base exception class (exception)
class CustomExceptionError(Exception): # MUST somehow inherit from class exception (even in later inheritance steps)
    pass # or instructions

# try block contains code that might cause an exception
# code inside try and after the error it is not executed
try:
    ...
    raise CustomExceptionError ("message") # raise the exception

# except takes control of error handling without passing through the interpreter
# block executed if an error occurs in try

# except error specified by class
except ExceptionClass:
    # Default error message is not shown
    # the program does not stop

# except on generic errors
except:
     # code here

# block executed if exception does not occur
else:
    # code here

# block executed in all cases, cleanup code goes here
finally:
    # code here

File

Opening A File

Text file opening mode:

  • w: write, overwrite the contents of the file
  • r: read, read file contents
  • a: append, add content to the file
  • w +: write & read
  • r +: write & read & append
  • a +: append & read
  • x: exclusive creation, if the file already exists -> FileExistError (extended write mode)

Open binary file mode:

  • wb: write, overwrites the contents of the file
  • rb: read, read file contents
  • ab: append, add content to the file
  • w + b: write & read
  • r + b: write & read & append
  • a + b: append & read
  • xb: exclusive creation, if the file already exists -> FileExistError (extended write mode)

Note: Linux and MacOSX use UTF-8 everywhere while windows uses cp1252, cp850,mbcs, UTF-8. Don't rely on default encoding and use explicitly UTF-8.

Python
object = open('filename', mode = 'r', encoding = 'utf-8') # encoding MUST BE utf-8 for compatibility
# filename can be the absolute path to the file location (default: file created in the source code folder)
# double slash to avoid \ escaping

with open('filename') as file:
    instructions_to_file # block use filename to indicate file

# CLOSE A FILE
object.close()

# WRITE TO A FILE
object.write(string) # write single string to file
object.writelines(* strings) # write multiple strings to file

# READING FROM A FILE
object.read() # return ALL the contents of the file (including escape sequence) and place the "cursor" at the end of the file
object.seek(0) # returns 0 (zero) and places the cursor at the beginning of the file
object.readlines() # return list of file lines (ATTENTION: keep everything in memory, be careful with large files)
object.readline() # returns single line file

# CHECK FILE EXISTENCE
import os, sys
if os.path.isfile('filepath'): # check file existence (TRUE if it exists)
    # code here
else:
    # code here
    sys.exit() # exits the program and does not execute the next cosice

COPY

SHALLOW COPY: copies the "container" and references to the content DEEP COPY: copies the "container" and contents (no reference)

Python
copy (x) # returns shallow copy of xor
deepcopy (x) # returns shallow copy of x