Jak używać klas danych Pythona

Wszystko w Pythonie jest obiektem, a przynajmniej tak się mówi. Jeśli chcesz tworzyć własne obiekty niestandardowe, z ich własnymi właściwościami i metodami, możesz classto zrobić za pomocą obiektu Pythona . Jednak tworzenie klas w Pythonie czasami oznacza pisanie wielu powtarzalnych, standardowych kodów w celu skonfigurowania instancji klasy na podstawie przekazanych do niej parametrów lub utworzenia wspólnych funkcji, takich jak operatory porównania.

Klasy danych, wprowadzone w Pythonie 3.7 (i przeniesione wstecz do Pythona 3.6), zapewniają wygodny sposób na zmniejszenie szczegółowości klas. Wiele typowych czynności wykonywanych w klasie, takich jak tworzenie instancji właściwości z argumentów przekazywanych do klasy, można sprowadzić do kilku podstawowych instrukcji.

Przykład klasy danych w języku Python

Oto prosty przykład konwencjonalnej klasy w Pythonie:

książka klasowa:

'' 'Obiekt do śledzenia fizycznych książek w kolekcji.' ''

def __init __ (self, name: str, weight: float, shelf_id: int = 0):

self.name = imię

self.weight = waga # w gramach, do obliczenia kosztów wysyłki

self.shelf_id = shelf_id

def __repr __ (self):

return (f "Książka (name = {self.name! r},

waga = {self.weight! r}, shelf_id = {self.shelf_id! r}) ")

Największym bólem głowy jest tutaj sposób, w jaki każdy z przekazanych argumentów  __init__ musi zostać skopiowany do właściwości obiektu. To nie jest tak źle, jeśli masz do czynienia tylko z  Book, ale co zrobić, jeśli masz do czynienia z  BookshelfLibraryWarehousei tak dalej? Ponadto im więcej kodu musisz wpisać ręcznie, tym większe jest prawdopodobieństwo, że popełnisz błąd.

Oto ta sama klasa Pythona, zaimplementowana jako klasa danych Pythona:

from dataclasses import dataclass @dataclass class Książka: '' 'Obiekt do śledzenia fizycznych książek w kolekcji.' '' name: str waga: float shelf_id: int = 0 

Po określeniu właściwości zwanych  polami  w klasie danych  @dataclass automatycznie generuje cały kod potrzebny do ich zainicjowania. Zachowuje również informacje o typie dla każdej właściwości, więc jeśli użyjesz lintera kodu, takiego jak  mypy, zapewni to, że dostarczasz odpowiednie rodzaje zmiennych do konstruktora klasy.

Inną rzeczą,  @dataclass która robi się za kulisami, jest automatyczne tworzenie kodu dla wielu popularnych metod dunder w klasie. W konwencjonalnej klasie powyżej musieliśmy stworzyć własną  __repr__. W klasie danych jest to niepotrzebne; @dataclass generuje  __repr__ dla Ciebie.

Raz utworzona klasa danych jest funkcjonalnie identyczna jak zwykła klasa. Używanie klasy danych nie powoduje obniżenia wydajności, z wyjątkiem minimalnego narzutu dekoratora podczas deklarowania definicji klasy.

Dostosuj pola klasy danych języka Python za pomocą  field funkcji

Domyślny sposób działania klas danych powinien być odpowiedni dla większości przypadków użycia. Czasami jednak trzeba dostosować sposób inicjalizacji pól w klasie danych. Aby to zrobić, możesz użyć  field funkcji.

z klas danych import dataclass, pole z wpisania import List @dataclass class Książka: '' 'Obiekt do śledzenia fizycznych książek w kolekcji.' '' name: str stan: str = pole (porównaj = fałsz) waga: float = pole (domyślnie = 0.0, repr = False) shelf_id: int = 0 chapters: List [str] = field (default_factory = list) 

Ustawienie wartości domyślnej na wystąpienie programu  fieldzmienia sposób konfiguracji pola w zależności od podanych parametrów  field. Oto najczęściej używane opcje field (są inne):

  • default: Ustawia domyślną wartość pola. Musisz użyć, defaultjeśli a) używasz  field do zmiany innych parametrów pola i b) chcesz ustawić domyślną wartość w polu na górze. W tym przypadku używamy  default do zestawu  weight do  0.0.
  • default_factory: Podaje nazwę funkcji, która nie przyjmuje parametrów, a zwraca obiekt, który ma służyć jako wartość domyślna pola. W tym przypadku chcemy  chapters być pustą listą.
  • repr: Domyślnie ( True) kontroluje, czy dane pole pojawia się w automatycznie wygenerowanym  __repr__ dla klasy danych. W tym przypadku nie chcemy, aby waga książki była pokazywana w  __repr__, więc używamy  repr=False jej pomijania.
  • compare: Domyślnie ( True) włącza pole do metod porównania automatycznie generowanych dla klasy danych. Tutaj nie chcemy  condition być wykorzystywane jako część porównania dla dwóch książek, więc ustawiliśmy  compare=False.

Zwróć uwagę, że musieliśmy dostosować kolejność pól, aby pola inne niż domyślne były pierwsze.

Służy  __post_init__ do sterowania inicjalizacją klasy danych języka Python

W tym momencie prawdopodobnie zastanawiasz się: jeśli  __init__ metoda klasy danych jest generowana automatycznie, w jaki sposób mogę uzyskać kontrolę nad procesem init, aby wprowadzić drobniejsze zmiany?

Wprowadź  __post_init__ metodę. Jeśli __post_init__dołączysz tę  metodę do definicji klasy danych, możesz podać instrukcje dotyczące modyfikowania pól lub innych danych instancji.

z dataclass import dataclass, pole z wpisania import List @dataclass class Książka: '' 'Obiekt do śledzenia fizycznych książek w kolekcji.' '' name: str waga: float = field (default = 0.0, repr = False) shelf_id: int = field (init = False) chapters: List [str] = field (default_factory = list) condition: str = field (default = "Good", compare = False) def __post_init __ (self): if self.condition == "Discarded ": self.shelf_id = Nikt inny: self.shelf_id = 0 

W tym przykładzie, stworzyliśmy  __post_init__ metodę ustalania shelf_id się  None , jeśli warunek tej książki jest inicjowany jako  "Discarded". Zwróć uwagę, jak używamy  field do inicjalizacji  shelf_idi przekazujemy  init jako  False do  field. Oznacza to,  shelf_id że nie zostanie zainicjowany w  __init__.

Służy  InitVar do sterowania inicjalizacją klasy danych języka Python

Innym sposobem dostosowania konfiguracji klasy danych w języku Python jest użycie  InitVar typu. Pozwala to określić pole, które zostanie przekazane,  __init__ a następnie do  __post_init__, ale nie będzie przechowywane w instancji klasy.

Używając InitVar, możesz pobrać parametry podczas konfigurowania klasy danych, które są używane tylko podczas inicjalizacji. Przykład:

z klas danych import dataclass, field, InitVar z wpisania import List @dataclass class Książka: '' 'Obiekt do śledzenia fizycznych książek w kolekcji.' '' name: str stan: InitVar [str] = brak waga: float = pole (domyślnie = 0.0, repr = False) shelf_id: int = field (init = False) chapters: List [str] = field (default_factory = list) def __post_init __ (self, condition): if condition == "Discarded": self.shelf_id = Nikt inny: self.shelf_id = 0 

Ustawienie typu pola na  InitVar (z jego podtypem będącym rzeczywistym typem pola) sygnalizuje, że  @dataclass nie należy przekształcać tego pola w pole klasy danych, ale przekazywać dane  __post_init__ jako argument.

W tej wersji naszej  Book klasy nie przechowujemy  condition jako pola w instancji klasy. Używamy tylko conditionpodczas fazy inicjalizacji. Jeśli stwierdzimy, że  condition została ustawiona na  "Discarded", ustawimy ją  shelf_id na  None - ale nie przechowujemy  condition w instancji klasy.

Kiedy używać klas danych Pythona - a kiedy ich nie używać

Jednym z typowych scenariuszy używania klas danych jest zastąpienie metody namedtuple. Klasy danych oferują te same zachowania i więcej, i można je uczynić niezmiennymi (tak jak krotki nazw), używając po prostu  @dataclass(frozen=True) jako dekoratora.

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 i venv: wyjaśniono wirtualne środowiska Pythona
  • Python virtualenv i venv co robić, a czego nie
  • Wyjaśnienie wątków i podprocesów w Pythonie
  • Jak korzystać z debugera Pythona
  • Jak używać timeit do profilowania kodu Pythona
  • Jak używać cProfile do profilowania kodu Pythona
  • Jak przekonwertować Python na JavaScript (iz powrotem)