Come usare le dataclass Python
Tutto in Python è un oggetto, o almeno così dice il proverbio. Se vuoi creare i tuoi oggetti personalizzati, con le loro proprietà e metodi, usi l' class
oggetto di Python per farlo accadere. Ma creare classi in Python a volte significa scrivere un sacco di codice ripetitivo e boilerplate per impostare l'istanza della classe dai parametri ad essa passati o per creare funzioni comuni come operatori di confronto.
Le classi di dati, introdotte in Python 3.7 (e trasferite in Python 3.6), forniscono un modo pratico per rendere le classi meno prolisse. Molte delle cose comuni che fai in una classe, come istanziare le proprietà dagli argomenti passati alla classe, possono essere ridotte a poche istruzioni di base.
Esempio di classe di dati Python
Ecco un semplice esempio di una classe convenzionale in Python:
libro di classe:'' 'Oggetto per il monitoraggio di libri fisici in una collezione.' ''
def __init __ (self, name: str, weight: float, shelf_id: int = 0):
self.name = nome
self.weight = peso # in grammi, per il calcolo della spedizione
self.shelf_id = shelf_id
def __repr __ (self):
return (f "Book (name = {self.name! r},
peso = {self.weight! r}, shelf_id = {self.shelf_id! r}) ")
Il problema più grande qui è il modo in cui ciascuno degli argomenti passati __init__
deve essere copiato nelle proprietà dell'oggetto. Questo non è così male se si sta solo trattare con Book
, ma cosa succede se si ha a che fare con Bookshelf
, Library
, Warehouse
e così via? Inoltre, più codice devi digitare a mano, maggiori sono le possibilità che tu commetta un errore.
Ecco la stessa classe Python, implementata come una classe di dati Python:
from dataclasses import dataclass @dataclass class Book: '' 'Oggetto per tenere traccia dei libri fisici in una collezione.' '' name: str weight: float shelf_id: int = 0
Quando si specificano proprietà, chiamate campi, in una classe di dati, @dataclass
genera automaticamente tutto il codice necessario per inizializzarli. Conserva anche le informazioni sul tipo per ogni proprietà, quindi se usi un linter di codice come mypy
, ti assicurerai di fornire i giusti tipi di variabili al costruttore della classe.
Un'altra cosa che @dataclass
fa dietro le quinte è creare automaticamente il codice per una serie di metodi dunder comuni nella classe. Nella classe convenzionale sopra, abbiamo dovuto crearne una nostra __repr__
. Nella classe di dati, questo non è necessario; @dataclass
genera il __repr__
per te.
Una volta creata, una classe di dati è funzionalmente identica a una classe normale. Non vi è alcuna penalizzazione delle prestazioni per l'utilizzo di una classe di dati, ad eccezione dell'overhead minimo del decoratore durante la dichiarazione della definizione della classe.
Personalizza i campi della classe di dati Python con la field
funzione
Il modo predefinito in cui funzionano le classi di dati dovrebbe essere corretto per la maggior parte dei casi d'uso. A volte, tuttavia, è necessario mettere a punto il modo in cui vengono inizializzati i campi nella classe dati. Per fare ciò, puoi usare la field
funzione.
da dataclass import dataclass, campo da digitare import List @dataclass class Book: '' 'Oggetto per il monitoraggio di libri fisici in una collezione.' '' nome: str condizione: str = field (compare = False) weight: float = field (default = 0.0, repr = False) shelf_id: int = 0 capitoli: List [str] = field (default_factory = list)
Quando imposti un valore predefinito su un'istanza di field
, cambia il modo in cui il campo è impostato a seconda dei parametri che dai field
. Queste sono le opzioni più comunemente usate per field
(ce ne sono altre):
default
: Imposta il valore predefinito per il campo. È necessario utilizzaredefault
se a) si utilizzafield
per modificare qualsiasi altro parametro per il campo eb) si desidera impostare un valore predefinito sul campo in cima a quello. In questo caso usiamodefault
per impostareweight
su0.0
.default_factory
: Fornisce il nome di una funzione, che non accetta parametri, che restituisce un oggetto da utilizzare come valore predefinito per il campo. In questo caso, vogliamochapters
essere un elenco vuoto.repr
: Per impostazione predefinita (True
), controlla se il campo in questione viene visualizzato nel generato automaticamente__repr__
per la classe di dati. In questo caso non vogliamo che il peso del libro venga mostrato in__repr__
, quindi lorepr=False
omettiamo.compare
: Per impostazione predefinita (True
), include il campo nei metodi di confronto generati automaticamente per la classe di dati. In questo caso, non vogliamocondition
essere utilizzati come parte del confronto per due libri, quindi impostiamocompare=
False
.
Si noti che abbiamo dovuto modificare l'ordine dei campi in modo che i campi non predefiniti vengano prima.
Utilizzare __post_init__
per controllare l'inizializzazione della classe di dati Python
A questo punto probabilmente ti starai chiedendo: se il __init__
metodo di una classe di dati viene generato automaticamente, come posso ottenere il controllo sul processo di inizializzazione per apportare modifiche più dettagliate?
Inserisci il __post_init__
metodo. Se includi il __post_init__
metodo nella definizione della classe di dati, puoi fornire istruzioni per modificare i campi o altri dati dell'istanza.
da dataclass import dataclass, campo da digitare import List @dataclass class Book: '' 'Oggetto per il monitoraggio di libri fisici in una collezione.' '' name: str weight: float = field (default = 0.0, repr = False) shelf_id: int = field (init = False) capitoli: List [str] = field (default_factory = list) condizione: str = field (default = "Good", compare = False) def __post_init __ (self): if self.condition == "Scartato ": self.shelf_id = Nessun altro: self.shelf_id = 0
In questo esempio, abbiamo creato un __post_init__
metodo per impostare shelf_id
per None
se la condizione del libro è inizializzato come "Discarded"
. Nota come usiamo field
inizializzare shelf_id
, e passiamo init
come False
a field
. Ciò significa shelf_id
che non verrà inizializzato in __init__
.
Utilizzare InitVar
per controllare l'inizializzazione della classe di dati Python
Un altro modo per personalizzare l'impostazione della classe di dati Python è usare il InitVar
tipo. Ciò consente di specificare un campo che verrà passato a __init__
e poi a __post_init__
, ma non verrà memorizzato nell'istanza della classe.
Utilizzando InitVar
, è possibile inserire parametri durante l'impostazione della classe di dati che vengono utilizzati solo durante l'inizializzazione. Un esempio:
da dataclass import dataclass, field, InitVar digitando import List @dataclass class Book: '' 'Oggetto per tenere traccia dei libri fisici in una collezione.' '' nome: str condizione: InitVar [str] = Nessuno weight: float = field (default = 0.0, repr = False) shelf_id: int = field (init = False) capitoli: List [str] = field (default_factory = list) def __post_init __ (self, condition): if condition == "Discarded": self.shelf_id = Nessun altro: self.shelf_id = 0
L'impostazione del tipo di un campo su InitVar
(con il suo sottotipo che è il tipo di campo effettivo) segnala @dataclass
di non trasformare quel campo in un campo di classe dati, ma di passare i dati __post_init__
come argomento.
In questa versione della nostra Book
classe, non archiviamo condition
come campo nell'istanza della classe. Usiamo solo condition
durante la fase di inizializzazione. Se troviamo che condition
era impostato su "Discarded"
, impostiamo shelf_id
su None
- ma non memorizziamo condition
nell'istanza della classe.
Quando usare le classi di dati Python e quando non usarle
Uno scenario comune per l'utilizzo di dataclass è in sostituzione di namedtuple. Le classi dati offrono gli stessi comportamenti e altro ancora e possono essere rese immutabili (come lo sono le coppie nominate) semplicemente usando @dataclass(frozen=True)
come decoratore.
Another possible use case is replacing nested dictionaries, which can be clumsy to work with, with nested instances of dataclasses. If you have a dataclass Library
, with a list property shelves
, you could use a dataclass ReadingRoom
to populate that list, and then add methods to make it easy to access nested items (e.g., a book on a shelf in a particular room).
But not every Python class needs to be a dataclass. If you’re creating a class mainly as a way to group together a bunch of static methods, rather than as a container for data, you don’t need to make it a dataclass. For instance, a common pattern with parsers is to have a class that takes in an abstract syntax tree, walks the tree, and dispatches calls to different methods in the class based on the node type. Because the parser class has very little data of its own, a dataclass isn’t useful here.
How to do more with Python
- Get started with async in Python
- How to use asyncio in Python
- How to use PyInstaller to create Python executables
- Cython tutorial: How to speed up Python
- How to install Python the smart way
- How to manage Python projects with Poetry
- How to manage Python projects with Pipenv
- Virtualenv e venv: spiegazione degli ambienti virtuali Python
- Python virtualenv e venv cosa fare e cosa non fare
- Spiegazione del threading e dei sottoprocessi di Python
- Come utilizzare il debugger Python
- Come usare timeit per profilare il codice Python
- Come usare cProfile per profilare il codice Python
- Come convertire Python in JavaScript (e viceversa)