Was sind Datenklassen und wie unterscheiden sie sich von gängigen Klassen?

140

Mit PEP 557 werden Datenklassen in die Python-Standardbibliothek eingeführt.

Sie verwenden den @dataclassDekorator und sollen "veränderbare benannte Tupel mit Standard" sein, aber ich bin mir nicht sicher, ob ich verstehe, was dies tatsächlich bedeutet und wie sie sich von üblichen Klassen unterscheiden.

Was genau sind Python-Datenklassen und wann ist es am besten, sie zu verwenden?

König Julian
quelle
8
Was möchten Sie angesichts des umfangreichen Inhalts des PEP noch wissen? namedtuples sind unveränderlich und können keine Standardwerte für die Attribute haben, während Datenklassen veränderbar sind und diese haben können.
Jonrsharpe
28
@jonrsharpe Scheint mir vernünftig, dass es einen Stackoverflow-Thread zu diesem Thema geben sollte. Stackoverflow soll eine Enzyklopädie im Q & A-Format sein, oder? Die Antwort lautet niemals "schau einfach auf diese andere Website". Hier hätte es keine Abstimmungen geben dürfen.
Luke Davis
10
Es gibt fünf Themen zum Anhängen eines Elements an eine Liste. Eine Frage zu @dataclassführt nicht dazu, dass sich die Site auflöst.
Eric
2
@jonrsharpe namedtuplesKANN Standardwerte haben. Schauen Sie hier: stackoverflow.com/questions/11351032/…
MJB

Antworten:

151

Datenklassen sind nur reguläre Klassen, die auf das Speichern von Status ausgerichtet sind und mehr als viel Logik enthalten. Jedes Mal, wenn Sie eine Klasse erstellen, die hauptsächlich aus Attributen besteht, haben Sie eine Datenklasse erstellt.

Das dataclassesModul erleichtert das Erstellen von Datenklassen. Es kümmert sich um eine Menge Kesselplatte für Sie.

Dies ist besonders wichtig, wenn Ihre Datenklasse hashbar sein muss. Dies erfordert sowohl eine __hash__Methode als auch eine __eq__Methode. Wenn Sie eine benutzerdefinierte __repr__Methode hinzufügen, um das Debuggen zu vereinfachen, kann dies sehr ausführlich werden:

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))

Mit können dataclassesSie es reduzieren auf:

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

Die gleiche Klasse decorator können auch Vergleichsverfahren (generieren __lt__, __gt__usw.) und Griff Unveränderlichkeit.

namedtupleKlassen sind ebenfalls Datenklassen, aber standardmäßig unveränderlich (und auch Sequenzen). dataclassessind in dieser Hinsicht viel flexibler und können leicht so strukturiert werden, dass sie dieselbe Rolle wie eine namedtupleKlasse übernehmen können .

Das PEP wurde von dem attrsProjekt inspiriert , das noch mehr kann (einschließlich Slots, Validatoren, Konverter, Metadaten usw.).

Wenn Sie einige Beispiele sehen möchten, die ich kürzlich dataclassesfür einige meiner Advent of Code- Lösungen verwendet habe, lesen Sie die Lösungen für Tag 7 , Tag 8 , Tag 11 und Tag 20 .

Wenn Sie ein dataclassesModul in Python-Versionen <3.7 verwenden möchten , können Sie das zurückportierte Modul installieren (erfordert 3.6) oder das attrsoben erwähnte Projekt verwenden.

Martijn Pieters
quelle
2
Verstecken Sie im ersten Beispiel absichtlich Klassenmitglieder mit gleichnamigen Instanzmitgliedern? Bitte helfen Sie, diese Redewendung zu verstehen.
VladimirLenin
4
@VladimirLenin: Es gibt keine Klassenattribute, es gibt nur Typanmerkungen. Siehe PEP 526 , insbesondere den Abschnitt Anmerkungen zu Klassen- und Instanzvariablen .
Martijn Pieters
1
@Bananach: Das @dataclassgeneriert ungefähr dieselbe __init__Methode mit einem quantity_on_handSchlüsselwortargument mit Standardwert. Wenn Sie eine Instanz erstellen quantity_on_hand, wird immer das Instanzattribut festgelegt . Mein erstes Beispiel , das keine Datenklasse ist, verwendet dasselbe Muster, um zu wiederholen, was der generierte Code der Datenklasse bewirkt.
Martijn Pieters
1
@Bananach: so im ersten Beispiel, wir konnten nur eine Instanz Attribut auslassen Einstellung und nicht die Klasse Attribut Schatten, ist es überflüssig Einstellung es ohnehin in diesem Sinne, aber dataclasses tut es gesetzt.
Martijn Pieters
1
@ user2853437 Ihr Anwendungsfall wird von Datenklassen nicht wirklich unterstützt. Vielleicht ist es besser, wenn Sie den größeren Cousin der Datenklasse , attrs, verwenden . Dieses Projekt unterstützt Konverter pro Feld , mit denen Sie Feldwerte normalisieren können. Wenn Sie sich an Datenklassen halten möchten, führen Sie eine Normalisierung in der __post_init__Methode durch.
Martijn Pieters
62

Überblick

Die Frage wurde angesprochen. Diese Antwort fügt jedoch einige praktische Beispiele hinzu, um das grundlegende Verständnis von Datenklassen zu erleichtern.

Was genau sind Python-Datenklassen und wann ist es am besten, sie zu verwenden?

  1. Codegeneratoren : Boilerplate-Code generieren; Sie können spezielle Methoden in einer regulären Klasse implementieren oder von einer Datenklasse automatisch implementieren lassen.
  2. Datencontainer : Strukturen, die Daten enthalten (z. B. Tupel und Diktate), häufig mit gepunkteten Attributzugriffen wie Klassen namedtupleund anderen .

"veränderbare Namedtuples mit Standard [s]"

Folgendes bedeutet der letztere Satz:

  • veränderlich : Standardmäßig können Datenklassenattribute neu zugewiesen werden. Sie können sie optional unveränderlich machen (siehe Beispiele unten).
  • namedtuple : Sie haben einen gepunkteten Attributzugriff wie eine namedtupleoder eine reguläre Klasse.
  • Standard : Sie können Attributen Standardwerte zuweisen.

Im Vergleich zu herkömmlichen Klassen sparen Sie in erster Linie bei der Eingabe von Boilerplate-Code.


Eigenschaften

Dies ist eine Übersicht über Datenklassenfunktionen (TL; DR? Siehe Übersichtstabelle im nächsten Abschnitt).

Was man bekommt

Hier sind Funktionen, die Sie standardmäßig von Datenklassen erhalten.

Attribute + Darstellung + Vergleich

import dataclasses


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

Diese Standardeinstellungen werden bereitgestellt, indem die folgenden Schlüsselwörter automatisch auf gesetzt werden True:

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

Was Sie einschalten können

Zusätzliche Funktionen sind verfügbar, wenn die entsprechenden Schlüsselwörter festgelegt sind True.

Auftrag

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

Die Bestellmethoden sind jetzt implementiert (Überladungsoperatoren :) < > <= >=, ähnlich wie functools.total_orderingbei stärkeren Gleichheitstests.

Hashable, veränderlich

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

Obwohl das Objekt möglicherweise veränderlich (möglicherweise unerwünscht) ist, wird ein Hash implementiert.

Hashable, unveränderlich

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

Ein Hash ist jetzt implementiert und das Ändern des Objekts oder das Zuweisen von Attributen ist nicht zulässig.

Insgesamt ist das Objekt hashbar, wenn entweder unsafe_hash=Trueoder frozen=True.

Siehe auch die ursprüngliche Hashing-Logik-Tabelle mit weiteren Details.

Was du nicht bekommst

Um die folgenden Funktionen zu erhalten, müssen spezielle Methoden manuell implementiert werden:

Auspacken

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

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

Optimierung

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

Die Objektgröße wird jetzt reduziert:

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

__slots__Verbessert unter bestimmten Umständen auch die Geschwindigkeit beim Erstellen von Instanzen und beim Zugriff auf Attribute. Außerdem erlauben Slots keine Standardzuweisungen. Andernfalls wird a ValueErrorangehoben.

Weitere Informationen zu Slots finden Sie in diesem Blogbeitrag .


Übersichtstabelle

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       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__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

+ Diese Methoden werden nicht automatisch generiert und erfordern eine manuelle Implementierung in eine Datenklasse.

* __ne__ wird nicht benötigt und ist daher nicht implementiert .


Zusatzfunktionen

Nachinitialisierung

@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)

Erbe

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

Konvertierungen

Konvertieren Sie eine Datenklasse rekursiv in ein Tupel oder ein Diktat :

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

Einschränkungen


Verweise

  • R. Hettingers Vortrag über Datenklassen: Der Codegenerator zum Beenden aller Codegeneratoren
  • T. Hunners Vortrag über einfachere Klassen: Python-Klassen ohne die ganze Cruft
  • Pythons Dokumentation zu Hashing-Details
  • Real Pythons Leitfaden zum ultimativen Leitfaden für Datenklassen in Python 3.7
  • A. Shaws Blogbeitrag über Eine kurze Tour durch Python 3.7-Datenklassen
  • E. Smiths Github-Repository für Datenklassen
Pylang
quelle
2

Aus der PEP-Spezifikation :

Es wird ein Klassendekorator bereitgestellt, der eine Klassendefinition auf Variablen mit Typanmerkungen überprüft, wie in PEP 526, "Syntax für Variablenanmerkungen" definiert. In diesem Dokument werden solche Variablen als Felder bezeichnet. Mithilfe dieser Felder fügt der Dekorateur der Klasse generierte Methodendefinitionen hinzu, um die Instanzinitialisierung, eine Repr, Vergleichsmethoden und optional andere Methoden zu unterstützen, wie im Abschnitt Spezifikation beschrieben. Eine solche Klasse wird als Datenklasse bezeichnet, aber die Klasse hat wirklich nichts Besonderes: Der Dekorateur fügt der Klasse generierte Methoden hinzu und gibt dieselbe Klasse zurück, die sie erhalten hat.

Der @dataclassGenerator fügt Methoden der Klasse , dass Sie anders definieren würde sich wie __repr__, __init__, __lt__, und __gt__.

Mahmoud Hanafy
quelle
2

Betrachten Sie diese einfache Klasse Foo

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

Hier ist der dir()eingebaute Vergleich. Auf der linken Seite befindet sich der FooDekorator ohne @dataclass und auf der rechten Seite mit dem Dekorateur @dataclass.

Geben Sie hier die Bildbeschreibung ein

Hier ist ein weiterer Unterschied, nachdem das inspectModul zum Vergleich verwendet wurde.

Geben Sie hier die Bildbeschreibung ein

Prosti
quelle