# Python Notes ## Basics ```py #!/usr/bin/env python # commento standard '''commento multilinea''' """DOCSTRING""" ''' NAMING CONVENTION Class --> PascalCase Method, Function --> snake_case Variable --> snake_case ''' help(oggetto.metodo) # restituisce spiegazione metodo dir(object) # return an alphabetized list of names comprising (some of) the attributes of the given object import sys # importa modulo from sys import argv # importa singolo elemento da un modulo from sys import * # importa tutti gli elementi di un modulo (non necessaria sintassi modulo.metodo) import sys as alias # importa il modulo con un alias, utilizzo alias.metodo # SET CARATTERI 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 # CARATTERI SPECIALI # (\a, \b, \f, \n, \r, \t, \u, \U, \v, \x, \\) ``` ### Operazione Assegnamento ```py """istruzioni a dx di = eseguite prima di istruzioni a sx di =""" variabile = espressione # il tipo della variabile viene deciso dinamicamente da python in base al contenuto var_1, var_2 = valore1, valore2 # assegnamento parallelo var_1, var_2 = var_2, var_1 # swap valori # 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). ``` ### Conversione Tipo Variabile `tipo(espressione)` ### Assegnamento Espressioni ```py (var := expression) # assegna ad una variabile un espressione per evitare di ripetere l'espressione ``` ### Confronto Variabili (`==` vs `is`) `==` confronta i valori degli oggetti `is` compara le identità degli oggetti ### Output Su Schermo ```py print() # stampa linea vuota e va a capo print('stringa'*n) # stampa stringa n volte print('stringa1 \n stringa2') # va a capo con \n print(variabile) # stampa contenuto variabile print('stringa', end='') # stampa senza andare a capo # FORMATTAZIONE name = 'Alex' marks = 94.5 print(name, marks) print('Name is', name, '\nMarks are', marks) # espande il resto dell'espressione e scrive teso prima di = in output print(f'{name=}, {marks=}') # OUTPUT: name=Alex, marks=94.5 # USO DEI PLACEHOLDER print('Name is %s, Marks are %3.2f' % (name, marks)) # metodo ereditato da C. La variabile viene sostituita al posto di %.. print("Name is {}, Marks are {}".format(name, marks)) print("Name is {1}, Marks are {2}".format(marks, name)) # indici in parentesi ordinano elementi in .format print("Name is {n}, Marks are {m}".format(m = '94.5', n = 'Alex')) # indici in parentesi ordinano elementi in .format print(f'Name is {name}, Marks are {marks}') # formattazione con f-strings ``` ### Format Specification Mini-Language `{value:width.precision symbol}` `width.precision` => numeroCifreTotali.numeroCifreDecimali Format: `[[fill]align] [sign] [#] [width] [grouping] [.precision] [type]` OVERRIDE __format__() {!a} | chiama ascii() sulla variabile {!r} | chiama repr() sulla variabile {!s} | chiama str() sulla variabile RIEMPIMENTO [fill] {} | `[align]` | Allineamento | | --------- | ---------------------- | | `:<` | allineamento sinistra | | `:>` | allineamento destra | | `:=` | padding dopo il segno | | `:^` | centrato | | `[sign]` | SEGNI NUMERI | | -------- | -------------------------------------------------------------------------------------------------------------- | | `:+` | segno sia per numeri positivi che negativi | | `:-` | segno solo per numeri negativi | | `:` | spazio per num > 0, '-' per num < 0 | | `:#` | forma alternativa: interi prefisso tipo (0x, 0b, 0o), float e complessi hanno sempre almeno una cifra decimale | | `[grouping]` | RAGGRUPPAMENTO | | ------------ | ------------------------------------ | | `:,` | usa virgola per separare migliaia | | `:_` | usa underscore per separare migliaia | | `[type]` | TIPO OUTPUT | | -------- | ------------------------------------------------------------------------------ | | `:s` | output è stringa | | `:b` | output è binario | | `:c` | output è carattere | | `:d` | output è intero decimale (base 10) | | `:o` | output è intero ottale (base 8) | | `:x` | output è intero esadecimale (base 16) | | `:X` | output è intero esadecimale (base 16) con lettere maiuscole | | `:e` | output è notazione esponenziale (precisione base 6 cifre) | | `:E` | output è notazione esponenziale (precisione base 6 cifre) separatore maiuscolo | | `:f` | output è float (precisione base 6 cifre) | | `:%` | output è percentuale (moltiplica * 100, display come :f) | ### Input Da Tastiera ```py # input ritorna sempre una STRINGA s = input() # richiesta input senza messaggio s = input('Prompt') # richiesta input i = int(input('prompt')) # richiesta input con conversione di tipo # INPUT MULTIPLI lista = [int(x) for x in input('prompt').split('separatore')] # salva input multipli in una lista (.split separa i valori e definisce il separatore ``` ## Tipi Numerici ```py a = 77 b = 1_000_000 # underscore può essere usato per separare gruppi di cifre c = -69 # float numbers x = 3.15 y = 2.71 z = 25.0 d = 6 + 9j # complex number # restituisce un numero complesso a partire da due reali 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 # CONVERSIONE TIPO VARIABILE h = int(y) i = float('22.5') # CONVERSIONE BASE NUMERICA bin(3616544) hex(589) oct(265846) # CONVERSIONE UNICODE 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) # ritorna valore assoluto di num (|num|) round(num, precisione) # arrotonda il numero alla data precisione, non converte float to int ``` ### Confronto Numeri Decimali Non usare `==` o `!=` per confrontare numeri in virgola mobile. Essi sono approssimazioni o hanno parecchie cifre. Conviene verificare se la differenza tra i numeri è sufficientemente piccola. ## Stringhe ```py stringa = 'contenuto stringa' # assegnazione e creazione variabile stringa stringa = '''multi line string''' stringa3 = stringa1 + stringa2 # concatenazione stringhe (polimorfismo operatore +) # INDEXING (selezione di un carattere nella stringa) stringa[0] stringa[2] stringa[-3] # selezione partendo dal fondo (indice negativo) # REPETITION (ripetizione output stringa) print(stringa * n) len(stringa) # mostra la lunghezza di una stringa # SLICING (estrazione di sotto-stringhe, non include la posizione dell'ultimo indice) stringa[0:5] stringa[:6] stringa[-3:-1] # SLICING CON STEP stringa[0:12:3] stringa[15::-1] stringa[::-1] # selezione in ordine inverso (step negativo) # STRIPPING (eliminazione spazzi prima e dopo stringa) stringa=' stripping test ' stringa.strip() stringa.lstrip() # solo spazi a sinistra rimossi stringa.rstrip() # solo spazi a destra rimossi stringa.removeprefix(prefix) # If the string starts with the prefix string, return string[len(prefix):] stringa.removesuffix(suffix) # If the string ends with the suffix string and that suffix is not empty, return string[:-len(suffix)] # INDIVIDUAZIONE SOTTOSTRINGA #ritorna indice di partenza della sottostringa o -1 se essa non è presente stringa.find('sottostringa', 0, len(stringa)) # si può specificare l'indice di partenza e fine della ricerca # CONTEGGIO APPARIZIONI stringa.count('t') # RIMPIAZZO stringa.replace('multi', 'multiple') # CONVERSIONE MAIUSCOLO-MINUSCOLO stringa.upper() stringa.lower() stringa.title() stringa.capitalize() # SEPARAZIONE IN ELEMENTI LISTA stringa.split() stringa.split('separatore') # separa usando il separatore (separatore omesso nella lista) stringa.partition('char') # -> tuple # separa la stringa i 3 parti alla prima occorrenza di separatore # METODI IS_CHECK --> bool stringa.isalnum() stringa.isalpha() stringa.islower() stringa.isspace() stringa.istitle() stringa.isupper() stringa.endswith('char') # ISTRUZIONE JOIN() ''.join(iterabile) # unisce tutti gli elementi dell'iterabile nella nuova stringa # FORMATTING string.center(larghezza, 'char') # allarga la stringa con char fino a raggiungere larghezza '...\t...'.expandtabs() # trasforma tabs in spaces ``` ## Liste ```py lista = [9, 11, 'WTC', -5.6, True] # le liste possono contenere dati di tipo diverso lista[3] # indexing lista[3:5] # slicing lista * 3 # repetition len(lista) # length lista3 = lista1 + lista2 # concatenazione liste (polimorfismo operatore +) lista[indice] = valore # modifica elemento lista del(lista[1]) # rimozione per indice (INBUILT IN PYTHON) # modifica la lista tra gli indici start e stop riassegnando gli elementi dell'iterabile lista[start:stop] = iterabile # METODI LISTE lista.append(oggetto) # aggiunge oggetto al fondo lista.count(item) # conta il numero di occorrenze di item lista.extend(sequenza) # aggiunge gli elementi di sequenza alla lista lista.insert(posizione, oggetto) # inserisce oggetto in lista[posizione] lista.index(item) # restituisce l'indice di item lista.remove(item) # rimuove item lista.pop(item) # elimina item e lo restituisce lista.clear() # rimozione di tutti gli elementi lista.sort() # sorts in ascending order (in place) lista.sort(reverse = True) # sorts in descending order (in place) lista.reverse() # inverte la stringa (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) # LISTE ANNIDATE (MATRICI) lista_1 = [1, 2, 3] lista_2 = [4, 5, 6] lista_3 = [7, 8, 9] matrice = [lista_1, lista_2, lista_3] matrice[i][j] # identifica elemento di lista_i indice j # MASSIMO E MINIMO max(lista) min(lista) # ALL() & ANY() all(sequenza) # ritorna TRUE se tutti gli elementi della sequenza hanno valore True any(sequenza) # ritorna TRUE se almeno un elemento della sequenza ha valore True # ISTRUZIONE MAP # applica la funzione all'iterabile e crea una nuova lista (oggetto map) # funzione può essere lambda map(funzione, iterabile) # -> oggetto map # ISTRUZIONE FILTER() # crea una nuova lista composta dagli elementi di iterabile per cui la funzione ritorna TRUE filter(funzione, iterabile) # -> oggetto filter # ISTRUZIONE ZIP() # crea una generatore di tuple unendo due o più iterabili # [(seq_1[0], seq_2[0], ...), (seq_1[1], seq_2[1], ...), ...] # tronca la sequenza alla lunghezza della sequenza input più corta zip(seq_1, seq_2, ...) # -> oggetto zip (generatore di tuple) # LIST COMPREHENSIONS var = [espressione for elemento in sequenza if condizione] # crea lista da lista pre-esistente (al posto di map, filter, reduce) applicando eventuali manipolazioni # l'espressione può essere una lambda, l'if è opzionale var = [espresione if condizione else istruzione for elemento in sequenza] # list comprehension con IF-ELSE var = [espressione_1 for elemento in [espressione_2 for elemento in sequenza]] # list comprehension annidata var = [(exp_1, exp_2) for item_1 in seq_1 for item_2 in seq_2] # --> [(..., ...), (..., ...), ...] ``` ## Tuple ```py # LE TUPLE NON POSSONO ESSERE MODIFICATE tuple = (69, 420, 69, 'abc') # assegnazione tuple tuple = (44, ) # tuple di singolo elemento necessitano di una virgola tuple[3] # indexing tuple * 3 # repetition tuple.count(69) # counting tuple.index(420) # individuazione indice len(tuple) # lunghezza tuple # CONVERSIONE DA TUPLE A LISTA tuple = tuple(lista) # 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 #OPERATORE *VAR (tuple unpacking) var_1, var_2, *rest = sequenza # var_1 = seq[0], var_2 = seq[1], rest = seq[2:] var_1, *body, var_2, var_3 = sequenza # var_1 = seq[0], body = seq[1:-2], var_2 = sequenza[-2], var_3 = seq[-1] # *var recupera gli item in eccesso, se in assegnamento parallelo usabile max una volta ma in posizione qualsiasi ``` ## Set ```py # I SET NON POSSONO CONTENERE ELEMENTI RIPETUTI (VENGONO OMESSI) # L'ORDINE NON IMPORTA (NO SLICING, INDEXING, REPETITION, ...) set = {10, 20, 30, 'abc', 20} len(set) # lunghezza set set() # crea set vuoto ({} cre dizionario vuoto) # FREEZING SETS (non più modificabili) fset = frozenset(set) # OPERATORI set_1 - set_2 # elementi in set_1 ma non in set_2 set_1 | set_2 # elementi in set_1 o set_2 set_1 & set_2 # elementi in set_1 e set_2 set_1 ^ set_1 # elementi in o set_1 o set_2 set_1 <= set_2 # elementi set_1 anche in set_2 set_1 < set_2 # set_1 <= set_2 and set_1 != set_2 set_1 >= set_2 # elementi set_2 anche in set_1 set_1 > set_2 # set_1 >= set_2 and set_1 != set_2 # METODI SET set.pop(item) # rimuove e restituisce item set.add(item) # aggiunge item al set set.copy() # -> set # restituisce una copia del set set.clear() # rimuove tutti gli elementi dal set set.remove(item) # rimuove item dal set se presente, altrimenti solleva KeyError set.discard(item) #rimuove item dal set se presente, altrimenti fa nulla set.difference(*sets) # -> set # restituisce elementi in set che sono assenti in *sets set.difference_update(*sets) # rimuove le differenze dal set_2 set.union(*sets) # -> set # restituisce tutti gli elementi dei set set.update(*sets) # aggiunge elementi *sets a set set.intersection(*sets) # -> set # restituisce gli elementi comuni ai set set.intersection_update(*sets) # rimuove tutti gli elementi tranne quelli comuni ai set set.symmetric_difference(*sets) # -> set # restituisce gli elementi non comuni ai set set.symmetric_difference_update(*sets) # rimuove tutti gli elementi comuni ai set (lasci solo gli elementi non comuni) set_1.isdisjoint(set_2) # -> bool # True se non ci sono elementi comuni (intersezione è vuota) set_1.issubset(set_2) # -> bool # True se ogni elemento di set_1 è anche in set_2 set_1.issuperset(set_2) # -> bool # True se ogni elemento di set_2 è anche in set_1 # SET COMPREHENSIONS var = {espressione for elemento in sequenza if condizione} # OGGETTO SLICE # [start:stop:step] --> oggetto slice(start, stop, step) var_1 = slice(start, stop, step) # assegnamento a variabile var_2[var_1] # uguale a var_2[start:stop:step] # OGGETTO ELLIPSIS var[i, ...] # --> shortcut per var[i , :, : ,:,] # usato per slice multidimensionale (NumPy, ...) ``` ## Bytes e Bytearray ```py # I BYTE NON SI POSSONO MODIFICARE NE INDICIZZARE # I BYTEARRAY SI POSSONO MODIFICARE E INDICIZZARE # NON SI PUO' FARE REPETITION E SLICING SU BYTE O BYTEARRAY b = bytes(lista) ba = bytearray(lista) # item di bytes e bytearray è sempre intero tra 0 e 255 # slice di bytes e bytearray è sequenza binaria (anche se len = 1) # METODI BYTES E BYTEARRAY bytes.fromhex(pair_hex_digits) # -> byte literal b'bite_literal'.hex() # -> str # restituisce una stringa contenente coppie di cifre hex bytearray.fromhex(pair_hex_digits) # -> byte literal bytes.count(subseq, start, end) # restituisce conteggio apparizioni subseq tra posizioni start e end bytearray.count(subseq, start, end) # restituisce conteggio apparizioni subseq tra posizioni start e end ``` ## Encoding-Decoding & Unicode BYTE LITERALS ASCII --> stesso carattere tab, newline, carriage return, escape sequence --> \t, \n, \r, \\ altro --> escape sequence esadecimale (null byte --> \x00) Unicode Literals: - `\u0041` --> 'A' - `\U00000041` --> 'A' - `\x41` --> 'A' ```py # ENCODING # trasforma stringa in byte literal # errors= |> UnicodeEncodeError in caso di errore # errors=ignore --> salta caratteri causanti errore # errors=replace --> sostituisce ? a caratteri causanti errore # errors=xmlcharrefreplace --> sostituisce entità XML a caratteri causanti errore stringa.encode('utf-8', errors='replace') # -> b'byte literals' # BOM (BYTE ORDER MARK) # byte literal premesso per indicare l'ordinamento dei byte (little-endian vs big-endian) # in little-endian i byte meno significativi vengono prima (e.g. U+0045 --> DEC 069 --> encoded as 69 and 0) # U+FEFF (ZERO WIDTH NO-BREAK SPACE) --> b'\xff\xfe' indica little-endian # DECODING # trasforma byte literal in stringa # error='replace' sostituisce gli errori (byte literal non appartenenti a formato di decodifica) con U+FFFD "REPLACEMENT CHARACTER" bytes.decode('utf-8', errors='replace') # -> str # NORMALIZZAZIONE UNICODE # gestione equivalenti canonici unicode (e.g. é, e\u0301 sono equivalenti per unicode) import unicodedata unicodedata.normalize(form, unicode_string) # FORM: NFC,NFD, NFCK, NFDK # NFC --> "Normalization Form C" --> produce la stringa equivalente più corta # NFD --> "Normalization Form D" --> produce la stringa equivalente più lunga # CASE FOLDING UNICODE # trasforma in minuscolo con alcune differenze (116 differenze, 0.11% di Unicode 6.3) stringa.casefold() # FUNZIONI UTILI PER EQUIVALENZA NORMALIZZATA (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 ```py # oggetti memoryview consentono a python l'accesso ai dati all'interno dell'oggetto # senza copia se esso supporta il buffer protocol v = memoryview(oggetto) # crea un memoryview con riferimento ad oggetto # slice di memoryview produce nuovo memoryview # METODI MEMORYVIEW v.tobytes() # restituisce i dati come bytestring, equivale a bytes(v) v.hex() # restituisce stringa contenete du cifre hex per ogni byte nel buffer v.tolist() # restituisce i dati nel buffer come un lista di elementi v.toreadonly() v.release() # rilascia il buffer sottostante v.cast(format, shape) # cambia il formato o la forma del memoryview v.oggetto # oggetto del memoryview v.format # formato del memoryview v.itemsize # dimensione in byte di ogni elemento del memoryview v.ndim # intero indicante le dimensioni dell'array multidimensionale rappresentato v.shape # tuple di interi indicanti la forma del 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` | ## Dizionari ```py # 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] # restituisce valore associato a key d[4] = 'Dan' # aggiunge o modifica elemento list(d) # restituisce una lista di tutti gli elementi len(d) # restituisce il numero di elementi del(d[2]) # elimina elemento # METODI DIZIONARI d.clear() # rimuove tutti gli elementi d.copy() # copia superficiale del dizionario d.get(key) # restituisce il valore associato a key d.items() # restituisce coppie key-value (oggetto view) d.keys() # restituisce le chiavi del dizionario (oggetto view) d.values() # restituisce i valori del dizionario (oggetto view) d.pop(key) # rimuove e restituisce il valore associato a key d.popitem() # rimuove e restituisce l'ultima coppia key-value d.setdefault(key, default) # se la key è presente nel dizionario la restituisce, altrimenti la inserisce con valore default e restituisce default d.update(iterabile) # aggiunge o modifica elementi dizionario, argomento deve essere coppia key-value # 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'} # DIZIONARI ANNIDATI (possibile annidare dizionari all'interno di dizionari) my_dict = {'key_1':123, 'key_2':[12, 23, 33], 'key_3':['item_0', 'item_1', 'item_2']} my_dict['key'][0] # restituisce elemento annidato # DICT COMPREHENSIONS var = {key : value for elemento in sequenza} ``` ## 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 | y | | 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 | ### Precedenza Operatori 1. operatori assegnamento `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, `//=` 2. operatori aritmetici binari `*`, `/`, `%`, `//` (floor division) 3. operatori aritmetici binari `+`, `-` 4. operatori booleani `<`, `>` , `<=`, `>=` 5. operatori booleani `==`, `!=` 6. operatore booleano `and` 7. operatore booleano `or` 8. operatore booleano `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` ```py if (condizione): # code here elif (condizione): # code here else: # code here ``` ### Context Manager Oggetto che definisce il contesto di runtime stabilito all'uso del `with` statement. Il manager gestisce l'entrate e l'uscita dal contesto di runtime per l'esecuzione del blocco di codice. ```py with risorsa as target: # code here # avvia context manager e lega risorsa restituita dal metodo al target usando operatore as contextmanager.__enter__(self) # exit runtime context # restituisce 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 -> restituisce None, None, None # SOPPRESSIONE ECCEZIONE: necessario restituire valore True ``` ## Loops ### Ciclo `while` ```py while (condizione): # code here else: # eseguito solo se condizione diventa False # break, continue, return in blocco while non eseguono blocco else # code here ``` ### Ciclo `for` ```py for indice in sequenza: # sequenza può essere una lista, set, tuple, etc.. # code here else: # eseguito solo se for arriva a fondo ciclo # break, continue, return in blocco for non eseguono blocco else # code here for indice in range(inizio, fine, step): # code here for key, value in dict.items(): # code here ``` ### Istruzioni `break` & `continue` `break`: causa l'uscita immediata dal ciclo senza l'esecuzione delle successive iterazioni `continue`: salta le restanti istruzioni del'iterazione e prosegue il ciclo ### Istruzione `range` ```py range(inizio, fine, step) # genera sequenza num interi (non include num stop) con eventuale passo list(range(inizio, fine, passo)) # restituire sequenza num interi in una lista ``` ### Istruzione `enumerate` ```py enumerate(iterabile) # restituisce oggetto enumerate list(enumerate(iterabile)) # restituisce lista di tuple [(1, iterabile[0]), (2, iterabile[1]), (3, iterabile[2])] ``` ### Istruzione `zip` ```py list_1 = [1, 2, 3, 4, 5] list_2 = ['a', 'b', 'c', 'd', 'e'] zip(list_1, list_2) # restituisce oggetto zip list(zip(list_1, list_2)) # restituisce lista di tuple fondendo la lista [(list_1[0], list_2[0]), (list_1[1], list_2[1]), ...] ``` ### Istruzione `shuffle` e `randint` ```py from random import shuffle, randint shuffle(iterabile) # mischia la lista randint(inizio, fine) # restituisce un intero random compreso tra inizio e fine ``` ### Istruzione `in` ```py item in iterabile # controlla presenza di item in iterabile (restituisce True o False) ``` ## Funzioni ### Definizione Funzione ```py def nome_funzione (parametri): """DOCSTRING""" # code here return espressione # if return id missing the function returns None ``` ### Invocazione Funzione `nome_funzione(parametri)` ### Specificare Tipo Parametri In Funzioni - parametri prima di `/` possono essere solo *posizionali* - parametri tra `/` e `*` possono essere *posizionali* o *keyworded* - parametri dopo `*` possono essere solo *keyworded* ```py def func(a, b, /, c, d, *, e, f): # code here ``` ### Docstring Style ```py """ descrizione funzione Args: argomento: Type - descrizione del parametro Returns: Type - descrizione di Raises: Eccezione: Causa dell'eccezione """ ``` ### *args **kwargs `*args` permette alla funzione di accettare un numero variabile di parametri (parametri memorizzati in una tuple) `**kwargs` permetta alla funzione di accettare un numero variabile di parametri key-value (parametri memorizzati in un dizionario) Se usati in combinazione `*args` va sempre prima di `**kwargs` (in def funzione e in chiamata funzione) ```py def funzione(*args, **kwargs): # code here ``` ### Funzione con Parametri di default ```py def funzione (parametro1 = valore1, parametro2 = valore3): # valori di default in caso di omesso uso di argomenti nella chiamata # code here return espressione funzione(parametro2 = valore2, parametro1 = valore1) # argomenti passati con keyword per imporre l'ordine di riferimento ``` ### VARIABILI GLOBALI E LOCALI ```py # global scope def func_esterna(): # enclosing local scope # code here def func_interna(): # local scope # code here ``` **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`: variabili dichiarate all'interno di una funzione non sono utilizzabili all'esterno ```py def funzione(): # istruzione global rende variabile globale. # azioni su variabile globale all'interno della funzione hanno effetto anche fuori # NORMALMENTE: valori locali rimangono locali global variabile # code here ``` ### Iterabili, Iteratori E Generatori **Iterabile**: oggetto implementante `__iter__()`, sequenze e oggetti supportanti `__getitem__` con index `0` **Iteratore**: oggetto implementante `__next__` e `__iter__` (**protocollo iteratore**), quando interamente consumato da next() diventa inutilizzabili. Gli iteratori sono iterabili, viceversa non vero. Restituisce `StopIteration` quando `next()` ha restituito tutti gli elementi. **Funzione Generatore**: funzione con keyword `yield` (se presente anche `return` causa `StopIteration`), restituisce un generatore che produce i valori uno alla volta. **Generator Factory**: funzione restituente generatore (può non contenere `yield`). Funzionamento `iter()`: - chiama __iter__() - in assenza di esso python sfrutta __getitem__() (se presente) per creare un iteratore che tenta di recuperare gli item in ordine, partendo dall'indice `0` - in caso di fallimento restituisce `TypeError "obj_cls is not iterable"` **Note**: `abc.Iterable` non controlla la presenza di `__getitem__` per decidere se un sotto-oggetto è membro conseguentemente il miglior test per l'iterabilità è usare `iter()` e gestirne le eccezioni. ### Istruzioni `next()` & `iter()` ```py next(iterabile) # prossimo item dell'iterabile o errore StopIteration iter(oggetto) # ottiene un iteratore a partire da un oggetto # chiama callable_onj.next() senza argomenti finché esso restituisce valori diversi da sentinella iter(callable_obj, sentinella) ``` ### Generatori Personalizzati Utilizzati per generare una sequenza di valori da utilizzare una sola volta (non vengono memorizzati) ```py def custom_generator (parametri): while condizione: # oppure for loop yield variabile # restituisce il valore senza terminare la funzione, valori passati al chiamante senza memorizzazione in una variabile # implementazione generatore for item in custom_generator(parametri): # code here ``` ### Terminazione Generatore E Exception Handling ```py # solleva eccezione al punto di sospensione e restituisce valore del generatore # se il generatore termina senza restituire valori solleva StopIteration # se un eccezione non viene gestita viene propagata al chiamante generator.throw(ExceptionType, exception_value, traceback) # solleva GeneratorExit al punto si sospensione # se generatore restituisce un valore -> RuntimeError # se viene sollevata unn eccezione essa si propaga al chiamante generator.close() ``` ### Generator Comprehensions ```py # sequenza di lunghezza zero (valori generati sul momento) var = (espressione for iterabile in sequenza if condizione) # ISTRUZIONE ENUMERATE() # restituisce una lista di tuple associando ad ogni elemento della sequenza un indice di posizione # [(0, sequenza[0]), (1, sequenza[1]), (2, sequenza[2]), ...) enumerate(sequenza) # -> oggetto enumerate ``` ## Coroutine ```py def simple_coroutine(): """coroutine definita come un generatore: yield nel blocco""" # code here # yield in espressione per ricevere dati # restituisce None (no variabili a dx di yield) var = yield value # restituisce value e poi sospende coroutine in attesa di input # istruzioni a dx di = eseguite prima di istruzioni a sx di = # code here gen_obj = simple_coroutine() # restituisce oggetto generatore next(gen_obj) # avvia coroutine (PRIMING) gen_obj.send(None) # avvia coroutine (PRIMING) gen_obj.send(value) # invia value alla coroutine (possibile solo in stato suspended) # STATI DELLA COROUTINE inspect.generatorstate() # restituisce lo stato della coroutine # GEN_CREATED: in attesa di avvio esecuzione # GEN_RUNNING: attualmente eseguito dall'interprete (visibile se multithreading) # GEN_SUSPENDED: attualmente sospesa da yield statement # GEN_CLOSED: l'esecuzione è stata completata # 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 # TERMINAZIONE COROUTINE E EXCEPTION HANDLING # eccezioni in coroutine non gestite si propagano alle iterazioni successive # un eccezione causa la terminazione della coroutine che non puo riprendere # yield solleva eccezione, se gestita ciclo continua # throw() restituisce valore del generatore coroutine.throw(exc_type, exc_value, traceback) # yield solleva GeneratorExit al punto di sospensione # se il generatore restituisce (yield) un valore -> RuntimeError # se ci sono altre eccezioni esse vengono propagate al chiamante coroutine.close() # stato coroutine diventa GEN_CLOSED ``` ### `yield from ` **Note**: auto-priming generators incompatible with `yield from` **DELEGATING GENERATOR**: funzione generatore contenente yield from **SUBGENERATOR**: generatore ottenuto da `yield from ` **CALLER-CLIENT**: codice chiamante *delegating generator* La funzione principale di `yield from` è aprire una canale bidirezionale tra il chiamante esterno (*client*) e il *subgenerator* interno in modo che valori ed eccezioni possono passare tra i due. 1. client chiama delegating generator, delegating generator chiama subgenerator 2. subgenerator esaurito restituisce valore a `yield from ` (istruzione `return `) 3. delegating generator restituisce `` a client - Any values that the subgenerator yields are passed directly to the caller of the delegating generator (i.e., the client code). - Any values sent to the delegating generator using `send()` are passed directly to the subgenerator. - If the sent value is `None`, the subgenerator's `__next__()` method is called. - If the sent value is not `None`, the subgenerator's `send()` method is called. - If the call raises `StopIteration`, the delegating generator is resumed. - Any other exception is propagated to the delegating generator. - `return ` in a generator (or subgenerator) causes `StopIteration()` to be raised upon exit from the generator. - The value of the `yield from` expression is the first argument to the `StopIteration` exception raised by the subgenerator when it terminates. - Exceptions other than `GeneratorExit` thrown into the delegating generator are passed to the `throw()` method of the subgenerator. - If the call raises `StopIteration`, the delegating generator is resumed. - Any other exception is propagated to the delegating generator. - 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. - If this call results in an exception, it is propagated to the delegating generator. - Otherwise, `GeneratorExit` is raised in the delegating generator ```py def sub_gen(): # code here sent_input = yield # code here # risultato di sub_gen() restituito a delegating_gen() # risultato di yield from return result def delegating_gen(var): # code here var = yield from sub_gen() # get values from sub_gen def client(): # code here result = delegating_gen() # use delegating_gen result.send(None) # termina istanza sub_gen (IMPORTANTE) ``` ## Ricorsione Il nucleo della ricorsione deve essere costituito da un'istruzione condizionale che permette di gestire casi diversi in base al parametro del metodo. Almeno una delle alternative deve contenere una chiamata ricorsiva del metodo, questa deve risolvere versioni ridotte del compito realizzato dal metodo. Almeno una delle alternative non deve contenere alcona chiamata ricorsiva o deve produrre il valore da restituire, tali alternative costituiscono i casi base o di arresto Example: ```py def factorial(n): if (n == 0): result = 1 # escape event, necessary to avoid infinite recursion else: result = (n*factorial(n-1)) return result ``` ## Funzioni Avanzate ### Assegnazione Di Funzioni A Variabili ```py def funzione(parametri): # code here return espressione # chiamata funzione senza parentesi funzione # restituisce oggetto funzione var = funzione # assegna (per riferimento) la funzione ad una variabile. # la chiamata funziona anche se la func originale viene eliminata (del funzione) var() # chiama la funzione tramite la variabile (usando le parentesi tonde) ``` ### Funzioni Annidate ```py # func_interna locale viene ritornata per riferimento def func_esterna(args): def func_interna(): # func_interna ha accesso a scope funzione esterna (aka Closure) # code here return func_interna # restituisce func_interna func_esterna() # chiamata func_esterna che chiama func_interna (risultato func_esterna è riferimento func_interna) ``` ### Funzioni Passate Per Argomento ```py # funzione passata come argomento viene eseguita (chiamata) all'esecuzione della funzione a cui viene passata def func_esterna(funzione): funzione() # chiama funzione passata come argomento func_esterna() # esecuzione func_esterna chiama func_interna ``` ### Funzioni Incapsulanti Funzioni (Argomenti Wrapper = Argomenti Wrapped) ```py def wrapped(*args): pass def wrapper(*args): # instructions wrapped(*args) # wrapped chiamata con gli argomenti passati a wrapper AS-IS (args = tuple) senza incapsulamento in una tuple ``` ## LAMBDA Functions Possibile uso all'interno delle funzioni. Utile per sostituire funzioni se la logica è semplice. ```py var = lambda argument_list: expression var = lambda argument_list: caso-vero if (condizione) else cado-falso var(args) # invocazione lambda ``` ## Decoratori Entità' chiamabile che prende in input una funzione (come argomento). Eventualmente effettua operazioni con la funzione decorata e la restituisce o sostituisce. vengono eseguiti all'importazione, prima di ogni altra istruzione. ```py # STRUTTURA DECORATORE PARZIALE (SOSTITUISCE FUNZIONE INPUT) def decorator(funzione): # prende in input una funzione def wrapper(): # funzione decoratrice # code here return wrapper # restituisce wrapper (chiamata a func_decorata chiama wrapper) # STRUTTURA DECORATORE COMPLETA (MODIFICA FUNZIONE INPUT) def decorator(funzione): # prende in input una funzione da decorare @functools.wraps(funzione) # keep code inspection available def wrapper(*args, **kwargs): # funzione decoratrice (args, kwargs sono argomenti della funzione decorata) # do something before var_func = funzione(*args, **kwargs) # do something after return var_func # restituisce il risultato della decorazione della funzione return wrapper # restituisce wrapper (chiamata a func_decorata chiama wrapper) @decorator # applicazione del decoratore alla funzione def funzione(): # funzione da decorare # code here return espressione #STRUTTURA DECORATORE COMPLETA PARAMETRIZZATA def decorator(*dec_args, **dec_kwargs): # prende in input argomenti del decoratore def outer_wrapper(func): def inner_wrapper(*args, **kwargs): # do something before var_func = funzione(*args, **kwargs) # do something after return var_func return inner_wrapper return outer_wrapper @decorator(*dec_args, **dec_kwargs) # decoratore parametrizzato invocato come funzione def funzione(): # funzione da decorare # code here return espressione ``` ## Programmazione Ad Oggetti ### Creazione Classe ```py class NomeClasse: # creazione variabile statica (o di classe; condivisa da tutte le istanze) # override possibile tramite subclassing o assegnazione diretta (oggetto.static_var =) # NomeClasse.static_var = cambia l'attributo di classe in tutte le istanze static_var = espressione def __init__(self, valore_1, valore_2): # costruttore di default parametrizzato # attributi sono alias degli argomenti (modifiche in-place cambiano argomenti) self.variabile = valore_1 # creazione variabili di istanza self.__variabile = valore_2 # variabile appartenente ad oggetti della classe e non ai figli, accesso tramite NAME MANGLING @classmethod # metodo agente sulla classe e non sull'oggetto (utile x costruttori alternativi) def metodo_classe(cls, parametri): instruction return cls(parametri) # crea istanza classe (cls accetta anche sottoclassi) # i parametri possono essere variabili statiche (Classe.variabile), variabili di istanza (self.variabile) o valori esterni alla classe def metodo_istanza(self, parametri): # code here return espressione @staticmethod # indica un metodo statico (NECESSARIO) def metodo_statico(parametri): # metodi statici non influenzano variabili di istanza (SELF non necessario) instruction return espressione oggetto = NomeClasse(parametri) # creazione di un oggetto oggetto.variabile = espressione # modifica variabile pubblica oggetto.metodo(parametri) # invocazione metodo di istanza NomeClasse.metodo(parametri) # invocazione metodo statico oggetto._NomeClasse__var_privata # accesso a variabile specificando la classe di appartenenza (NAME MANGLING) ``` ### Metodi Setter e Getter con `@Property` I nomi delle funzioni devono essere diversi dalla variabile (3 nomi diversi). Almeno `@property` & `@parametro.setter` devone essere usati. ```py class NomeClasse: def __init__(self, parametro): self.__parametro = parametro @property # metodo getter def parametro(self): """docstring""" return self.__parametro @parametro.setter def parametro(self, valore): self.__parametro = valore @parametro.deleter # metodo deleter def __parametro(self): del self.__parametro ``` ### `__slots__` L'attributo `__slots__` implementa **Flyweight Design Pattern**: salva gli attributi d'istanza in una tuple e può essere usato per diminuire il costo in memoria inserendovi solo le variabili di istanza (sopprime il dizionario dell'istanza). **Default**: attributi salvati in un dizionario (`oggetto.__dict__`) **Uso**: `__slots_ = [attributi]` `__slots__` non viene ereditato dalle sottoclassi, impedisce l'aggiunta dinamica degli attributi. ### Fluent Interface ```py class NomeClasse(): def __init__(self, parametri): instruction def metodo_1(self): instruction return self # permette il concatenamento di metodi def metodo_2(self): instruction return self oggetto.metodo_1().metodo_2() ``` ### Classi Incapsulate ```py class Class: def __init__(self, parameters): instruction class InnerClass: def __init__(self, parameters): instruction def metodo(self): instruction oggetto_1 = Class(argomenti) # crea classe 'esterna' oggetto_2 = Class.InnerClass(argomenti) # inner class creata come oggetto della classe 'esterna' oggetto.metodo() ``` ### Metodi Speciali I metodi speciali sono definiti dall'uso di doppi underscore; essi permettono l'uso di specifiche funzioni (eventualmente adattate) sugli oggetti definiti dalla classe. ```py class NomeClasse(): def __init__(self, parametri): istruzioni # usato da metodo str() e print() # gestisce le richieste di rappresentazione come stringa def __str__(self): return espressione # return necessario def __len__(self): return espressione # necessario return in quanto len richiede una lunghezza/dimensione def __del__(self): # elimina l'istanza della classe instruction # eventuali istruzioni che avvengono all'eliminazione oggetto = NomeClasse() len(oggetto) # funzione speciale applicata ad un oggetto del oggetto # elimina oggetto ``` #### Special Methods List ```py # se l'operatore no può essere applicato restituire NotImplemented # operatori aritmetici __add__(self, other) # + __sub__(self, other) # - __mul__(self, other) # * __matmul__(self, other) # (@) moltiplicazione matrici __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) # | # operatori aritmetici riflessi # [se self.__dunder__(other) fallisce viene chiamato other.__dunder__(self)] __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 | # operatori aritmetici in-place (=) # implementazione di base (built-in) come self = self other # ! da non implementare per oggetti immutabili ! # ! operatori in-place restituiscono 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) # |= # operatori matematici unari (-, +, 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)] # conversione tipo numerico __complex__(self) __int__(self) # se non definta fall-back a __trunc__() __float__(self) __index__(self) # conversione bin(), hex(), oct() e slicing # operazioni round() e math.trunc(), math.floor(), math.ceil() __round__(self) __trunc__(self) __floor__(self) __ceil__(self) # operatori uguaglianza 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 # operatori uguaglianza riflessi 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 # chiamato quando l'istanza è "chiamata" come funzione # x(arg1, arg2, arg3) è abbreviazione di x.__call__(arg1, arg2, arg3) __call__(self, args) # stringa rappresentazione oggetto per lo sviluppatore __repr__(self) # stringa rappresentazione oggetto per l'utente (usata da print) __str__(self) # specifica formattazione per format(), str.format() [format_spec = format-mini-language] __format__(format_spec) # restituisce valore (num intero) univoco per oggetti che hanno valore eguale # DEVE ESISTERE __EQ__ NELLA CLASSE # METODO GENERALE: hash((self.param_1, self.param_2, ...)) hash tuple componenti oggetto __hash__(self) # rende oggetto iterabile: # - restituendo self (nell'iteratore) # - restituendo un iteratore (nell'iterabile) # - usando yield (nel generatore __iter__) __iter__(self) # restituisce prosimo elemento disponibile, StopIteration altrimenti (scorre iteratore) __next__() # restituisce valore id verità __bool__ # restituisce item associato a key di una sequenza (self[key]) # IndexError se key non appropriata __getitem__(self, key) # operazione assegamento item in sequenza ( self[key] = value) # IndexError se key non appropriata __setitem__(self, key, value) # operazione eliminazione item in sequenza ( del self[key]) # IndexError se key non appropriata __delitem__(self, key) # chiamato da dict.__getitem__() per implementare self[key] se key non è nel dizionario __missing__(self, key) # implementa iterazione del contenitore __iter__(self) # implementa membership test __contains__(self, item) # implementazione issublass(instance, class) __instancecheck__(self, instance) # implementazione issubclass(subclass, class) __subclasscheck__(self, subclass) # implementa accesso ad attributi (obj.name) # chiamato se accade AttributeError o se chiamato da __getattribute__() __getattr__(self, name) # implementa asseganzione valore ad attributo (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. ### Ereditarieta` ```py class ClasseGenitore(): def __init__(self, parametri): instruction def metodo_genitore_1(self): instruction return espressione def metodo_genitore_2(self): instruction return espressione class ClasseFiglia1(classe_genitore): # classe genitore in parentesi per ereditare variabili e metodi def __init__(self, parametri, parametri_genitore): ClasseGenitore.__init__(self, parametri_genitore) # eredita variabili genitore def metodo(self): instruction return espressione def metodo_genitore_1(self): # override metodo (classe figli con metodo omonimo a classe genitore) instruction return espressione class ClasseFiglia2(classe_genitore): # parent class in brackets to inherit properties def __init__(self, parametri, parametri_genitore): super().__init__(parametri_genitore) # metodo differente per ereditare variabili genitore (SELF non necessario) usando SUPER() super(ClasseGenitore, self).__init__(parametri_genitore) # costruttore genitore invocato separatmente def metodo(self): instruction return espressione def metodo_genitore_2(self): # metodo genitore aggiornato super().metodo_genitore_1() # invoca metodo genitore così com'è instruction return espressione classeFiglia1.metodo_genitore_1() # metodo disponibile a classi genitore e figlia classeFiglia1.metodo() # method disponibile solo a classe figlia ``` **MRO (Method Resolution Order) for Multiple Inheritance**: se una sottoclasse ha un metodo ereditato da più classi che condividono lo stesso nome del metodo il MRO è definito dall'ordine di ereditarietà. **Multiple Inheritance Best Practices**: - ereditare un'interfaccia crea un sottotipo (implica relazione is-a) - ereditare un implementazione evita duplicazione del codice tramite riuso - se la classe deve definire un interfaccia dovrebbe essere un ABC esplicita - se la classe deve fornire vari metodi a sottoclassi scorrelate è il caso che sia una classe "minin" le classi mininx non creano nuovi sottotipi ma raggruppano metodi per il riuso. le classi mixin non devono essere instanziate e le loro sottoclassi non devono ereditare solo da esse - le classi mixin dovrebbero avere un suffisso mixin nel nome - classi ABC possono essere mixin, non viceversa - combinazioni di ABC o minxin utili per codice cliente dovrebbero fornire una "classe aggregato" che le riunisce in modo sensato ### Polimorfismo **Note**: python non supporta method overloading ```py # DUCKTYPING Operare con oggetti senza riguardo per il loro tipo, finchè implementano certi protocolli class Classe1: def metodo_1(self): instruction return espressione class Classe2: def metodo_1(self): instruction return espressione # dato che python è un linguaggio dinamico non importa di che tipo (classe) è l'oggetto passato # la funzione invoca il metodo dell'oggetto passato indipendentemente dalla classe dell'oggetto def metodo_polimorfo(oggetto): oggetto.metodo_1() # DEPENDENCY INJECTION CON DUCKTYPING class Classe: def __init__(self, oggetto): self.attributo = oggetto def metodo_polimorfo(self): # la funzione invoca il metodo dell'oggetto passato self.attributo.metodo_1() class ClasseSupporto1: def metodo_1(self): instruction return espressione class ClasseSupporto2: def metodo_1(self): instruction return espressione oggetto_1 = ClasseSupporto1() # oggetto polimorfo creato passando alla classe l'oggetto di supporto come argomento oggetto_0 = Classe(oggetto_1) # invoca il metodo dell'oggetto passato (metodo1) oggetto_0.metodo_polimorfo() oggetto_2 = ClasseSupporto2() # oggetto polimorfo creato passando alla classe l'oggetto di supporto come argomento oggetto_0 = Classe(oggetto_2) oggetto_0.metodo_polimorfo() # invoca il metodo dell'oggetto passato ``` ### Operator Overloading **Regola fondamentale operatori**: restisuire *sempre* un oggetto, se operazione fallisce restituire `NotImplemented` Limitations of operator overloading: - no overloading di tipi built-in - no creazione nuovi operatori - no overloading operatori is, and, or, not''' ### Astrazione Le **interfacce** sono classi astratte con *tutti* metodi astratti, sono usate per indicare quali metodi come le classi figlie *devono* avere. Le interfaccie hanno *solo* una lista di metodi astratti. Le **classi astratte** hanno *almeno* un metodo astratto; classi figlie che ereditano da una classe astratta *devono* implementare i metodi astratti. Le classi astartte *non possono* essere instanziate (non possono generare oggetti). Le sottoclassi virtuali vengono usate per includere classi di terze parti come sottoclassi di una classe propria. Esse vengono riconosciute come appartenenti a classe genitore senza però doverne implementare i metodi. I decoratori `@Classe.register` o `Classe.register(sottoclasse)` servono per marcare sottoclassi. ```py from abc import abstractmethod, ABC class ClasseAstratta(ABC): # classe astratta DEVE EREDITARE da classe parente ABC def __init__(self, parametri): instruction def metodo_genitore(self): instruction return espressione @abstractmethod # metodo astratto DEVE essere machiato con decoratore @abstractmethod def metodo_astratto(self): pass # metodo astratto DEVE essere sovrascritto (può essere non vuoto) #(.super() per invocarlo nella classe concreta) class ClasseFiglia(classe_genitore): # classe genitore in parentesi per ereditare variabili e metodi def __init__(self, parametri, parametri_genitore): classe_genitore.__init__(self, parametri_genitore) # necessario per ereditare variabili genitore def metodo(self): instruction return espressione def metodo_genitore(self): # override metodo (classe figli con metodo omonimo a classe genitore) instruction return espressione def metodo_astratto(self): # implementazione metodo astratto ereditato da classe astratta (NECESSARIA) mediante override instruction return espressione ``` ## Exception Handling ```py # CONTROLLO ASERZIONI assert condizione, 'mesaaggio di errore' # se l'asserzione risulta falsa mostra un messaggio d'errore # i tipi di eccezioni sono delle classi. ve ne sono alcune predefinite e se ne possonoi creare di personalizzate # errori particolari sono oggetti di una particolare classe di eccezioni che a sua volta è figlia della classe di eccezioni base (exception) class CustomExceptionError(Exception): # DEVE in qualche modo ereditare dalla classe exception (anche in passaggi successivi di eredità) pass # o istruzioni # blocco try contiene il codice che potrebbe causare un eccezione # codice dentro try e dopo l'errore non viene eseguito try: instruzioni raise CustomExceptionError("message") # attiva l'eccezione # except prende il controllo dell'error handling senza passsare per l'interprete # clocco eseguito se avviene un errore in try # except errore specificato dalla classe except ExceptionClass: # code here # messaggio d'errore di default non viene mostrato # il programma non si ferma # except su errori vari except: # code here # blocco eseguito se non avviene l'eccezione else: # code here # blocco eseguito in tutti i casi, codice di pulizia va qui finally: # code here ``` ## File ### Apertura Di Un File Modalità apertura file testo: - `w`: write, sovrarscrive contenuto del file - `r`: read, legge contenuto file - `a`: append, aggiunge contenuto al file - `w+`: write & read - `r+`: write & read & append - `a+`: append & read - `x`: exclusive creation, se il file esiste già -> `FileExistError` (modalità estesa di write) Modalità apertura file binario: - `wb`: write, sovrarscrive contenuto del file - `rb`: read, legge contenuto file - `ab`: append, aggiunge contenuto al file - `w+b`: write & read - `r+b`: write & read & append - `a+b`: append & read - `xb`: exclusive creation, se il file esiste già -> `FileExistError` (modalità estesa di write) **Note**: GNU/Linux e MacOSX usano `UTF-8` ovunque mentre windows usa `cp1252`, `cp850`, `mbcs`, `UTF-8`. Non fare affidamento a encoding di default ed usare **esplicitamente** `UTF-8`. ```py oggetto = open('filename', mode='r', encoding='utf-8') # encoding MUST BE utf-8 for compatibility # filename può essere il percorso assoluto della posizione del file (default: file creato nella cartella del codice sorgente) # doppio slash per evitare escape di \ with open('filename') as file: istruzioni_su_file # blocco usa nome_file per indicare il file # CHIUSURA DI UN FILE oggetto.close() # SCRITTURA IN UN FILE oggetto.write(stringa) # scrive singola stinga nel file oggetto.writelines(*stringhe) # scrive stringhe multiple sul file # LETTURA DA UN FILE oggetto.read() # restituisce TUTTO il contenuto del file (anche escape sequence) e posuzione il "cursore" a fondo file oggetto.seek(0) # restituisce 0 (zero) e posiziona il cursore a inizio file oggetto.readlines() # restituisce lista linee file (ATTENZIONE: tiene tutto in memoria, cautela con file grandi) oggetto.readline() # restituisce singola linea file # CONTROLLO ESISTENZA FILE import os, sys if os.path.isfile('filepath'): # controlla esistenza file (TRUE se esiste) # code here else: # code here sys.exit() # esce dal programma non eseguendo il cosice successivo # PICKLE-ING import pickle, classe # importa modulo pickle e classe da trasformare in byte stream fileObj = open('file.dat', 'wb') # apre file obj = classFile.classe(parameters) # crea oggetto pickle.dump(obj, fileObj) # traduce la gerarchia dell'oggetto in un byte stream (.dat contiene byte) fileObj.close() import pickle # importa modulo pickle per tradurre byte stream, classe oggetto non necessaria (oggetto codificato nel file) fileObj = open('file.dat', 'rb') obj = pickle.load(fileObj) # traduce oggetto da fileObj obj.method() # utilizza metodo dell'oggetto fileObj.close() ``` ## TIMEIT ```py timeit.timeit(' statement ', setup, timer=default_timer, number=num_of_trials) # statement: codice da testare (passato come stringa) # setup: codice di setup # timer: funzione timer (default_timer = time.perf_counter()) # number: numero di test eseguiti (num esecuzioni statement) ``` ## CONTEXTLIB Utility per operazioni coinvolgenti with statement. In un generatore decorato con @contextmanager lo statement yield divide il codice in 2 parti: - **PRE-YIELD**: eseguito alla chiamata __enter__ - **POST-YIELD**: eseguito alla chiamata __exit__ `yield` restituisce il valore da associare al "with-target" ```py # restituisce context manager che chiude thing alla conclusione del blocco contextlib.closing(thing) # costruisce context manager partendo da funzione restituente generatore-iteratore # senza implementare classe e protocollo @contextlib.contextmanager def generator(): # code here yield variabile # code here # classe bese per definire context manager basati su classi # che possono essere anche usati come decoratori class contextlib.ContextDecorator # context manager che permette l'inserimento di indefiniti context manager # alla finde del blocco with ExitStack chiama __exit__ in ordine LIFO class contextlib.ExitStack ``` ## COPY Opererazioni di copia (*shallow copy*) e copia profonda (*deep copy*) **SHALLOW COPY**: copia il "contenitore" e i riferimenti al contenuto **DEEP COPY**: copia il "contenitore" e i contenuti (no riferimento) ```py copy(x) # restituisce shallow copy di xor deepcopy(x) # restituisce shallow copy di x ```