代码之家  ›  专栏  ›  技术社区  ›  thothal

以dict作为参数调用函数

  •  0
  • thothal  · 技术社区  · 4 年前

    我目前正在使用一个命令行界面 argparse Namespace 对象来自 arg_parse . 基本上,CLI中的参数应该传递给2个函数。现在我正在寻找一个聪明的方法来分割 这样我就可以简单地使用开箱。

    下面的例子应该说明我需要什么:

    import argparse
    ns1 = argparse.Namespace(foo=1)
    ns2 = argparse.Namespace(bar=2)
    ns3 = argparse.Namespace(foo=1, bar=2)
    
    def f(foo): return foo
    def g(bar): return 10 * bar
    
    f(**vars(ns1)) # works
    g(**vars(ns2)) # works
    f(**vars(ns3)) # does not work, 'bar' is not an argument of f
    

    我可以把过滤器硬编码成

    f_args = ["foo"]
    g_args = ["bar"]
    
    f(**{k:v for k, v in vars(ns3).items() if k in f_args}) # works
    g(**{k:v for k, v in vars(ns3).items() if k in g_args}) # works
    

    但这感觉容易出错(如果 f 会变的,我要记得变 f_args 也是。)。但是我可以用 inspect.getfullargspec 将此部分自动化,如:

    import inspect
    f_args = inspect.getfullargspec(f).args
    

    但这一切都让我觉得很刻薄,我想知道我是否忽略了一个简单的模式?


    更新

    正如@doer\u uvc所指出的,一种方法是用 “一网打尽” 参数,如:

    def f(foo, **kwargs): return foo
    

    虽然这是一个可行的解决方案,但我很想找到一些不需要我触及 它自己。

    0 回复  |  直到 4 年前
        1
  •  1
  •   Utsav Chokshi ruddra    4 年前

    如果编写函数在您的范围内,那么让您的函数接受任意数量的关键字参数,并检查函数中感兴趣的参数。

    class MissingArgumentError(ValueError):
        pass
    
    
    def f(**kwargs):
        if not 'foo' in kwargs:
            raise MissingArgumentError('Missing keyword argument `foo`')
        return kwargs['foo']
    
    
    def g(**kwargs):
        if not 'bar' in kwargs:
           raise MissingArgumentError('Missing keyword argument `bar`')
        return 10 * kwargs['bar']
    

    在这种方法中,即使您的函数签名发生了更改,也不需要更改任何其他内容。


    如果您必须编写多个这样的函数,那么您可以使用 decorators .

    def argverifier(compulsory_args:list):
        def actual_decorator(function):
            def inner(**kwargs):
                for arg in compulsory_args:
                    if arg not in kwargs:
                        raise MissingArgumentError(f'Missing keyword argument : {arg}')
                return function(**kwargs)
            return inner
        return actual_decorator
    
    @argverifier(compulsory_args=['foo'])
    def f(**kwargs): return kwargs['foo']
    
    @argverifier(compulsory_args=['bar'])
    def g(**kwargs): return 10 * kwargs['bar']
    

    如果您不希望更改函数的签名,那么可行的解决方案是编写一个函数,使用 inspect 按照你的建议。

    from functools import partial
    import inspect
    
    def f(foo): return foo
    def g(bar): return 10 * bar
    
    def argextractor(args, kwargs):
        return {k:v for k, v in kwargs.items() if k in args}
    
    f_argextractor = partial(argextractor, inspect.getfullargspec(f).args) 
    g_argextractor = partial(argextractor, inspect.getfullargspec(g).args)
    
    f(**f_argextractor(vars(ns3)))
    g(**g_argextractor(vars(ns3)))
    
        2
  •  2
  •   MisterMiyagi    4 年前

    inspect.signature 这些不是黑客,Python签名可能会变得非常复杂 inspect 为了这样的目的而存在。

    import inspect
    
    
    def apply(call: 'Callable', kwargs: 'Dict[str, Any]'):
        """Apply all appropriate ``kwargs`` to ``call``"""
        call_kwargs = inspect.signature(call).parameters.keys()
        matching_kwargs = {name: kwargs[name] for name in kwargs.keys() & call_kwargs}
        return call(**matching_kwargs)
    

    call ,并选择 kwargs . 如果 关键字参数 呼叫 ,将引发缺少参数的标准异常。

    >>> def f(foo):
    ...     return foo
    ...
    >>> apply(f, {'foo': 2, 'bar': 3})
    2
    >>> apply(f, {'baz': 2, 'bar': 3})
    …
    TypeError: f() missing 1 required positional argument: 'foo'