データクラスとは何ですか?それらは一般的なクラスとどう違うのですか?


141

PEP 557のデータクラスは、Pythonの標準ライブラリに導入されています。

彼らは @dataclassデコレーター「デフォルトで変更可能な名前付きタプル」であると想定されていますが、これが実際に何を意味するのか、またそれらが一般的なクラスとどのように異なるのかを理解できません。

正確にpythonデータクラスとは何ですか、いつそれらを使用するのが最適ですか?


8
PEPの広範なコンテンツを考慮して、他に何を知りたいですか?namedtuplesは不変であり、属性のデフォルト値を持つことはできませんが、データクラスは変更可能であり、それらを持つことができます。
jonrsharpe 2017

30
@jonrsharpe件名にstackoverflowスレッドがあるべきだと私には合理的に思えます。Stackoverflowは、Q&A形式の百科事典であることを意図しています。答えは、「この他のWebサイトを見るだけ」ではありません。ここでは反対投票はありませんでした。
ルークデイビス

11
アイテムをリストに追加する方法には5つのスレッドがあります。に関する質問の1つ@dataclassは、サイトが崩壊する原因にはなりません。
エリック

2
@jonrsharpe namedtuplesCANにはデフォルト値があります。ここを見て: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私のいくつかのためのコードのアドベントソリューションのためのソリューションを参照してください7日目8日目11日目20日目

dataclassesPythonバージョン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あなたのユースケースは実際にはデータクラスでサポートされていません。おそらく、データクラスのより大きな従兄弟であるattrsを使用したほうがよいでしょう。そのプロジェクトは、フィールド値を正規化できるフィールドごとのコンバーターをサポートしています。データクラスを使い続けたい場合は、はい、__post_init__メソッドで正規化を行います。
Martijn Pieters

62

概観

質問が解決されました。ただし、この回答には、データクラスの基本的な理解に役立ついくつかの実用的な例が追加されています。

正確にpythonデータクラスとは何ですか、いつそれらを使用するのが最適ですか?

  1. コードジェネレーター:ボイラープレートコードを生成します。通常のクラスに特別なメソッドを実装するか、データクラスに自動的に実装させるかを選択できます。
  2. データコンテナ:構造物が多いような点線、属性アクセスのホールドデータ(例えばタプルとdicts)、そのクラス、namedtupleおよびその他

「デフォルトの変更可能な名前付きタプル[s]」

後者のフレーズの意味は次のとおりです。

  • mutable:デフォルトでは、dataclass属性を再割り当てできます。オプションでそれらを不変にすることができます(以下の例を参照)。
  • namedtuplenamedtupleまたは通常のクラスのように、ドットで区切られた属性アクセスがあります。
  • default:デフォルト値を属性に割り当てることができます。

一般的なクラスと比較すると、主にボイラープレートコードを入力する手間が省けます。


特徴

これはデータクラス機能の概要です(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__インスタンスの作成と属性へのアクセスの速度も向上します。また、スロットはデフォルトの割り当てを許可しません。それ以外の場合は、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}

制限事項


参考文献

  • R.ヘッティンガーの上のデータクラス:すべてのコードジェネレータを終了するコードジェネレータ
  • T. Hunnerによる、より簡単なクラスに関する講演:Pythonクラスですべての問題を解消
  • ハッシュの詳細に関するPythonのドキュメント
  • Python 3.7のデータクラスに関する究極ガイドに関する本物のPython ガイド
  • A. ShawのPython 3.7データクラスの概要ツアーに関するブログ投稿
  • E.スミスのgithubのリポジトリ上のデータクラス

2

PEP仕様から:

PEP 526「Syntax for Variable Annotations」で定義されているように、型アノテーションを持つ変数のクラス定義を検査するクラスデコレーターが提供されています。このドキュメントでは、そのような変数をフィールドと呼びます。これらのフィールドを使用して、デコレーターはクラスに生成されたメソッド定義を追加し、インスタンスの初期化、repr、比較メソッド、およびオプションで「仕様」セクションで説明されているその他のメソッドをサポートします。このようなクラスはデータクラスと呼ばれますが、クラスについて特別なことは何もありません。デコレータは生成されたメソッドをクラスに追加し、指定されたものと同じクラスを返します。

@dataclass発電機は、あなたがそれ以外のように自分自身を定義したいことをクラスにメソッドを追加して__repr____init____lt__、と__gt__


2

この単純なクラスを考えてみましょう Foo

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

以下はdir()組み込みの比較です。左側Fooは@dataclassデコレータなしで、右側は@dataclassデコレータありです。

ここに画像の説明を入力してください

inspect比較のためにモジュールを使用した後の別の相違点を次に示します。

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.