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' classoggetto 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  BookshelfLibraryWarehousee 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 utilizzare defaultse a) si utilizza  field per modificare qualsiasi altro parametro per il campo eb) si desidera impostare un valore predefinito sul campo in cima a quello. In questo caso usiamo  default per impostare  weight su  0.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, vogliamo  chapters 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 lo  repr=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 vogliamo  condition essere utilizzati come parte del confronto per due libri, quindi impostiamo  compare=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 conditiondurante 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)