Що таке класи даних та чим вони відрізняються від загальних класів?


141

За допомогою PEP 557 класи класів вводяться в стандартну бібліотеку пітона.

Вони використовують @dataclassдекоратор, і вони, мабуть, є "мутаційними названими парнями за замовчуванням", але я не дуже впевнений, що розумію, що це насправді означає і чим вони відрізняються від звичайних класів.

Що саме являють собою класи даних python і коли найкраще їх використовувати?


8
Зважаючи на обширний зміст PEP, що ще ви могли б знати? namedtuples є незмінними і не можуть мати значення за замовчуванням для атрибутів, тоді як класи даних є змінними і можуть мати їх.
jonrsharpe

31
@jonrsharpe Мені здається розумним, що на цю тему повинна бути нитка stackoverflow. Stackoverflow означає, що це енциклопедія у форматі запитань, ні? Відповідь ніколи "просто подивіться на цей інший веб-сайт". Тут не повинно було бути голосів.
Люк Девіс

12
Про те, як додати елемент до списку, існує п’ять ниток. Одне питання щодо @dataclassне призведе до розпаду сайту.
eric

2
@jonrsharpe namedtuplesМОЖЕ мати значення за замовчуванням. Подивіться тут: stackoverflow.com/questions/11351032/…
MJB

Відповіді:


152

Класи даних - це просто звичайні класи, орієнтовані на збереження стану, більш ніж містять багато логіки. Кожен раз, коли ви створюєте клас, який здебільшого складається з атрибутів, ви склали клас даних.

Що dataclassesробить модуль, це полегшує створення класів даних. Він піклується про велику кількість плит котла для вас.

Це особливо важливо, коли ваш клас даних повинен бути доступним; для цього потрібен __hash__метод, а також __eq__метод. Якщо ви додасте спеціальний __repr__метод для зручності налагодження, він може стати досить багатослівним:

class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def __init__(
            self, 
            name: str, 
            unit_price: float,
            quantity_on_hand: int = 0
        ) -> None:
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

    def __repr__(self) -> str:
        return (
            'InventoryItem('
            f'name={self.name!r}, unit_price={self.unit_price!r}, '
            f'quantity_on_hand={self.quantity_on_hand!r})'

    def __hash__(self) -> int:
        return hash((self.name, self.unit_price, self.quantity_on_hand))

    def __eq__(self, other) -> bool:
        if not isinstance(other, InventoryItem):
            return NotImplemented
        return (
            (self.name, self.unit_price, self.quantity_on_hand) == 
            (other.name, other.unit_price, other.quantity_on_hand))

З dataclassesвами може зменшити його:

from dataclasses import dataclass

@dataclass(unsafe_hash=True)
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Ж клас декоратор може також генерувати методи порівняння ( __lt__, __gt__і т.д.) і ручку незмінність.

namedtupleкласи також є класами даних, але вони є незмінними за замовчуванням (а також є послідовностями). dataclassesв цьому плані набагато гнучкіші, і їх можна легко структурувати таким чином, що вони можуть виконувати ту саму роль, що і namedtupleклас .

PEP був натхненний attrsпроектом , який може зробити ще більше (включаючи слоти, валідатори, перетворювачі, метадані тощо).

Якщо ви хочете побачити кілька прикладів, я нещодавно використовував dataclassesдекілька моїх рішень Advent of Code , перегляньте рішення для 7-го , 8-го , 8-го та 20-го дня .

Якщо ви хочете використовувати dataclassesмодуль у версіях Python <3.7, тоді ви можете встановити підтримуваний модуль (потрібно 3.6) або використовувати attrsпроект, згаданий вище.


2
У першому прикладі ви навмисно ховаєте членів класу з одноіменними членами екземпляра? Будь ласка, допоможіть зрозуміти цю фразу.
Володимир

4
@VladimirLenin: атрибутів класу немає, є лише анотації типів. Див. PEP 526 , зокрема розділ анотацій змінних класів та екземплярів .
Martijn Pieters

1
@Bananach: @dataclassгенерує приблизно той самий __init__метод, quantity_on_handаргумент ключового слова зі значенням за замовчуванням. Коли ви створюєте екземпляр, він завжди встановлюватиме quantity_on_handатрибут примірника. Отже, мій перший приклад, некласифікований, використовує ту саму схему, щоб повторювати те, що робитиме код, створений класом даних.
Martijn Pieters

1
@Bananach: тому в першому прикладі ми можемо просто опустити встановлення атрибута екземпляра, а не затінювати атрибут класу, це все одно зайве його встановлення в цьому сенсі, але класи даних задають його.
Martijn Pieters

1
@ user2853437 ваш випадок використання насправді не підтримується класами даних; можливо , ви б краще використовувати більше кузена dataclasses ', AttrS . Цей проект підтримує польові перетворювачі, які дозволяють нормалізувати значення полів. Якщо ви хочете дотримуватися класів даних, то так, зробіть нормалізацію __post_init__методу.
Martijn Pieters

62

Огляд

Питання вирішено. Однак ця відповідь додає кілька практичних прикладів, які допомагають в базовому розумінні класів даних.

Що саме являють собою класи даних python і коли найкраще їх використовувати?

  1. генератори коду : генерують код котла; ви можете вибрати, щоб реалізувати спеціальні методи в звичайному класі або мати клас даних, щоб реалізувати їх автоматично.
  2. контейнери даних : структури, що містять дані (наприклад, кортежі та дикти), часто з пунктирним, атрибутивним доступом, наприклад класами namedtupleта ін .

"змінні ім'яназви з замовчуванням [s]"

Ось що означає остання фраза:

  • mutable : за замовчуванням атрибути класу даних можна перепризначити. Ви можете необов'язково зробити їх непорушними (див. Приклади нижче).
  • nametuple : ви вказали крапки, доступ до атрибутів, як namedtupleабо звичайний клас.
  • за замовчуванням : атрибутам можна призначити значення за замовчуванням.

Порівняно із звичайними класами, ви в першу чергу економите на введенні кодового шаблону.


Особливості

Це огляд функцій класу даних (TL; DR? Див. Зведену таблицю в наступному розділі).

Що ви отримуєте

Ось функції, які ви отримуєте за замовчуванням з класів даних.

Атрибути + Представлення + Порівняння

import dataclasses


@dataclasses.dataclass
#@dataclasses.dataclass()                                       # alternative
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

Ці параметри за замовчуванням надаються шляхом автоматичного встановлення таких ключових слів True:

@dataclasses.dataclass(init=True, repr=True, eq=True)

Що можна включити

Додаткові функції доступні, якщо встановлено відповідні ключові слова True.

Замовлення

@dataclasses.dataclass(order=True)
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

В даний час реалізовані методи впорядкування (оператори перевантаження:) < > <= >=, як і у functools.total_orderingбільш твердих тестів рівності.

Міцний, змінний

@dataclasses.dataclass(unsafe_hash=True)                        # override base `__hash__`
class Color:
    ...

Хоча об'єкт є потенційно змінним (можливо, небажаним), хеш реалізований.

Міцний, незмінний

@dataclasses.dataclass(frozen=True)                             # `eq=True` (default) to be immutable 
class Color:
    ...

Зараз реалізовано хеш, і зміна об'єкта або призначення атрибутів заборонено.

В цілому об'єкт є хешируемим, якщо будь-який unsafe_hash=Trueабо frozen=True.

Дивіться також оригінальну логічну таблицю хешування з більш детальною інформацією.

Чого ви не отримуєте

Для отримання наступних функцій спеціальні методи потрібно вручну реалізувати:

Розпакування

@dataclasses.dataclass
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

    def __iter__(self):
        yield from dataclasses.astuple(self)

Оптимізація

@dataclasses.dataclass
class SlottedColor:
    __slots__ = ["r", "b", "g"]
    r : int
    g : int
    b : int

Розмір об'єкта тепер зменшено:

>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888

За деяких обставин __slots__також покращується швидкість створення екземплярів та доступу до атрибутів. Також слоти не допускають призначення за замовчуванням; в іншому випадку a ValueErrorпіднімається.

Дивіться більше про слоти в цій публікації в блозі .


Зведена таблиця

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       Feature        |       Keyword        |                      Example                       |           Implement in a Class          |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Attributes           |  init                |  Color().r -> 0                                    |  __init__                               |
| Representation       |  repr                |  Color() -> Color(r=0, g=0, b=0)                   |  __repr__                               |
| Comparision*         |  eq                  |  Color() == Color(0, 0, 0) -> True                 |  __eq__                                 |
|                      |                      |                                                    |                                         |
| Order                |  order               |  sorted([Color(0, 50, 0), Color()]) -> ...         |  __lt__, __le__, __gt__, __ge__         |
| Hashable             |  unsafe_hash/frozen  |  {Color(), {Color()}} -> {Color(r=0, g=0, b=0)}    |  __hash__                               |
| Immutable            |  frozen + eq         |  Color().r = 10 -> TypeError                       |  __setattr__, __delattr__               |
|                      |                      |                                                    |                                         |
| Unpacking+           |  -                   |  r, g, b = Color()                                 |   __iter__                              |
| Optimization+        |  -                   |  sys.getsizeof(SlottedColor) -> 888                |  __slots__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

+ Ці методи не генеруються автоматично і потребують ручної реалізації в класі даних.

* __ne__ не потрібен і, отже, не реалізований .


Додаткові характеристики

Постініціалізація

@dataclasses.dataclass
class RGBA:
    r : int = 0
    g : int = 0
    b : int = 0
    a : float = 1.0

    def __post_init__(self):
        self.a : int =  int(self.a * 255)


RGBA(127, 0, 255, 0.5)
# RGBA(r=127, g=0, b=255, a=127)

Спадщина

@dataclasses.dataclass
class RGBA(Color):
    a : int = 0

Конверсії

Перетворіть клас даних у кортеж чи дикт, рекурсивно :

>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{r: 128, g: 0, b: 255}

Обмеження


Список літератури

  • Розмова Р. Хеттінгера про Класи даних: Генератор коду для завершення всіх генераторів коду
  • Розмова Т. Ханнера про легші класи: Класи пітона без усього суворого
  • Документація Python щодо деталей хешування
  • Справжній посібник Python про Кінцевий посібник із класів даних у Python 3.7
  • Запис у блозі А. Шоу про короткий тур по класам даних Python 3.7
  • Сховище github Е. Сміта на класах даних

2

З специфікації PEP :

Надається декоратор класу, який перевіряє визначення класу для змінних з анотаціями типів, визначених у PEP 526, "Синтаксис змінних анотацій". У цьому документі такі змінні називаються полями. Використовуючи ці поля, декоратор додає згенеровані визначення методу до класу для підтримки ініціалізації екземпляра, повторної копії, методів порівняння та необов'язково інших методів, як описано в розділі Специфікація. Такий клас називається класом даних, але в цьому дійсно немає нічого особливого: декоратор додає в клас згенеровані методи та повертає той самий клас, який був заданий.

The @dataclassГенератор додає методи до класу , який ви б інакше визначати себе як __repr__, __init__, __lt__і __gt__.


2

Розглянемо цей простий клас Foo

from dataclasses import dataclass
@dataclass
class Foo:    
    def bar():
        pass  

Ось dir()вбудований порівняння. Ліворуч - Fooдекоратор без @dataclass, а праворуч - з декоратором @dataclass.

введіть тут опис зображення

Ось ще одна відмінність, після використання inspectмодуля для порівняння.

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.