【问题标题】:Give function defaults arguments from a dictionary in Python从 Python 中的字典中提供函数默认参数
【发布时间】:2019-12-26 18:39:06
【问题描述】:

假设我有一个字典:

d = {'a': 3, 'b':4}

我想创建一个与这个函数做完全相同的事情的函数 f:

def f(x, a=d['a'], b=d['b']):
  print(x, a, b)

(不一定是打印,而是对变量做一些事情并直接从他们的名字中调用)。

但是我想直接从dict中创建这个函数,也就是说,我想要一些看起来像的东西

def f(x, **d=d):
  print(x, a, b)

其行为类似于之前定义的函数。这个想法是我有一个大字典,其中包含我的函数参数的默认值,我不想这样做

def f(a= d['a'], b = d['b'] ...)

我不知道在 python 中是否有可能。任何见解都值得赞赏!

编辑:这个想法是能够调用f(5, a=3)。 Edit2:问题不是将存储在字典中的参数传递给函数,而是定义一个函数,其参数名称和默认值存储在字典中。

【问题讨论】:

  • 可以创建一个只取字典的函数,然后在函数内部解析字典
  • 为什么不传递整个字典?搜索诸如trick之类的原因是什么?
  • 这个想法是我希望能够调用 f(3, a=5) 而不必传递字典。
  • 没有一种“干净”的方法可以做到这一点。要在函数中创建局部变量(实际上就是关键字参数),您可能不得不求助于exec,如this answerrelated question 中所示。
  • 虽然我发现这是一个有趣的问题,但我认为重新思考和设计可能更安全、更惯用,以便您只使用显式字典(或其他对象)而不是函数的本地命名空间。

标签: python


【解决方案1】:

将此作为答案发布,因为评论太长了。

小心this answer。如果你尝试

@kwargs_decorator(a='a', b='b')
def f(x, a, b):
    print(f'x = {x}')
    print(f'a = {a}')
    print(f'b = {b}')

f(1, 2)

会报错:

TypeError: f() got multiple values for argument 'a'

因为您将 a 定义为位置参数(等于 2)。

我实施了一种解决方法,尽管我不确定这是否是最佳解决方案:

def default_kwargs(**default):
    from functools import wraps
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            from inspect import getfullargspec
            f_args = getfullargspec(f)[0]
            used_args = f_args[:len(args)]

            final_kwargs = {
                key: value 
                for key, value in {**default, **kwargs}.items() 
                if key not in used_args
            }

            return f(*args, **final_kwargs)
        return wrapper
    return decorator

在此解决方案中,f_args 是一个列表,其中包含 f 的所有命名位置参数的名称。然后used_args 是作为位置参数有效传递的所有参数的列表。因此,final_kwargs 的定义几乎与之前完全相同,只是它检查参数(在上述情况下为 a)是否已作为位置参数传递。

例如,此解决方案与以下功能完美配合。

@default_kwargs(a='a', b='b', d='d')
def f(x, a, b, *args, c='c', d='not d', **kwargs):
    print(f'x = {x}')
    print(f'a = {a}')
    print(f'b = {b}')
    for idx, arg in enumerate(args):
        print(f'arg{idx} = {arg}')
    print(f'c = {c}')
    for key, value in kwargs.items():
        print(f'{key} = {value}')

f(1)
f(1, 2)
f(1, b=3)
f(1, 2, 3, 4)
f(1, 2, 3, 4, 5, c=6, g=7)

还要注意default_kwargs 中传递的默认值比f 中定义的优先级更高。例如,这种情况下d 的默认值实际上是'd'(在default_kwargs 中定义),而不是'not d'(在f 中定义)。

【讨论】:

    【解决方案2】:

    这个问题很有趣,似乎不同的人对这个问题真正想要什么有不同的猜测。

    我也有自己的。这是我的代码,可以表达自己:

    # python3 only
    from collections import defaultdict
    
    # only set once when function definition is executed
    def kwdefault_decorator(default_dict):
        def wrapper(f):
            f.__kwdefaults__ = {}
            f_code = f.__code__
            po_arg_count = f_code.co_argcount
            keys = f_code.co_varnames[po_arg_count : po_arg_count + f_code.co_kwonlyargcount]
            for k in keys:
                f.__kwdefaults__[k] = default_dict[k]
            return f
    
        return wrapper
    
    default_dict = defaultdict(lambda: "default_value")
    default_dict["a"] = "a"
    default_dict["m"] = "m"
    
    @kwdefault_decorator(default_dict)
    def foo(x, *, a, b):
        foo_local = "foo"
        print(x, a, b, foo_local)
    
    @kwdefault_decorator(default_dict)
    def bar(x, *, m, n):
        bar_local = "bar"
        print(x, m, n, bar_local)
    
    foo(1)
    bar(1)
    # only kw_arg permitted
    foo(1, a=100, b=100)
    bar(1, m=100, n=100)
    

    输出:

    1 a default_value
    1 m default_value
    1 100 100
    1 100 100
    

    【讨论】:

      【解决方案3】:

      无法在函数定义中实现这一点,因为 Python 静态确定函数的范围。虽然,可以编写一个装饰器来添加默认关键字参数。

      from functools import wraps
      
      def kwargs_decorator(dict_kwargs):
          def wrapper(f):
              @wraps(f)
              def inner_wrapper(*args, **kwargs):
                  new_kwargs = {**dict_kwargs, **kwargs}
                  return f(*args, **new_kwargs)
              return inner_wrapper
          return wrapper
      

      用法

      @kwargs_decorator({'bar': 1})
      def foo(**kwargs):
          print(kwargs['bar'])
      
      foo() # prints 1
      

      或者,如果您知道变量名但不知道它们的默认值...

      @kwargs_decorator({'bar': 1})
      def foo(bar):
          print(bar)
      
      foo() # prints 1
      

      警告

      例如,上面可以用于动态生成具有不同默认参数的多个函数。虽然,如果您要传递的参数对于每个函数都相同,那么简单地传递 dict 的参数会更简单、更惯用。

      【讨论】:

      • 这很酷,但我还是要在foo的代码中做kwargs['bar'],我想在foo的代码中调用print(bar)
      • 所以你想设置一个参数?不是夸格?我认为这是不可能的,因为本地范围是在函数定义中构建的
      • 再一次,我的想法是我有很多变量,我不想手动解析所有变量。
      • @StatisticDean,您想使用关键字访问函数参数并且不在声明中写此关键字?
      • 要明确一点,如果你想要单独的局部变量,你仍然必须在函数签名中包含 all 关键字。也就是说,默认值字典中的每个键都必须出现在函数签名中。所以如果字典有十几个键,但你的函数真的只需要其中三个,你仍然需要在函数签名中列出所有 12 个。
      【解决方案4】:

      当然... 希望这会有所帮助

      def funcc(x, **kwargs):
          locals().update(kwargs)
          print(x, a, b, c, d)
      
      kwargs = {'a' : 1, 'b' : 2, 'c':1, 'd': 1}
      x = 1
      
      funcc(x, **kwargs)
      

      【讨论】:

      • 用这么多变量更新全局变量真的很不安全。求有机会掩盖重要的事情。
      • 这也会将变量泄漏到 outer 范围。避免。
      • locals代替globals会更好。
      【解决方案5】:

      Python 的设计使得任何函数的局部变量都可以通过查看函数的源代码来明确确定。所以你建议的语法

      def f(x, **d=d):
        print(x, a, b)
      

      是一个失败者,因为没有任何东西表明ab 是否是f 的本地对象;它取决于字典的运行时值,其值可能会随着运行而改变。

      如果您愿意明确列出所有参数的名称,则可以在运行时自动设置它们的默认;其他答案已经很好地涵盖了这一点。无论如何,列出参数名称可能是很好的文档。

      如果您真的想在运行时从d 的内容合成整个参数列表,您必须构建函数定义的字符串表示并将其传递给exec。例如,collections.namedtuple 就是这样工作的。

      模块和类范围内的变量是动态查找的,所以这在技术上是有效的:

      def f(x, **kwargs):
          class C:
              vars().update(kwargs)  # don't do this, please
              print(x, a, b)
      

      但请不要这样做,除非在 IOPCC 条目中。

      【讨论】:

      • 文档甚至不完全清楚这是“技术上有效的”。我在我的电脑上试过,它恰好适用于我的 Python 版本,在我的机器上。但是,如果您查看 vars()locals()__dict__ 的文档,您会得到明显的印象,即这属于 C 人员通常所说的“未定义行为”。
      【解决方案6】:

      你可以解压dict的值:

      from collections import OrderedDict
      
      def f(x, a, b):
          print(x, a, b)
      
      d = OrderedDict({'a': 3, 'b':4})
      f(10, *d.values())
      

      UPD。

      是的,可以通过创建装饰器来实现修改本地范围的疯狂想法,该装饰器将返回具有覆盖 __call__() 的类并将您的默认值存储在类范围中,但它是大规模的过度杀伤。。 p>

      您的问题是您试图将您的架构问题隐藏在这些技巧背后。如果您将默认值存储在 dict 中,则可以通过键访问它们。如果你想使用关键字 - 定义类。

      附注我仍然不明白为什么这个问题会获得如此多的支持。

      【讨论】:

      • 减去...什么?
      • 我没有投反对票,但我不明白这是如何回答问题的,因为f 没有任何默认值。
      • @wjandrea,花了一些时间才明白作者想要什么(不仅对我而言)。
      【解决方案7】:

      试试这个:

      # Store the default values in a dictionary
      >>> defaults = {
      ...     'a': 1,
      ...     'b': 2,
      ... }
      >>> def f(x, **kwa):
              # Each time the function is called, merge the default values and the provided arguments
              # For python >= 3.5:
              args = {**defaults, **kwa}
              # For python < 3.5:
              # Each time the function is called, copy the default values
              args = defaults.copy()
              # Merge the provided arguments into the copied default values
              args.update(kwa)
      ...     print(args)
      ... 
      >>> f(1, f=2)
      {'a': 1, 'b': 2, 'f': 2}
      >>> f(1, f=2, b=8)
      {'a': 1, 'b': 8, 'f': 2}
      >>> f(5, a=3)
      {'a': 3, 'b': 2}
      

      感谢 Olvin Roght 指出如何在 python >= 3.5 中很好地合并字典

      【讨论】:

      • 在函数中使用默认值(外部变量)有什么意义?这限制了函数的可用性
      • @prashantrana - 因为这是 OP 要求的,基本上。
      • 这不是一个好方法,因为你可以使用 globals().update(kwa) 它做同样的工作
      • @prashantrana - 不,它不是在做同样的事情。 OP 对更新全局变量不感兴趣。
      • 合并两个字典不需要这么多代码:return {**defaults, **kwa}
      【解决方案8】:

      **kwargs 技巧怎么样?

      def function(arg0, **kwargs):
          print("arg is", arg0, "a is", kwargs["a"], "b is", kwargs["b"])
      
      d = {"a":1, "b":2}
      function(0., **d)
      

      结果:

      arg is 0.0 a is 1 b is 2
      

      【讨论】:

      • 问题是我希望我的函数将 d 的键作为参数,而不是任何关键字,如果我的 d 很大,我必须编写大量代码才能获得所需的行为,这适得其反,(写f(x, a =d['a'], b= d['b'])比这快。)
      • 对,我明白了。抱歉,我误解了你的问题。
      猜你喜欢
      • 2021-09-28
      • 2018-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多