Pythonで、クラスオブジェクトをdictにキャストするにはどうすればよいですか?


89

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

これは素晴らしいです!しかし、今私はそれを同様の方法で口述にキャストしたいと思います

>>> 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、両方のケースで。クラスを反復可能にするだけで問題が解決するとは思いません。また、「python cast object to dict」というさまざまな表現を考えられる限り多くのグーグルで試しましたが、関連するものは見つかりませんでした:{

また!呼び出しに注意してくださいw.__dict__、含まれているために起こっているので、私はやりたいことではないだろうw.versionw.sum。を使用しdictてキャストをカスタマイズできるのと同じ方法で、キャストをカスタマイズしたいと思います。intdef int(self)

私はこのようなことをすることができることを知っています

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

しかし、私は作る神託の方法があると仮定していますdict(w)、それはようなものと同じタイプであるため、作業がint(w)str(w)。もっとパイソン的な方法がなければ、それも問題ありません。ああ!重要なことなので、これはpython 2.7の場合ですが、2.4の古くて破壊されたソリューションの場合もスーパーボーナスポイントです。

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

更新:asdict提案されたルートを使用することを選択しましたが、質問への回答にしたいものを選択するのは難しい選択でした。@RickTeacheyと@ jpmc26の両方が私がロールするつもりの答えを提供しましたが、前者はより多くの情報とオプションを持っていて、同じ結果に到達し、より多くの賛成を得たので、私はそれを選びました。しかし、すべての周りに賛成票を投じ、助けてくれてありがとう。私はstackoverflowに長く懸命に潜んでいて、つま先をもっと水中に入れようとしています。


1
これはもう見たことがありますか?docs.python.org/3/reference/…–
Garrett R

@GarrettRこれは役に立ちますが、この特定の問題に対する多くの洞察を実際に明らかにするものではありません。
zizouz212 2016


@RickTeachey確かに、オーバーロード...の質問に似ていますが、よりカスタマイズ可能なdict表現を探しています。たとえば、オブジェクトは、私がdictで見たくないものを格納している可能性があります。
2016

あなたの質問は誤解を招く表現です。オブジェクトをdict表現に変換またはキャストしたくない(変換し直した場合にオブジェクトを完全に回復できるようにするため)。(たとえば)パブリックインスタンス変数のdict表現ビューのみを作成する必要があります。
SMCI

回答:


124

少なくとも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

もう1つの制限は、NTが読み取り専用であることです。これは望ましい場合と望ましくない場合があります。

オプション2:実装し__iter__ます。

このように、例えば:

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

今、あなたはただすることができます:

dict(my_object)

これが機能するのは、dict()コンストラクターが反復可能な(key, value)ペアを受け入れて辞書を構築するためです。これを行う前に、この方法でオブジェクトを一連のキーと値のペアとして反復することは、作成には便利ですがdict、他のコンテキストでは実際に驚くべき動作になるかどうかを自問してください。たとえば、list(my_object)「...の振る舞いはどうあるべきか」という質問を自問してください。

さらに、get itemobj["a"]構文を使用して値に直接アクセスすることはできず、キーワード引数の解凍も機能しないことに注意してください。そのためには、マッピングプロトコルを実装する必要があります。

オプション3: マッピングプロトコルを実装ます。これにより、キーによるアクセス動作、dictを使用せずにへのキャストが可能になり、すべてのキーが文字列()の場合、__iter__アンパック動作({**my_obj})およびキーワードアンパック動作も提供されます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を使用している場合は、マッピングプロトコルオブジェクトを次のように辞書内包表記に解凍することもできます(この場合、キーが文字列である必要はありません)。

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

マッピングプロトコルはそのノートよりも優先さ__iter__にオブジェクトをキャストする際の方法dict(すなわち、kwargアンパックを使用せずに直接dict(m))。したがって、反復可能として使用した場合(たとえば、list(m))とdictdict(m))にキャストした場合で、オブジェクトの動作を変えることができ、場合によっては便利です。

強調:マッピングプロトコルを使用することができるからといって、あなたがいることを意味するものではありませんすべきで、そう。オブジェクトがキーと値のペアのセットとして、またはキーワードの引数と値として渡されることは実際に意味がありますか?辞書のように、キーでアクセスすることは本当に意味がありますか?

これらの質問に対する答えが「はい」の場合は、次のオプションを検討することをお勧めします。

オプション4:'collections.abc'モジュールの使用を検討します

クラスを他のユーザーから継承する'collections.abc.Mapping'collections.abc.MutableMapping、他のユーザーに通知することで、すべての目的と目的で、クラスはマッピング*であり、そのように動作することが期待できます。

dict必要に応じてオブジェクトをキャストすることもできますが、そうする理由はおそらくほとんどありません。ダックタイピングのため、マッピングオブジェクトをにキャストするdict手間は、ほとんどの場合、追加の不要な手順になります。

この答えも役立つかもしれません。

以下のコメントに記載されているように、これをabcの方法で行うと、基本的にオブジェクトクラスが同様のクラスに変わりますdictMutableMapping読み取り専用のMapping基本クラスではなく、を使用すると仮定します)。でできることはすべてdict、独自のクラスオブジェクトで行うことができます。これは望ましい場合と望ましくない場合があります。

また、numbersモジュール内の数値abcsを確認することも検討してください。

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:Python3.8で追加されたtyping.TypedDictを使用します。

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

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

このオプションを使用すると、結果のオブジェクトがあるdict(強調:それはされませんWharrgarbl)。それを口述に「キャスト」する理由はまったくありません(コピーを作成している場合を除く)。

また、オブジェクトはであるdictため、初期化署名はの署名と同じでdictあり、キーワード引数または別の辞書のみを受け入れます。

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

強調:上記の「クラス」Wharrgarblは実際にはまったく新しいクラスではありません。これはdict、タイプチェッカーのさまざまなタイプのフィールドを持つ型付きオブジェクトを作成するための単なる構文糖衣です。

そのため、このオプションは、コードのリーダー(およびmypyなどのタイプチェッカー)に、そのようなdictオブジェクトが特定の値型を持つ特定のキーを持つことが期待されていることを通知するのに非常に便利です。

ただし、これは、たとえば、次のことを試すことはできても、他のメソッドを追加できないことを意味します。

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'

※「マッピング」は、dictダックタイピングのような標準的な「名前」になりました。


1
これ、OPが望む効果を得る方法ですが、他の効果があることを明確にする必要for k, v in my_objectがあります。たとえば、有効になることもあります。
zwol 2016

@RickTeachey私はあなたがアディクトポイントで何を運転しているのかわかります。私は確かにオブジェクトが反復可能である必要はありません。
2016

他のリンク/programming/61517/python-dictionary-from-an-objects-fieldsで説明されているように、単純にvars関数を使用できる理由。私はこの質問で何かが欠けていますか?
ユーザー

@user OPの質問には、メンバーのみたかったabcしてをdict。このvars関数は、不要なものを含むすべてのオブジェクトメンバーを含む辞書を返します:sumおよびversion。場合によってvarsは機能しますが、私の経験では実際には慣用的なPythonではありません。通常、人々は、たとえば、「オブジェクトを辞書にキャストしています」と言って、より明確になりますobj.asdict()。を使用varsすると、通常、obj_kwargs = vars(obj)その後にMyObject(**obj_kwargs)。が続くようなものになります。言い換えると、:varsは主にオブジェクトをコピーするために使用されます。
リックは

を返すメソッドを追加するのはself.__dict__どうですか?
user328 8220年

19

あなたが望むことをする魔法の方法はありません。答えは、単に適切な名前を付けることです。主asdictdictに触発された、への単純な変換には合理的な選択ですnamedtuple。ただし、メソッドには明らかに、その名前からすぐにはわからない可能性のある特別なロジックが含まれています。クラスの状態のサブセットのみを返します。概念を明確に伝える、もう少し冗長な名前を思い付くことができれば、なおさらです。

他の回答はを使用することを提案し__iter__ていますが、オブジェクトが本当に反復可能(一連の要素を表す)でない限り、これは実際にはほとんど意味がなく、メソッドの厄介な悪用を構成します。クラスの状態の一部を除外したいという事実は、このアプローチをさらに疑わしいものにします。


はい、クラス全体の状態のサブセットである関連性の値を返すことが、私が達成しようとしていることです。計算や比較などをすばやく行うための「非表示」値がいくつか保存されていますが、それらは人間が見たり遊んだりしたいものではなく、オブジェクト自体を実際に表すものでもありません。
2016

@ tr3buchetサンプルコードでは、これらの値は初期化子に渡されます。それはあなたの実際のコードのようですか?キャッシュされた計算が引数として渡されるのは奇妙に思えます。
jpmc26 2016

まったくそうではありませんでしたが、例を示すためにそれを行っていました
2016

他の回答が示唆しているように、ドキュメントは使用を容認し__iter__ているように見えることに注意してください。「マッピングの場合、コンテナのキーを反復処理し、メソッドiterkeys()としても使用できるようにする必要があります。」
dbenton 2018

@dbentonそれは、OPが行っていることでも、他の回答が推奨していることでもありません。OPのオブジェクトは「コンテナ」ではなく、このコンテキストでは基本的にコレクションの同義語として使用されています。OPのオブジェクトも、通常マッピングが意味するものではありません。マッピングは、任意の数のキーと値を持ち、Mapping基本クラスを拡張する必要があるものです。特定の名前付き属性を持つオブジェクトは、このカテゴリに分類されません。
jpmc26 2018

11

このようなものはおそらくうまくいくでしょう

class MyClass:
    def __init__(self,x,y,z):
       self.x = x
       self.y = y
       self.z = z
    def __iter__(self): #overridding this to return tuples of (key,value)
       return iter([('x',self.x),('y',self.y),('z',self.z)])

dict(MyClass(5,6,7)) # because dict knows how to deal with tuples of (key,value)

おもしろい!タプルの反復可能で、私はこれを考慮しませんでした。簡単なテストを行います
2016

また、タプルのリストのiterを返すと、x、y、およびzのすべての値が、iterの基になっているリストにスナップショットされます。3つのyieldステートメントの場合、スレッド環境でこれを実行すると、別のスレッドがy属性またはz属性を変更して、報告されたx、y、およびzは一貫していません。
PaulMcG 2016

@PaulMcGuire:CPythonは、Joranのコードでもすべてが一貫していることを保証しますか?たとえば、最初のタプルwithxが作成された後、2番目のタプルwithが作成される前に、別のスレッドをスケジュールできると思いましたy。私は間違っていますか?実際、リスト内包表記はスレッドのスケジューリングに関してアトミックですか?
スティーブジェソップ2016

Pythonのスレッドスイッチャーはソースレベルではなくバイトコードレベルでそうするので、リスト内包表記でさえアトミックであることが保証されていないことに気づきました。ただし、公開はリストの作成期間中のみです。リストが作成されると、プロパティの更新は重要ではありません(プロパティ値が可変オブジェクトでない限り)。disモジュールを使用して実際のバイトコードを表示するのが最適です。
PaulMcG 2016

11

私はこれがあなたのために働くと思います。

class A(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 __iter__(self):
        return self.__dict__.iteritems()

a = A(1,2,3,4,5)
print dict(a)

出力

{'a':1、 'c':3、 'b':2、 'sum':6、 'バージョン':5}


合計とバージョンはどうなりましたか?これは、__dict__提案されている他の回答のように単にアクセスするのとどう違うのですか?
Joran Beasley 2016

それは口述ではありません。iteritemsを呼び出すと、イテレータが返されます
Garrett R

1
私はこの回答が好きです...しかし、OPの要件を満たすには、もう少し作業が必要です...
Joran Beasley 2016

この答えは、@ JoranBeasleyとまったく同じです。違いは、彼が選択的であるのに対し、あなたはself.__dict__私がオブジェクトの辞書バージョンに入れたくないアイテムを含む全体をつかむということです。
2016

6

他の多くのように、辞書へのキャストを許可するのではなく(またはそれに加えて)to_dict()関数を実装することをお勧めします。クラスがそのような機能をサポートしていることがより明白になると思います。次のようなメソッドを簡単に実装できます。

def to_dict(self):
    class_vars = vars(MyClass)  # get any "default" attrs defined at the class level
    inst_vars = vars(self)  # get any attrs defined on the instance (self)
    all_vars = dict(class_vars)
    all_vars.update(inst_vars)
    # filter out private attributes
    public_vars = {k: v for k, v in all_vars.items() if not k.startswith('_')}
    return public_vars

1

問題の全体的なコンテキストを知らずに言うのは難しいですが、私はオーバーライドしません__iter__

私は__what_goes_here__クラスに実装します。

as_dict(self:
    d = {...whatever you need...}
    return d

いや、それは変数が中に保存されているものです。
Zizouz212

3
w.__dict__はそれが私が必要とすることをしないことを特に指摘しました。
2016

しかし__dict__、オーバーライドすることはできませんか?
Noisewaterphd 2016

@noisewaterphdいいえ、それは魔法の方法です(おそらくそれをオーバーライドすることはできますが、控えめに言っても意味が怖いです)
Joran Beasley 2016

1
dictから継承しないのはなぜですか、あるいはもっと良いのは、クラスにメソッドを作成してdictとして返すだけです。
Noisewaterphd 2016

0

alistまたはdict。の「両方」であるクラスを作成しようとしています。プログラマーがこのオブジェクトをlist(キーをドロップする)に「キャスト」できるようにするか、dict(ます。

Pythonが現在dict()キャストを行っている方法を見てみましょうMapping.update()。渡されたオブジェクトを使用して呼び出します。これはPythonリポジトリからのコードです

def update(self, other=(), /, **kwds):
    ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
        If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
        If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
        In either case, this is followed by: for k, v in F.items(): D[k] = v
    '''
    if isinstance(other, Mapping):
        for key in other:
            self[key] = other[key]
    elif hasattr(other, "keys"):
        for key in other.keys():
            self[key] = other[key]
    else:
        for key, value in other:
            self[key] = value
    for key, value in kwds.items():
        self[key] = value

ifステートメントの最後のサブケース。繰り返し処理されます。 otherは、ほとんどの人が念頭に置いているものです。ただし、ご覧のとおり、keys()プロパティを持つことも可能です。これをaと組み合わせると、__getitem__()サブクラスを辞書に適切にキャストするのが簡単になります。

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 __keys__(self):
        return ["a", "b", "c"]

    def __getitem__(self, key):
        # have obj["a"] -> obj.a
        return self.__getattribute__(key)

その後、これは機能します:

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

マッピングプロトコルを実装しました。上記の私の答えのオプション3を参照してください。listキーのドロップも行いたい場合は__iter__、値を反復処理するメソッドを追加できますが、へのキャストdictは引き続き機能します。通常、辞書では、リストにキャストすると、必要な値ではなく、KEYSが返されることに注意してください。これが発生する理由が明らかでない限り、これはあなたのコードを使用している他の人々にとって驚くべきことかもしれません。
リックは
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.