Prima di affrontare l’argomento “Le Data Classes in Python” occorre fare una piccola premessa su quelle che sono le Type Annotations.

TYPE ANNOTATIONS

Le annotazioni sono funzionalità di Python che suggeriscono agli sviluppatori i tipi di dati delle variabili o dei parametri di funzione e il tipo restituito. Esistono principalmente due tipi di annotazioni in Python: annotazioni di funzioni e annotazioni di variabili. La sintassi per l’annotazione della funzione è mostrata di seguito:

def func(a: <expression>, b: <expression>) -> <expression>:

    pass

Esempi di annotazioni di funzioni e variabili vengono mostrate nel codice sotto riportato. E’ importante sottolineare che anche le classi come riportato nel codice possono far uso di annotazioni, questo aspetto è fondamentale per le Data Classes.

from typing import List
#Diamo un'occhiata a un paio di esempi. I tre esempi seguenti 
#accettano due parametri e restituiscono la loro somma. 
#Come puoi vedere, <expression> può essere qualsiasi cosa. 
#Tutte e tre sono definizioni di funzioni valide ma mostra 
#diversi modi di utilizzare le annotazioni di funzione.
#Nota che se ci sono parametri con valori predefiniti, 
#le annotazioni devono sempre precedere il valore predefinito 
#di un parametro come hai visto nel terzo esempio sotto riportato.
def func1(num1:str = "1st param", num2:str = "2nd param") -> str:
    return num1 + num2
def func2(num1: int, num2: int) -> int:
    return num1 + num2
def func3(num1: int, num2: int=10) -> int :
    return num1 + num2

print(func1('pippo','pluto'))
print(func2(20,30))
print(func3(80))
print(func2.__annotations__)

#Annotazioni di variabili
name: str
city: str = 'Mysor'
age: int = 35

# Nell'esempio seguente, la funzione quadrato si aspetta 
# un parametro intero num e restituisce i quadrati di tutti 
# i numeri da 0 a num. La variabile squares è dichiarata come 
# List [int] a indicare che contiene un elenco di numeri interi. 
# Allo stesso modo, anche il tipo restituito dalla funzione è List [int] . 
# Successivamente, square .__ annotations__ fornisce 
# annotazioni locali alla funzione e __annotations__ fornisce 
# annotazioni globali. 


squares: List[int] = []
def square(num: int) -> List[int]:
    for i in range(num):
        squares.append(i**2)
    return squares
print(square(10))
print(square.__annotations__)
print(__annotations__)
#Anche le classi possono usare le data Annotations
class MyClass:
    nome: str
    cognome:str
    
    def __init__(self,nome,cognome):
        self.nome=nome
        self.cognome=cognome

    def denominazione(self):
        print(f'{self.nome} {self.cognome}')

m=MyClass(nome='Mario',cognome='Rossi')
print(m.denominazione())
print(m.nome)
print(m.cognome)

ANNOTAZIONI DI VARIABILI

Per le annotazioni delle variabili esamineremo quelle introdotte in PEP 526 in Python 3.6. L’esempio più semplice di annotazione delle variabili è mostrato di seguito. Alcune delle regole per scrivere le annotazioni delle variabili sono:

  • Non dovrebbe esserci spazio prima dei due punti.
  • Dovrebbe esserci uno spazio dopo i due punti.

myVar: int=10

È possibile accedere alle annotazioni di funzione utilizzando l’attributo __annotations__ sull’oggetto funzione. Il __annotations__ dà risultati sotto forma di un dizionario in cui chiavi e valori sono mappati rispettivamente ai parametri e annotazioni.

PERCHE’ USARE LE ANNOTAZIONI

  • Controllo del tipo: strumenti di terze parti come mypy possono essere utilizzati per rilevare errori di tipo prima del runtime.
  • Suggerimenti sul tipo per IDE: se l’annotazione è stata utilizzata per indicare il tipo di dati dei parametri, gli IDE suggeriscono il tipo di dati del parametro, etc. Ciò aiuterà a rilevare gli errori di tipo mentre scrivi il codice nell’IDE come Pycharm.
  • Completamento automatico: le annotazioni aiutano anche nel completamento automatico del codice e semplificano la vita.
  • Leggibilità: le annotazioni rendono anche il codice più leggibile.

LE DATA CLASSES

Le data classes sono un’aggiunta che è stata fatta alla libreria standard di Python che serve ad arricchire di significato le classi stesse prevalentemente quando queste contengono la definizione di dati. Il principio delle data Classes ruota attorno a un decorator che si chiama dataclass. Tale decoratore aggiunge in automatico una serie di metodi. Il primo di questi è __init__ nella forma illustrata in figura:

Metodi generati dal decorator

Il decorator implementa poi __repr__, __eq__, __ne__. __repr__ serve a rappresentare il contenuto di un’istanza attraverso i suoi dati. Equal (__eq__) e Not Equal (__ne__) sono implementati per adattarsi alla specifica classe. In particolare confronta tutti i campi in sequenza e stabilisce se le istanze di una classe sono uguali oppure no.

PARAMETRI DEL DECORATORE

Il decorator ammette dei parametri, i più importanti sono illustrati in figura.

  • init: di default vale True e permette di generare l’inizializzatore __init__. Se lo impostiamo a false non possiamo più generare istanze della classe nel modo riportato in figura.
  • repr: di default questo valore vale True quindi il metodo viene generato. Consente di avere una visualizzazione più chiara dei campi dell’istanza. Se si imposta a False si ottiene una visualizzazione più grezza quando si fa la print(mc).
  • order: consente di generare una serie di metodi che rappresentano la ridefinizione di operatori standard come <, <=, >, >=. Di default il suo valore è impostato a False, quindi questi metodi non vengono generati. I confronti se impostato a True vengono fatti in base ai valori attuali dei campi delle istanze.
  • frozen: per default il valore di questo parametro è False, questo significa che possiamo modificare i valori dei dati nelle istanze di classe. Se si imposta a True non è più possibile modificare tali valori.
Metodi generati dal decorator
Parametri del decoratore
from dataclasses import dataclass
#A partire dalle annotazioni di tipo Python è in grado di inferire
#la presenza di tipi di oggetto Field che rappresentano i campi della classe
@dataclass(init=True,repr=True,order=True,frozen=False)
class MyClass(object):
    nome: str
    cognome: str

mc1=MyClass(nome='Luigi',cognome='Rossi')
#Il metodo repr facendo una print(mc) ci fornisce una stampa più compatta.
print(mc1)
mc2=MyClass(nome='Luigi',cognome='Rossi')
print(mc2)
print(mc1<mc2)
print(mc1>mc2)
print(mc1==mc2)

APPROFONDIMENTO

Le annotazioni di tipo (Type Annotations) in Python sono una caratteristica introdotta per la prima volta nella versione 3.5 che consente di aggiungere informazioni sui tipi ai parametri delle funzioni e ai valori di ritorno. Anche se Python è un linguaggio dinamico, cioè non richiede la dichiarazione esplicita dei tipi, le annotazioni di tipo offrono una maggiore leggibilità del codice e possono essere utilizzate da strumenti di verifica statica del tipo, come mypy, per rilevare errori.

Sintassi Base

La sintassi delle annotazioni di tipo in Python è semplice e intuitiva. Ecco un esempio di come annotare i tipi per una funzione:

def somma(a: int, b: int) -> int:
    return a + b

In questo esempio:

a: int indica che il parametro a dovrebbe essere un intero (int).

b: int indica che il parametro b dovrebbe essere un intero (int).

-> int indica che la funzione dovrebbe restituire un intero (int).

Annotazioni per Altri Tipi

Python supporta l’annotazione di una vasta gamma di tipi, inclusi tipi complessi come liste, dizionari, tuple, ecc., utilizzando il modulo typing. Di seguito alcuni esempi comuni:

Liste: List[int] per indicare una lista di interi.

Dizionari: Dict[str, int] per indicare un dizionario con chiavi di tipo stringa e valori di tipo intero.

Tuple: Tuple[int, str] per indicare una tupla che contiene un intero e una stringa.

Opzionali: Optional[int] per indicare che un valore può essere un intero o None.

Esempio di utilizzo:

from typing import List, Dict, Tuple, Optional

def process_data(data: List[int], settings: Dict[str, str]) -> Tuple[int, Optional[str]]:
       # Implementazione della funzione
       pass

Tipi Complessi e Customizzati

È possibile definire tipi personalizzati utilizzando classi o NamedTuple:

from typing import NamedTuple

class Punto(NamedTuple):
       x: int
       y: int

       def distanza(p1: Punto, p2: Punto) -> float:
          return ((p1.x – p2.x) ** 2 + (p1.y – p2.y) ** 2) ** 0.5

Annotazioni nei Dizionari e nelle Variabili

Le annotazioni di tipo non sono limitate alle funzioni. È possibile annotare anche le variabili e i campi dei dizionari:

from typing import Dict

eta: Dict[str, int] = {‘Alice‘: 30, ‘Bob‘: 25}

Verifica Statica del Tipo

Le annotazioni di tipo sono puramente informative e non vengono applicate in fase di esecuzione. Tuttavia, strumenti come mypy possono essere utilizzati per verificare il rispetto delle annotazioni e rilevare errori prima dell’esecuzione.

Esempio Completo

from typing import List, Dict, Tuple

def analizza_voti(voti: List[int]) -> Tuple[float, Dict[str, int]]:
      media = sum(voti) / len(voti)
      conteggio = {str(voto): voti.count(voto) for voto in set(voti)}
      return media, conteggio

voti_studenti = [8, 7, 10, 6, 8, 9, 10, 7]
media_voti, distribuzione = analizza_voti(voti_studenti)

In questo esempio:

voti: List[int] indica che il parametro voti è una lista di interi.

-> Tuple[float, Dict[str, int]] indica che la funzione restituisce una tupla contenente un float e un dizionario.

Conclusione

Le annotazioni di tipo in Python offrono un potente strumento per migliorare la leggibilità del codice e aiutare nella verifica statica dei tipi. Anche se non sono obbligatorie, il loro utilizzo è altamente consigliato nei progetti di medie e grandi dimensioni, dove la chiarezza e la coerenza del codice sono cruciali.

LE DATA CLASSES IN PYTHON

In Python, le data classes (o “classi di dati”) sono una funzionalità introdotta nella versione 3.7 per semplificare la creazione di classi che sono principalmente utilizzate per memorizzare dati. Le data classi automatizzano la generazione di metodi comuni come __init__, __repr__, __eq__, riducendo il codice boilerplate.

Descrizione delle principali caratteristiche delle data classi:

1. Decoratore @dataclass:

Le data classi sono definite utilizzando il decoratore @dataclass, che è applicato sopra una classe. Questo decoratore automatizza la creazione di metodi speciali.

2. Campi della data class:

All’interno di una data class, ogni attributo definito è automaticamente considerato un campo. I campi possono avere valori di default e possono essere tipizzati utilizzando annotazioni di tipo.

3. Generazione automatica di metodi:

__init__: Genera automaticamente un metodo costruttore che inizializza i campi della classe.

__repr__: Fornisce una rappresentazione stringa leggibile della classe, utile per il debugging.

__eq__: Implementa l’uguaglianza strutturale, confrontando i valori dei campi.

• Altri metodi come __lt__, __le__, __gt__, __ge__ possono essere generati automaticamente aggiungendo parametri opzionali al decoratore.

4. Ereditarietà:

Le data classi supportano l’ereditarietà come le classi regolari in Python. È possibile estendere una data class con altre data class o con classi normali.

5. Ordine dei campi:

È possibile specificare se la data class dovrebbe avere un ordine, rendendo così gli oggetti comparabili in base ai loro campi.

6. Immutabilità:

Le data classi possono essere rese immutabili usando l’opzione frozen=True nel decoratore, trasformandole in strutture dati immutabili simili alle tuple.

Esempio di utilizzo di una data class:

from dataclasses import dataclass

@dataclass
class Persona:
     nome: str
     cognome: str
     età: int

p = Persona(nome=”Mario“, cognome=”Rossi“, età=30)
print(p) # Output: Persona(nome=’Mario’, cognome=’Rossi’, età=30)

In questo esempio, la classe Persona è definita come una data class. Il decoratore @dataclass si occupa di generare automaticamente il metodo __init__, __repr__ e altri metodi speciali basati sui campi definiti (nome, cognome, età).

Le data classi sono particolarmente utili quando si ha a che fare con oggetti che rappresentano dati strutturati, rendendo il codice più pulito e meno soggetto a errori.

LINK AL CODICE GITHUB

GITHUB

LINK AI POST PRECEDENTI

PREVIOUS POST LINKS