将一个传递的字典“解压缩”到Python中函数的名称空间中?

时间:2009-12-13 20:24:34

标签: python function dictionary parameters

在我的工作中,为了方便起见,我经常需要将参数分组到子集中:

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}

这导致每个函数重复三次参数列表。这可以通过装饰器自动完成:

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

这导致装饰器重复三次,每个功能加两次,所以如果你有很多功能,那就更好了。

有更好的方法吗?也许使用eval的东西?谢谢!

11 个答案:

答案 0 :(得分:29)

您始终可以将字典作为参数传递给函数。例如,

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

答案 1 :(得分:10)

如果您希望d.variable语法优于d['variable'],则可以将字典包装在an almost trivial "bunch" object中,例如:

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

它并没有将字典内容完全带入本地命名空间,但如果对对象使用短名称则会关闭。

答案 2 :(得分:7)

假设词典中的所有键都有资格成为标识符, 你可以这样做:

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

答案 3 :(得分:5)

这类似于你的装饰者的想法,但它更通用,因为它允许你传递任意数量的dicts到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

答案 4 :(得分:4)

这是我用作locals().update(d1)解决方法的内容:

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

答案 5 :(得分:3)

这是一个单行内容的解包方法:

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

lambda args ab是我想按顺序解压缩到xy的密钥。 **_可以忽略字典中的任何其他键,即c

答案 6 :(得分:2)

我认为你不能为Python中的dict解压缩获得更多便利。所以,这里有强制性的“如果疼,不要那样做”答案。

映射项访问比Python中的属性访问更麻烦,所以也许你应该传递用户定义类的实例而不是dicts。

答案 7 :(得分:2)

我认为常识是“不要在生产代码中使用inspect模块”,我大多同意这一点。因此,我认为在生产代码中执行以下操作是个坏主意。但是,如果你正在开发一个支持帧的python(比如CPython),这个应该工作:

>>> 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,当在函数体内调用时,它应该是函数的名称空间。我不知道在使用CPython时是否存在或不存在locals()不仅仅是这样做的情况;文档中的警告可能是“修改dict返回的locals()的效果取决于python实现”。因此,虽然它可以在CPython中修改dict,但它可能不在另一个实现中。

更新:此方法实际上无效。

>>> 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,但无法通过正常的本地命名空间查找来查看更新。

答案 8 :(得分:1)

为此,我经常使用.items()方法保持安静。

def unpack(dict):
    for key, value in dict.items():
        print(key, value)

答案 9 :(得分:0)

我编写了一个名为var_arguments的Python包,用于方便地捆绑和解绑在这里应该有用的参数;它可用on 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修改f2以便它接受一个可选的dargs关键字参数,如果存在,它应该提供一个字典列表(dargs = [d1,d2])。关键/价值 这些词典中的对被添加到关键字参数中,否则这些参数将提供给函数的调用,关键字参数具有最高优先级,d2具有第二高,并且 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适用于您拥有一个包含旧值的字典的情况 对于您感兴趣的所有密钥,以及另一个拥有该密钥的字典 所有这些键的新值(以及您可能不想要的其他键)。例如:

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])

答案 10 :(得分:0)

您可以使用sorcery

from sorcery import unpack_dict

x, y = unpack_dict(d1)