【问题标题】:Make kwargs directly accessible使 kwargs 可以直接访问
【发布时间】:2013-11-21 08:41:18
【问题描述】:

我正在重构一段代码,我遇到了以下问题。我有一个 巨大的 参数列表,现在我想将其作为kwargs 传递。代码是这样的:

def f(a, b, c, ...):
  print a
  ...

f(a, b, c, ...)

我将其重构为:

data = dict(a='aaa', b='bbb', c='ccc', ...)
f(**data)

这意味着我必须做:

def f(**kwargs):
  print kwargs['a']
  ...

但这是一个皮塔饼。我想保留:

def f(**kwargs):
  # Do some magic here to make the kwargs directly accessible
  print a
  ...

是否有任何直接的方法可以直接访问 kwargs dict 中的参数,也许是通过使用一些帮助类/库?

【问题讨论】:

  • 如果有人决定不通过a,会发生什么。那么a 的值是多少?
  • 那个人就是我 :) 不会发生,否则会崩溃

标签: python arguments keyword-argument


【解决方案1】:

some ways - 但你也可以像这样包装你的函数:

def f(**kwargs):
    arg_order = ['a', 'b', 'c', ...]
    args = [kwargs.get(arg, None) for arg in arg_order]

    def _f(a, b, c, ...):
        # The main code of your function

    return _f(*args)

示例:

def f(**kwargs):
    arg_order = ['a', 'b', 'c']
    args = [kwargs.get(arg, None) for arg in arg_order]

    def _f(a, b, c):
        print a, b, c

    return _f(*args)

data = dict(a='aaa', b='bbb', c='ccc')
f(**data)

输出:

>>> 
aaa bbb ccc

【讨论】:

  • 确实是非常聪明的把戏。作为一个附带问题,我一直想知道内部函数是否每次都被解释器重新声明。这种方法有缺点吗,尤其是。当循环调用(外部)函数时?
  • 这真是太棒了。
  • @Tibo 我认为这只是一个例子,您可以在包装器之外声明_f
  • 这正是我所要求的。使用的方法有点尴尬,但我可能会使用它。谢谢!
  • 第一个例子的第二行有错字吗?我猜a, b, c 应该都被引用?
【解决方案2】:

好吧,您可以手动更新locals,但文档特别警告不要这样做。

for key, value in kwargs.iteritems(): #Python 2.7 here
    locals()[key] = value

另一个选项是使用exec,虽然通常不赞成,但至少可以保证正常工作。

for key, value in kwargs.iteritems(): #Python 2.7 here
    exec('{} = value'.format(key)

虽然我永远不会向任何人承认你确实做过其中任何一个。

【讨论】:

  • 我强烈怀疑 exec 是否比本地人更好。
  • @VikramSaran,请参阅此处了解为什么 locals 并不总是有效:stackoverflow.com/questions/8028708/… exec 可能不会更优雅,但至少它没有错。
  • 谢谢,但 localsexec 并不是我的小菜一碟。太低级了。
【解决方案3】:

函数内部:

for k, v in kwargs.iteritems():
    locals()[k] = v

【讨论】:

【解决方案4】:

另一种可能的方法(基于您的评论here)是使用属性字典:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr] if attr in self.keys() else None
    def __setattr__(self, attr, value):
        self[attr] = value 

这样使用:

def f(**kwargs):
    kwargs = AttributeDict(kwargs)

示例:

def f(**kwargs):
    kwargs = AttributeDict(kwargs)
    print kwargs.a, kwargs.b, kwargs.c

data = dict(a='aaa', b='bbb', c='ccc')
f(**data)

输出:

>>> 
aaa bbb ccc

注意:如果你想要一个更短的变量名,你总是可以将它命名为x,那么访问权限就是x.a

【讨论】:

  • 有趣。非常类似于Bunch 的建议,对吧?实际上,Bunch 似乎就是你所说的AttributeDict
  • 这里的区别在于它的构造方式,Bunch 需要创建为 Bunch,而 AttributeDict 可以从任何字典创建。
  • 而 AttributeDict 需要创建为 AttributeDict?我看不出有什么区别。您也可以使用 kwargs = Bunch(**kwargs) 从字典中创建 Bunch。或者,您可以更改其构造函数以具有稍微不同的签名。然后根据您的喜好,在函数外部或函数内部创建 Bunch/AttributeDict 的简单变化。
【解决方案5】:

在类似的情况下我不得不重构我的代码,我和你一样不喜欢使用kwargs['a'],这感觉有点尴尬。有时我使用bunch 对象而不是字典,它允许您通过属性访问(params.a)而不是字典查找来访问字段。每次使用参数时,它只为您节省 3 个字符的输入,但我发现它更好看,因为您不需要在参数名称周围加上引号。有各种各样的方法可以实现一个,参见例如theseones.

因此,与其在你的情况下使用 dict,不如使用它

In [1]: class Bunch:
   ...:     def __init__(self, **kwds):
   ...:         self.__dict__.update(kwds)

In [2]: params = Bunch(a = 'aaa', b = 'bbb')

In [3]: def f(p):
   ...:     print p.a
   ...:     

In [4]: f(params)
aaa

我知道这不是您问题的直接答案,但它只是使用 kwargs 的一种替代方法。

【讨论】:

  • 实际上,这与我正在寻找的非常接近。它更容易输入(在使用所有参数之前粘贴params.),并且看起来不错。其他选项(使用localsexec)似乎是灾难的收据:) 无论如何感谢大家的有趣想法。
  • 这还有一个额外的好处,就是我不需要再次输入大量的键(就像我在@InbarRose 的回答中必须做的那样)
【解决方案6】:

在我看来,您正在做多余的工作,最简单的解决方案将保持原样。那些a, b, c 是关键字参数以及位置参数,因此您可以按照自己喜欢的方式调用函数:

>>> def f(a, b, c=3):
...     print a, b, c

所有关键字参数

>>> f(**{'a':1, 'b':2}) 
1 2 3

结合位置和关键字参数

>>> f(5, **{'b':4})
5 4 3

并在关键字参数错误的情况下得到正确的错误

>>> f(**{'d':4, 'a':1, 'b':2}) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'd'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-21
    • 2020-04-19
    • 1970-01-01
    相关资源
    最近更新 更多