web-dev-qa-db-ja.com

渡された辞書をPythonの関数の名前空間に「アンパック」しますか?

私が行う作業では、便宜上サブセットにグループ化する必要があるパラメーターがよくあります。

d1 = {'x':1,'y':2}
d2 = {'a':3,'b':4}

これを行うには、複数の辞書を渡します。ほとんどの場合、渡された辞書を直接使用します。

def f(d1,d2):
    for k in d1:
        blah( d1[k] )

一部の関数では、変数に直接アクセスする必要があり、物事が煩雑になります。ローカルの名前空間にこれらの変数が本当に必要です。私は次のようなことができるようになりたいです:

def f(d1,d2)
    locals().update(d1)
    blah(x)
    blah(y)    

しかし、locals()が返すディクショナリの更新は、実際にネームスペースを更新することは保証されていません。

ここに明白な手動の方法があります:

def f(d1,d2):
    x,y,a,b = d1['x'],d1['y'],d2['a'],d2['b']
    blah(x)
    return {'x':x,'y':y}, {'a':a,'b':b}

これにより、関数ごとにパラメーターリストが3回繰り返されます。これはデコレータで自動化できます:

def unpack_and_repack(f):
    def f_new(d1, d2):
        x,y,a,b = f(d1['x'],d1['y'],d2['a'],d3['b'])
        return {'x':x,'y':y}, {'a':a,'b':b}
    return f_new
@unpack
def f(x,y,a,b):
    blah(x)
    blah(y)
    return x,y,a,b

これにより、デコレータが3回繰り返され、さらに関数ごとに2回繰り返されるため、多くの関数がある場合に適しています。

もっと良い方法はありますか?多分evalを使用して何か?ありがとう!

30
Andrew Wagner

辞書は関数の引数としていつでも渡すことができます。例えば、

dict = {'a':1, 'b':2}
def myFunc(a=0, b=0, c=0):
    print(a,b,c)
myFunc(**dict)
30
Bear

d.variable構文がd['variable']よりも良い場合は、次のように、辞書を ほぼ自明な「束」オブジェクト でラップできます。

class Bunch:
    def __init__(self, **kw):
        self.__dict__.update(kw)

辞書の内容をローカルの名前空間に正確に反映するわけではありませんが、オブジェクトに短い名前を使用すると近づきます。

10

辞書内のすべてのキーが識別子であると仮定すると、次のようにすることができます。

adict = { 'x' : 'I am x', 'y' : ' I am y' }
for key in  adict.keys():
  exec(key + " = adict['" + key + "']")
blah(x)
blah(y)
7
Thava

これはデコレーターのアイデアに似ていますが、任意の数のディクテーションをfooに渡すことができ、デコレーターはディクターのキーについて何も知る必要がないため、少し一般的です。または、基になるfoo関数を呼び出すときの引数の順序。

#!/usr/bin/env python
d1 = {'x':1,'y':2}
d2 = {'a':3,'b':4}

def unpack_dicts(f):
    def f_new(*dicts):
        new_dict={}
        for d in dicts:
            new_dict.update(d)
        return f(**new_dict)
    return f_new

@unpack_dicts
def foo(x,y,a,b):
    print x,y,a,b

foo(d1,d2)
# 1 2 3 4
5
unutbu

これは私がlocals().update(d1)回避策として使用するものです:

def f(d1,d2)
    exec ','.join(d1) + ', = d1.values()'
    blah(x)
    blah(y)
4
Thomas Holder

ワンライナーでの解凍方法は次のとおりです。

x,y = (lambda a,b,**_: (a,b))(**{'a':'x', 'b':'y', 'c': 'z'})

ラムダ引数abは、xyにこの順序で展開するキーです。 **_は、辞書の他のキー、つまりcを無視するためのものです。

3
Doody P

一般的な知識は「inspectモジュールを本番コードで使用しないこと」であると私は思います。そのため、本番コードで次のことを行うのは悪い考えだと思います。しかし、pythonで作業している場合、これは動作するはずです

_>>> def framelocals():
...    return inspect.currentframe(1).f_locals
... 
>>> def foo(ns):
...    framelocals().update(ns)
...    print locals()
... 
>>> foo({'bar': 17})
{'ns': {'bar': 17}, 'bar': 17}
_

実際のdictを呼び出し元のフレームから取得するだけです。これは、関数本体の内部で呼び出されると、関数の名前空間になるはずです。 locals()がとにかくこれを行わない場合、CPythonを使用するときに状況があるかどうかはわかりません。ドキュメントの警告は、「locals()によって返されたdictを変更した場合の影響はpython実装に依存している」ということになるかもしれません。 CPythonでそのdictを変更するには、別の実装ではない場合があります。

UPDATE:このメソッドは実際には機能しません。

_>>> def flup(ns):
...    framelocals().update(ns)
...    print locals()
...    print bar
... 
>>> flup({'bar': 17})
{'ns': {'bar': 17}, 'bar': 17}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in flup
NameError: global name 'bar' is not defined
_

ddaa が示唆するように、関数のコンパイルには、ローカル変数に注意を向ける、より深い魔法があります。したがって、dictを更新できますが、通常のローカル名前空間ルックアップでは更新を確認できません。

2
Matt Anderson

Pythonでdictを展開すると、これ以上便利にならないと思います。だから、ここに「それが痛いならそれをしないでください」という義務的な答えがあります。

アイテムへのアクセスのマッピングIS Pythonの属性へのアクセスよりも面倒なので、dictsではなく、ユーザー定義クラスのインスタンスを渡す必要があります。

2
ddaa

Python var_argumentsと呼ばれるパッケージを書いて、ここで役立つはずの引数を便利にバンドルおよびアンバンドルするために使用します。これは利用可能です github上

書く代わりに、こう言う:

def f(d1,d2):
    x,y,a,b = d1['x'],d1['y'],d2['a'],d2['b']
    y=x+a
    return {'x':x,'y':y}, {'a':a,'b':b}

あなたは書くことができます:

from var_arguments import recon_dict, use_dargs

def f(d1,d2):
  r=f2(dargs=[d1,d2])
  return recon_dict(d1,r), recon_dict(d2,r)

@use_dargs
def f2(x,y,a,b):
  y=x+a
  return locals()

私はこのような解決策を、あなたが行っているように見えるものと一致するように書きました:辞書がグループに到着し、グループから離れ、辞書内のキーの名前に言及したり、手動でそれらにアクセスしたりする回数を最小限に抑えます。具体的には、x、y、a、およびbをこの方法で一度だけ言及する必要があります。

基本的には、@ use_dargsはオプションのdargsキーワード引数を受け入れるようにf2を変更し、存在する場合は辞書のリスト(dargs = [d1、d2])を提供する必要があります。これらのディクショナリのキーと値のペアは、関数の呼び出しに提供されるキーワード引数に追加されます。キーワード引数は、最高の優先度、d2は2番目に高く、d1は最低の優先度です。したがって、さまざまな方法でf2を呼び出して、同じ結果を得ることができます。

f2(1,2,3,4)
f2(1,a=3,dargs=[dict(y=2,b=4)])
f2(dargs=[dict(x=1,y=2),dict(a=3,b=4)])

recon_dictは、関心のあるすべてのキーの古い値を保持する1つのディクショナリと、それらすべてのキー(および場合によっては不要な他のキー)の新しい値を保持する別のディクショナリがある場合に使用します。 。例えば:

old_d=dict(a=8,b=9) # I want these keys, but they're old values
xyab=dict(x=1,y=2,a=3,b=4) # These are the new values, but I don't want all of them
new_d=recon_dict(old_d,xyab)
assert new_d==dict(a=3,b=4)

次に、var_argumentsが処理する関数本体内で変数名を複数回言及する冗長性を取り除くためのいくつかの追加のトリックを示します。まず、変更できます。

{'x':x,'y':y}

に:

ddict('x,y',locals())

同様に、次のように変更できます。

f(x=x,y=y)

に:

dcall(f,'x,y',locals())

より一般的には、キーがxとyの辞書xyがあり、ローカル変数にaとbが含まれている場合、次のように変更できます。

f(x=xy['x'],y=xy['y'],a=a,b=b)

に:

ldcall(f,'x,y,a,b',[locals(),xy])
0
Marc Coram

sorcery を使用できます:

from sorcery import unpack_dict

x, y = unpack_dict(d1)
0
Alex Hall