web-dev-qa-db-ja.com

** kwargsでOrderedDictを使用する

OrderedDictインスタンスを**kwargs構文を使用する関数に渡し、順序を維持することは可能ですか?

私がしたいことは:

def I_crave_order(**kwargs):
    for k, v in kwargs.items():
        print k, v

example = OrderedDict([('first', 1), ('second', 2), ('third', -1)])

I_crave_order(**example)
>> first 1
>> second 2
>> third -1

ただし、実際の結果は次のとおりです。

>> second 2
>> third -1
>> first 1

すなわち、典型的なランダムな辞書の順序。

順序を明示的に設定するのが適切な他の用途があるので、通常の引数としてOrderedDictを渡すだけではなく、**kwargsを保持したい

30
theodox

Python 3.6、 キーワード引数の順序は保持されます 以降。3.6より前では、OrderedDictdict


最初に理解することは、**exampleに渡した値が自動的に**kwargsの値にならないことです。 kwargsexampleの一部のみを持つ場合を考えます。

def f(a, **kwargs):
    pass
example = {'a': 1, 'b': 2}
f(**example)

または、この例では、例よりも多くの値があります。

example = {'b': 2}
f(a=1, c=3, **example)

またはまったく重複していません:

example = {'a': 1}
f(b=2, **example)

だから、あなたが求めていることは本当に意味がありません。

それでも、キーワードがどこから来たのかに関係なく、順序付けられた**kwargsを指定する何らかの方法があったらいいかもしれません—明示的なキーワード引数が出現順に続き、その後に**exampleのすべての項目がexampleに出現する順に続きます(exampledictですが、OrderedDictの場合も意味があります。

手間のかかる詳細をすべて定義し、パフォーマンスを許容範囲に保つことは、思ったよりも難しいことがわかります。アイデアについての議論については PEP 468 とリンクされたスレッドを参照してください。今回は行き詰まっているようですが、誰かがそれを手に入れて擁護した場合(そして、C APIからアクセス可能なOrderedDictに依存する、人々が遊ぶための参照実装を作成した場合) 3.5+に存在する)、私はそれが最終的に言語に入ると思う。


ちなみに、これが可能であれば可能であれば、ほぼ確実にOrderedDict自体のコンストラクタで使用されます。しかし、それを試した場合、あなたがしていることは、永続的な順序として任意の順序をフリーズすることだけです。

>>> d = OrderedDict(a=1, b=2, c=3)
OrderedDict([('a', 1), ('c', 3), ('b', 2)])

一方、どのようなオプションがありますか?

まあ、明らかなオプションは、exampleをアンパックする代わりに通常の引数として渡すことです:

def f(example):
    pass
example = OrderedDict([('a', 1), ('b', 2)])
f(example)

または、もちろん、*argsを使用してアイテムをタプルとして渡すこともできますが、通常は醜いです。

PEPからリンクされたスレッドには他にもいくつかの回避策があるかもしれませんが、実際には、これより優れたものはありません。 (ただし、IIRCを除き、Li Haoyiは MacroPy に基づいて順序保持キーワード引数を渡すソリューションを考え出しましたが、詳細は覚えていません。MacroPyのソリューションは、 MacroPyを使用して、Pythonのように完全に読み取れないコードを書くことをいとわないが、それが常に適切であるとは限らない…)

28
abarnert

python 3.6 のデフォルトになりました。

Python 3.6.0a4+ (default:d43f819caea7, Sep  8 2016, 13:05:34)
>>> def func(**kw): print(kw.keys())
...
>>> func(a=1, b=2, c=3, d=4, e=5)
dict_keys(['a', 'b', 'c', 'd', 'e'])   # expected order

他の回答で指摘されているように、以前はそれを行うことはできません。

16
damio

Python=が署名で**kwargs構文に遭遇すると、kwargsが「マッピング」であると想定します。これは、2つのことを意味します:(1)できることkwargs.keys()を呼び出して、マッピングに含まれる反復可能なキーを取得します。(2)kwargs.__getitem__(key)は、keys()によって返される反復可能なキーごとに呼び出すことができ、結果の値は、そのキーに関連付ける必要のある値です。

内部的には、Pythonは、マッピングが辞書になっているものは何でも「変換」します。次のようになります:

**kwargs -> {key:kwargs[key] for key in kwargs.keys()}

kwargsがすでにdictであると考えると、これは少しばかげています。完全に等価なdictをから構築する理由はないためです。それが渡されます。

ただし、kwargsが必ずしもdictでない場合は、その内容を適切なデフォルトのデータ構造にして、引数のアンパックを実行するコードが処理内容を常に認識できるようにすることが重要です。

したがって、特定のデータ型がアンパッケージされる方法をcan混乱させますが、一貫したarg-unpacking-protocolのためにdictに変換するためです、引数のアンパックの順序に保証を付けることができない場合があります(dictは要素が追加された順序を追跡しないため)。 Python=の言語が**kwargsOrderedDictではなくdictに引き下げた場合(キーワード引数としてのキーの順序は次に、それらがトラバースされる順序)、OrderedDictまたはkeys()がある種の順序を尊重するその他のデータ構造を渡すことにより、できます引数の特定の順序を期待しますdictが標準として選択されているのは実装の癖であり、他のタイプのマッピングではありません。

「アンパック」できるクラスのばかげた例を次に示しますが、アンパックされたすべての値は常に42として扱われます(実際にはそうではありません)。

class MyOrderedDict(object):
    def __init__(self, odict):
        self._odict = odict

    def __repr__(self):
        return self._odict.__repr__()

    def __getitem__(self, item):
        return 42

    def __setitem__(self, item, value):
        self._odict[item] = value

    def keys(self):
        return self._odict.keys()

次に、展開されたコンテンツを出力する関数を定義します。

def foo(**kwargs):
    for k, v in kwargs.iteritems():
        print k, v

値を作って試してみましょう:

In [257]: import collections; od = collections.OrderedDict()

In [258]: od['a'] = 1; od['b'] = 2; od['c'] = 3;

In [259]: md = MyOrderedDict(od)

In [260]: print md
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

In [261]: md.keys()
Out[261]: ['a', 'b', 'c']

In [262]: foo(**md)
a 42
c 42
b 42

このキーと値のペアのカスタマイズされた配信(ここでは、常に42を返す)は、Pythonでの**kwargsの動作をいじる能力の範囲です。

*argsをパッケージ化解除する方法をいじくり回すための柔軟性が少し増えました。詳細については、この質問を参照してください:< 引数の展開では反復またはアイテム取得を使用しますか? >。

3
ely