1. ホーム
  2. python

[解決済み] Python で、クラスオブジェクトを dict にキャストするにはどうしたらいいですか?

2022-05-14 12:17:08

質問

Pythonで簡単なクラスがあるとします。

class Wharrgarbl(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __what_goes_here__(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

非常に簡単に整数にキャストできます。

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> int(w)
9006

これは素晴らしいことです。しかし、今度は同じような方法でdictにキャストしたいと思います。

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}

これを動作させるには、何を定義すればよいのでしょうか?私は両方の __dict__dict に対して __what_goes_here__ が、しかし dict(w) は結果として TypeError: Wharrgarbl object is not iterable の2つのケースで発生しました。単にクラスを反復可能にするだけでは解決しないように思います。また、思いつく限り多くの異なる単語で "python cast object to dict" を使って多くのググりを試みましたが、関連するものを見つけることができませんでした :{。

また!どのように w.__dict__ を呼び出すと、私の望むようにはならないことに注意してください。 w.versionw.sum . キャストをカスタマイズして dict にカスタマイズできるのと同じように、キャストを int を使うことで def int(self) .

こんな感じでやればいいんだろうけど

>>> w.__what_goes_here__()
{'a': 'one', 'c': 'three', 'b': 'two'}

しかし、私はpythonicな方法で dict(w) と同じようなものなので、python的な方法があると仮定しています。 int(w) または str(w) . もしもっとpythonicな方法がないのなら、それもいいですが、ただ聞いてみようと思っただけです。ああ!私はそれが重要なので、これはpython 2.7用だと思いますが、2.4の古い、壊れたソリューションのためのスーパーボーナスポイントも。

別の質問があります pythonのクラスで__dict__()をオーバーロードする は、この質問と似ていますが、重複しないことを保証するために十分に異なっているかもしれません。私は、OPが彼のクラスオブジェクト内のすべてのデータを辞書としてキャストする方法を求めていると信じています。私は、よりカスタマイズされたアプローチを探しています。 __dict__ によって返される辞書に含まれる dict() . 私が探しているものを説明するには、パブリック変数とプライベート変数のようなもので十分かもしれません。オブジェクトは、計算などで使用される、結果の辞書に表示する必要がない、または表示したくない値を格納する予定です。

UPDATE。 私は asdict のルートを提案されましたが、質問に対する答えを選ぶのは大変なことでした。しかし、@RickTeacheyと@jpmc26の両方が、私が選んだ答えを提供してくれました。しかし、前者はより多くの情報と選択肢を持ち、同じ結果にたどり着き、より多くのアップヴォートを得たので、私はそれを採用しました。しかし、私はこの答えに決めました。私はstackoverflowで長い間、一生懸命潜伏してきたし、私はより多くの水の中に私のつま先を取得しようとしている。

どのように解決するのですか?

少なくとも 5 の6つの方法があります。望ましい方法は、ユースケースが何であるかによります。

オプション 1:

単純に asdict() メソッドを追加します。

問題の説明に基づいて、私はぜひとも asdict の方法を検討したいと思います。なぜなら、あなたのオブジェクトは実際にはたいしたコレクションではないように見えるからです。

class Wharrgarbl(object):

    ...

    def asdict(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

以下の他のオプションを使用すると、どのオブジェクトのメンバーが反復されるか、またはキー-値ペアとして指定されないかが非常に明白でない限り、他の人を混乱させる可能性があります。

オプション 1a:

あなたのクラスを 'typing.NamedTuple' (または、ほぼ同等の 'collections.namedtuple' を使用します)、そして _asdict メソッド が提供されています。

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

名前付きタプルを使用すると、最小限の労力でクラスに多くの機能を追加することができ、次のような非常に便利な方法です。 を含む _asdict メソッド . ただし、制限事項として、上記のようにNTには 全て のメンバーを _asdict .

辞書に含めたくないメンバがある場合、そのメンバに対応するために _asdict の結果を修正する必要があります。

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

    def _asdict(self):
        d = super()._asdict()
        del d['sum']
        del d['version']
        return d

もう一つの制限は、NTが読み取り専用であることです。これは望ましいことかもしれませんし、そうでないかもしれません。

オプション 2:

実装 __iter__ .

例えばこんな感じ。

def __iter__(self):
    yield 'a', self.a
    yield 'b', self.b
    yield 'c', self.c

これでOKです。

dict(my_object)

これは dict() のイテラブルを受け付けるからです。 (key, value) のペアを受け取り、辞書を構築します。これを実行する前に、この方法で一連のキーと値のペアとしてオブジェクトを反復することは dict - を作成するのには便利ですが、この方法でオブジェクトを一連のキーと値のペアとして反復することは、他のコンテキストでは実際には驚くべき動作かもしれません。例えば、次のような質問を自分に投げかけてみてください。 list(my_object) の動作はどうあるべきか...?

さらに、get アイテムを使って直接値にアクセスする場合は、以下のようになります。 obj["a"] の構文で直接値にアクセスすることはできませんし、 キーワード引数の展開もうまくいきません。それらについては、マッピングプロトコルを実装する必要があります。

オプション 3:

実装 その マッピングプロトコル . これにより、キーごとにアクセスする動作が可能になり dict を使わずに __iter__ を使わず、また、2種類の解凍動作を提供します。

  1. マッピングの解凍動作。 {**my_obj}
  2. キーワードのアンパッキングビヘイビアですが すべてのキーが文字列である場合のみ : dict(**my_obj)

マッピングプロトコルでは、(最低でも)2つのメソッドを一緒に提供することが必要です。 keys()__getitem__ .

class MyKwargUnpackable:
    def keys(self):
        return list("abc")
    def __getitem__(self, key):
        return dict(zip("abc", "one two three".split()))[key]

というようなことができるようになりました。

>>> m=MyKwargUnpackable()
>>> m["a"]
'one'
>>> dict(m)  # cast to dict directly
{'a': 'one', 'b': 'two', 'c': 'three'}
>>> dict(**m)  # unpack as kwargs
{'a': 'one', 'b': 'two', 'c': 'three'}

上記のように、十分に新しいバージョンの python を使用している場合は、このように mapping-protocol オブジェクトを辞書の内包に展開することもできます (この場合、キーが文字列であることは必須ではありません)。

>>> {**m}
{'a': 'one', 'b': 'two', 'c': 'three'}

なお、マッピングプロトコル よりも優先されます。 __iter__ メソッドにオブジェクトをキャストするとき dict に直接キャストする場合 (クワーグのアンパッキングを使用しない、つまり dict(m) ). ですから、反復可能なオブジェクトとして使われたときに異なる動作をさせることは可能であり、時には便利かもしれません(例. list(m) ) と dict ( dict(m) ).

しかし、通常の辞書では、リストにキャストした場合、KEYSが返され、要求されたVALUESは返されないことにも注意してください。他の非標準的な動作を __iter__ に別の非標準的な動作 (キーの代わりに値を返す) を実装すると、なぜそうなるのかが非常に明白でない限り、あなたのコードを使用する他の人々が驚くことになるかもしれません。

強調表示 : マッピングプロトコルを使用することができるというだけです。 が使えるからといって べきである そうする . 実際に 意味があるのか オブジェクトがキーと値のペアのセットとして、あるいはキーワード引数と値として渡されることは、実際に意味があるのでしょうか?辞書のようにキーでアクセスすることは本当に意味があるのでしょうか?また、あなたのオブジェクトが、次のような他の標準的なマッピング方法を持つことを期待しますか? items , values , get ? をサポートしますか? in キーワードと等号チェック ( == )?

もし、これらの質問に対する答えが である場合 の場合、ここで立ち止まらず、次の選択肢を検討するのがよいでしょう。

オプション 4:

を使用することを検討してください。 'collections.abc モジュール .

を継承したクラスは 'collections.abc.Mapping または 'collections.abc.MutableMapping は、他のユーザーに対して、どう見てもあなたのクラスが はマッピングである * であり、そのように動作することが期待されます。また、このクラスはメソッド items , values , get をサポートし in キーワードと等号チェック ( == ) を無償で提供します。

まだ、オブジェクトをキャストして dict にキャストすることもできますが、おそらくそうする理由はほとんどないでしょう。なぜなら ダックタイピング にマッピングオブジェクトをわざわざキャストする必要があるからです。 dict にわざわざキャストすることは、ほとんどの場合、不要な追加ステップになるだけです。

をどのように使うかについて、私からのこの回答は ABC s の使い方についての私の回答も参考になるかもしれません。

以下のコメントで指摘されているように、abcの方法でこれを行うと、本質的にあなたのオブジェクトクラスが dict -のようなクラスです。 MutableMapping を使い、読み出し専用の Mapping 基底クラスではありません)。でできるようになることはすべて dict でできることはすべて、あなた自身のクラス・オブジェクトでできることです。これは望ましいことかもしれませんし、そうでないかもしれません。

の数値abcを見ることも考えてみてください。 numbers モジュールにある数値の abc を見ることも考えてみてください。

https://docs.python.org/3/library/numbers.html

また、オブジェクトを int にキャストしているので、クラスを本格的な int にしてしまった方が、キャスティングが不要になります。

オプション 5:

を使用することを検討してください。 dataclasses モジュール (Python 3.7+ のみ) には、便利な asdict() ユーティリティメソッドが含まれています。

from dataclasses import dataclass, asdict, field, InitVar

@dataclass
class Wharrgarbl(object):
    a: int
    b: int
    c: int
    sum: InitVar[int]  # note: InitVar will exclude this from the dict
    version: InitVar[str] = "old"

    def __post_init__(self, sum, version):
        self.sum = 6  # this looks like an OP mistake?
        self.version = str(version)

これで、こんなことができます。

    >>> asdict(Wharrgarbl(1,2,3,4,"X"))
    {'a': 1, 'b': 2, 'c': 3}

オプション 6:

使用方法 typing.TypedDict であったものを は python 3.8 で追加された .

注:オプション 6 はおそらく ではない であり、OPまたはこの質問のタイトルに基づく他の読者が求めているものではありません。以下の追加コメントを参照してください。

class Wharrgarbl(TypedDict):
    a: str
    b: str
    c: str

このオプションを使用すると、結果のオブジェクト dict (強調:それは ではない a Wharrgarbl ). これをdictにキャストする理由は全くありません(コピーを作成する場合を除く)。

そして、オブジェクトは dict と同じで、初期化シグネチャは dict と同じで、キーワード引数か別の辞書を受け付けるだけです。

    >>> w = Wharrgarbl(a=1,b=2,b=3)
    >>> w
    {'a': 1, 'b': 2, 'c': 3}
    >>> type(w)
    <class 'dict'>

強調表示 : 上記の "class" Wharrgarbl は実際には全く新しいクラスではありません。これは単に型付き dict オブジェクトを作成するための単なる構文解析です。 型チェッカのために . 実行時には、これはまだ dict .

このオプションは、コードの読者に(そしてmypyのような型チェッカーにも)、そのような dict オブジェクトが特定の値の型を持つ特定のキーを持つことが期待されていることをコードの読者に(そしてmypyのような型チェッカーにも)知らせるのにかなり便利です。

しかしこれは、例えば他のメソッドを追加することはできない、ということを意味します(試すことはできますが)。

class MyDict(TypedDict):
    def my_fancy_method(self):
        return "world changing result"

...が、うまくいきません。

>>> MyDict().my_fancy_method()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'my_fancy_method'


* "Mapping" は標準的な "name" のようになっています。 dict -のようなダックタイプの