破壊-辞書の内容をバインドする


84

辞書を「分解」して、値をそのキーの後の変数名に関連付けようとしています。何かのようなもの

params = {'a':1,'b':2}
a,b = params.values()

ただし、辞書は順序付けられていないparams.values()ため、(a, b)。の順序で値を返すという保証はありません。これを行うための良い方法はありますか?


3
怠惰?たぶん...しかしもちろん、私は説明のために最も単純なケースを示しました。理想的には、params.itemsのxのようにしたかったのですが、eval( '%s =%f'%x)ですが、eval()では割り当てが許可されていないと思います。
hatmatrix 2010年

7
@JochenRitzel ES6(JavaScript)のほとんどのユーザーは、新しいオブジェクト破壊構文を気に入っていると確信していますlet {a, b} = params。それは読みやすさを向上させ、あなたが話したい禅と完全に一致します。
アンディ

8
@AndyJSでのオブジェクトの破壊が大好きです。dictからいくつかのキーを抽出するためのクリーンでシンプルで読みやすい方法。私はPythonで似たようなものを見つけることを期待してここに来ました。
ロタレティ2017

2
ES6オブジェクトの破棄も大好きですが、ES6のMapオブジェクトが破棄をサポートしていないのと同じ理由で、Pythonでは機能しないのではないかと心配しています。キーは、ES6MapやPythondictの文字列だけではありません。また、ES6でオブジェクトを破棄する「プル」スタイルは気に入っていますが、割り当てスタイルは単純ではありません。何が起きてる? let {a: waffles} = params。あなたがそれに慣れていても、それを理解するのに数秒かかります。
ジョンクリストファージョーンズ

1
@ naught101トレードオフのための厄介な驚きで状況的に役立ちます。ユーザーの場合:Pythonでは、任意のオブジェクトが独自のstr / reprメソッドを提供できます。JSONのシリアル化を容易にするために、少し複雑なキーオブジェクト(名前付きタプルなど)に対してこれを実行したくなる場合もあります。これで、名前でキーを分解できない理由が頭に浮かびます。また、なぜこれはアイテムに対しては機能するが、属性に対しては機能しないのですか?多くの図書館はattrsを好みます。実装者にとって、このES6機能はシンボル(バインド可能な名前)と文字列を混同します。JavaScriptでは妥当ですが、Pythonにははるかに豊富なアイデアがあります。また、見た目も醜いです。
ジョンクリストファージョーンズ

回答:


10

ローカル辞書の使用に伴う問題を恐れて、元の戦略に従うことを好む場合は、Python2.7および3.1コレクションのOrderedDictionaries.OrderedDictsを使用すると、辞書アイテムを最初に挿入された順序で復元できます。


@Stephen幸運なことに、OrderedDictsはpython3.1からpython2.7に移植されました
joaquin 2010年

楽しみにしています...!それは私にとって常にPythonのこだわりであると思っていました(注文した辞書には他のバッテリーが搭載されていませんでした)
hatmatrix 2010年

4
現在、3.5以降では、すべての辞書が注文されています。これはまだ「保証」されていません。つまり、変更される可能性があります。
チャールズメリアム2016

3
3.6以降で保証されています。
naught1 0119

118
from operator import itemgetter

params = {'a': 1, 'b': 2}

a, b = itemgetter('a', 'b')(params)

手の込んだラムダ関数や辞書の理解の代わりに、組み込みのライブラリを使用することもできます。


9
これはおそらく最もPython的な方法であるため、受け入れられる答えになるはずです。attrgetterオブジェクト属性(obj.a)で機能する同じ標準ライブラリモジュールから使用するように回答を拡張することもできます。これは、JavaScriptとの大きな違いobj.a === obj["a"]です。
ジョンクリストファージョーンズ

キーが辞書に存在しない場合、KeyError例外が発生します
Tasawar Hussain

2
しかし、あなたは今、破壊ステートメントでaとbを2回入力しています
オットー

1
@JohnChristopherJonesそれは私にはあまり自然に思えませんが、既存のものを使用しても、それが理解できるとは限りません。多くの人が実際のコードですぐに理解できるとは思えません。一方、提案されてa, b = [d[k] for k in ('a','b')]いるように、はるかに自然で読みやすいです(フォームははるかに一般的です)。これはまだ興味深い答えですが、それは最も簡単な解決策ではありません。
cglacet

@cglacetそれを読んだり実装したりする回数に依存すると思います。同じ3つのキーを定期的に選択する場合はget_id = itemgetter(KEYS)、後で使用するようなものの方serial, code, ts = get_id(document)が簡単です。確かに、高階関数に慣れている必要がありますが、Pythonは一般的にそれらに非常に慣れています。たとえば、のようなデコレータのドキュメントを参照してください@contextmanager
ジョンクリストファージョーンズ

28

Jochenの提案よりも少ない繰り返しでこれを行う1つの方法は、ヘルパー関数を使用することです。これにより、変数名を任意の順序で一覧表示し、dictにあるもののサブセットのみを分解する柔軟性が得られます。

pluck = lambda dict, *args: (dict[arg] for arg in args)

things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')

また、joaquinのOrderedDictの代わりに、キーを並べ替えて値を取得することもできます。唯一の落とし穴は、変数名をアルファベット順に指定し、dict内のすべてを非構造化する必要があることです。

sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)

4
これはちょっとした問題ですがlambda、変数にaを割り当てる場合は、通常の関数構文をdef。で使用することをお勧めします。
Arthur Tacca 2017年

{1、B:2}それはCONST {A、B} =あろうJSようにこれを行うupvotedカント
PirateApp

1
標準ライブラリitemgetterから実装しoperatorました。:)
ジョンクリストファージョーンズ

16

Pythonはシーケンスを「分解」することしかできず、辞書はできません。したがって、必要なものを作成するには、必要なエントリを適切なシーケンスにマップする必要があります。私の場合、私が見つけた最も近い一致は(あまりセクシーではありません)です:

a,b = [d[k] for k in ('a','b')]

これはジェネレーターでも機能します。

a,b = (d[k] for k in ('a','b'))

完全な例を次に示します。

>>> d = dict(a=1,b=2,c=3)
>>> d
{'a': 1, 'c': 3, 'b': 2}
>>> a, b = [d[k] for k in ('a','b')]
>>> a
1
>>> b
2
>>> a, b = (d[k] for k in ('a','b'))
>>> a
1
>>> b
2

10

たぶんあなたは本当にこのようなことをしたいですか?

def some_func(a, b):
  print a,b

params = {'a':1,'b':2}

some_func(**params) # equiv to some_func(a=1, b=2)

ありがとう、しかしそれではありません...私は関数内で破壊しています
hatmatrix 2010年

10

JSで破壊的代入が機能するのと同様にそれを行う別の方法は次のとおりです。

params = {'b': 2, 'a': 1}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

私たちが行ったのは、paramsディクショナリをキー値にアンパックすることでした(**を使用)(Jochenの回答のように)、ラムダ署名でそれらの値を取得し、キー名に従って割り当てました-そしてここにボーナスがあります-私たちはまた、ラムダの署名に含まれていないものの辞書を取得します。

params = {'b': 2, 'a': 1, 'c': 3}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

ラムダが適用された後、rest変数には次のものが含まれます:{'c':3}

辞書から不要なキーを省略するのに便利です。

お役に立てれば。


一方で、おもしろいことに、機能的にはもっと良いと思います。あなたはおそらくこれを数回使うでしょう、そしてそうすればあなたもそれに名前を付けるでしょう。(私が関数と言うとき、私はラムダ関数ではなく、意味します)。
cglacet

3

これを試して

d = {'a':'Apple', 'b':'Banana','c':'Carrot'}
a,b,c = [d[k] for k in ('a', 'b','c')]

結果:

a == 'Apple'
b == 'Banana'
c == 'Carrot'

3

警告1:ドキュメントに記載されているように、これはすべてのPython実装で機能することが保証されているわけではありません。

CPython実装の詳細:この関数は、インタープリターでのPythonスタックフレームのサポートに依存しています。これは、Pythonのすべての実装に存在することが保証されているわけではありません。Pythonスタックフレームサポートのない実装で実行している場合、この関数はNoneを返します。

警告2:この関数はコードを短くしますが、可能な限り明示的にするというPythonの哲学と矛盾する可能性があります。さらに、コメントでジョン・クリストファー・ジョーンズが指摘した問題には対処していませんが、キーの代わりに属性を使用して機能する同様の関数を作成することはできます。これは、本当に必要な場合にそれを実行できることを示す単なるデモンストレーションです。

def destructure(dict_):
    if not isinstance(dict_, dict):
        raise TypeError(f"{dict_} is not a dict")
    # the parent frame will contain the information about
    # the current line
    parent_frame = inspect.currentframe().f_back

    # so we extract that line (by default the code context
    # only contains the current line)
    (line,) = inspect.getframeinfo(parent_frame).code_context

    # "hello, key = destructure(my_dict)"
    # -> ("hello, key ", "=", " destructure(my_dict)")
    lvalues, _equals, _rvalue = line.strip().partition("=")

    # -> ["hello", "key"]
    keys = [s.strip() for s in lvalues.split(",") if s.strip()]

    if missing := [key for key in keys if key not in dict_]:
        raise KeyError(*missing)

    for key in keys:
        yield dict_[key]
In [5]: my_dict = {"hello": "world", "123": "456", "key": "value"}                                                                                                           

In [6]: hello, key = destructure(my_dict)                                                                                                                                    

In [7]: hello                                                                                                                                                                
Out[7]: 'world'

In [8]: key                                                                                                                                                                  
Out[8]: 'value'

このソリューションでは、JavaScriptのように、すべてではなく一部のキーを選択できます。ユーザー提供の辞書にも安全です


1

さて、クラスでこれらが必要な場合は、いつでもこれを行うことができます:

class AttributeDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttributeDict, self).__init__(*args, **kwargs)
        self.__dict__.update(self)

d = AttributeDict(a=1, b=2)

いいね。ありがとう、でも呼び出し構文をd ['a']からdaに変更する方法のようですか?そして、おそらくこれらのパラメータに暗黙的にアクセスするメソッドを追加しています...
hatmatrix 2010年

0

@ShawnFumoの回答に基づいて、私はこれを思いついた:

def destruct(dict): return (t[1] for t in sorted(dict.items()))

d = {'b': 'Banana', 'c': 'Carrot', 'a': 'Apple' }
a, b, c = destruct(d)

(dictのアイテムの順序に注意してください)


0

辞書はPython> = 3.7で挿入順序を維持すること保証されているためでこれは、今日これを行うだけで完全に安全で慣用的なことを意味します。

params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)

出力:

1
2

2
すごい!私のポストから9年しかかかりませんでした。:)
hatmatrix

1
問題は、b, a = params.values()名前ではなく順序を使用しているため、実行できないことです。
コーマン

1
@ruoholaそれがこのソリューションの問題です。これは、名前自体ではなく、名前の辞書の順序に依存しているため、私はこれに反対しました。
コーマン

1
そして、それがキーの順序に依存している場合、それは悪い解決策になります。itemgetterこれを行うための最もPython的な方法です。これはPython3.6以下では機能せず、順序に依存しているため、最初は混乱する可能性があります。
コーマン

-1

いいスタイルかどうかはわかりませんが

locals().update(params)

トリックを行います。次に、がありa、辞書にあるbものはすべてparams、対応するローカル変数として使用できます。


2
'params'ディクショナリが何らかの方法でユーザー提供され、適切にフィルタリングされていない場合、これは大きなセキュリティ問題になる可能性があることに注意してください。
Jacek Konieczny 2010年

9
docs.python.org/library/functions.html#localsを引用するには注:この辞書の内容は変更しないでください。変更は、インタプリタが使用するローカル変数と自由変数の値に影響を与えない場合があります。
Jochen Ritzel 2010年

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