【问题标题】:How to create a function at runtime with specified argument names?如何在运行时创建具有指定参数名称的函数?
【发布时间】:2013-12-20 21:43:38
【问题描述】:

假设我有这个功能:

def f(x,y):
   return x+y

如果我使用inspect.getargspec(f).args,我会得到['x','y']。太好了。

现在假设我想在运行时创建另一个函数 g(a,b),直到运行时我才知道参数名称 ab

def g(a,b):
   return f(a,b)

有没有办法做到这一点? Lambda 几乎是正确的,除了我只能在编译时分配参数名称。

g = lambda *p: f(*p)

不知何故,我想在运行时根据列表 L(例如 L=['a','b'])动态创建函数,以便 inspect.getargspec(g).args == L)。

【问题讨论】:

  • 不确定我是否理解了这个问题。猜猜你可以使用eval 语句创建lambda...你能举一个在运行时创建的函数的例子吗?
  • 您可以使用FunctionTypeCodeType 来构造对象,但我不确定我是否明白这一点。
  • 这听起来像是一个 XY 问题 - 与动态命名的 变量 相同,通常意味着提问者想要一个列表,这感觉像是解决问题的错误途径跨度>
  • 我将随机猜测并假设您的意图是在装饰器中保留参数名称,以便使用 inspect.getargspec 的文档工具提供一些有用的东西 - 在在这种情况下,您需要 decorator 模块
  • @Eric 不错的猜测 :) 我的第一个是:“如何从检查中混淆函数参数”lolz

标签: python function arguments


【解决方案1】:

这是一种有点老套的方法,它首先从现有函数中创建一个新函数并进行修改,然后用它替换原始代码。这主要是因为types.CodeType() 调用有很多参数。 Python 3 版本有些不同,因为许多function.func_code 属性被重命名,types.CodeType() 的调用顺序略有改变。

我从@aaronasterling 的this answer 获得了这个想法(他说他从 Michael Foord 的 Voidspace 博客条目 #583 标题为 Selfless Python 中获得了这个想法)。它可以很容易地制成装饰器,但根据您告诉我们的预期用途,我认为这没有帮助。

import sys
import types

def change_func_args(function, new_args):
    """ Create a new function with its arguments renamed to new_args. """

    if sys.version_info[0] < 3:  # Python 2?
        code_obj = function.func_code
        assert(0 <= len(new_args) <= code_obj.co_argcount)
        # The arguments are just the first co_argcount co_varnames.
        # Rreplace them with the new argument names in new_args.
        new_varnames = tuple(new_args[:code_obj.co_argcount] +
                             list(code_obj.co_varnames[code_obj.co_argcount:]))
        new_code_obj = types.CodeType(code_obj.co_argcount,
                                      code_obj.co_nlocals,
                                      code_obj.co_stacksize,
                                      code_obj.co_flags,
                                      code_obj.co_code,
                                      code_obj.co_consts,
                                      code_obj.co_names,
                                      new_varnames,
                                      code_obj.co_filename,
                                      code_obj.co_name,
                                      code_obj.co_firstlineno,
                                      code_obj.co_lnotab,
                                      code_obj.co_freevars,
                                      code_obj.co_cellvars)
        modified = types.FunctionType(new_code_obj, function.func_globals)

    else:  # Python 3
        code_obj = function.__code__
        assert(0 <= len(new_args) <= code_obj.co_argcount)
        # The arguments are just the first co_argcount co_varnames.
        # Replace them with the new argument names in new_args.
        new_varnames = tuple(new_args[:code_obj.co_argcount] +
                             list(code_obj.co_varnames[code_obj.co_argcount:]))

        new_code_obj = types.CodeType(code_obj.co_argcount,
                                      code_obj.co_posonlyargcount,
                                      code_obj.co_kwonlyargcount,
                                      code_obj.co_nlocals,
                                      code_obj.co_stacksize,
                                      code_obj.co_flags,
                                      code_obj.co_code,
                                      code_obj.co_consts,
                                      code_obj.co_names,
                                      new_varnames,
                                      code_obj.co_filename,
                                      code_obj.co_name,
                                      code_obj.co_firstlineno,
                                      code_obj.co_lnotab)

        modified = types.FunctionType(new_code_obj, function.__globals__)

    function.__code__ = modified.__code__  # replace code portion of original

if __name__ == '__main__':

    import inspect

    def f(x, y):
        return x+y

    def g(a, b):
        return f(a, b)

    print('Before:')
    print('inspect.getargspec(g).args: {}'.format(inspect.getargspec(g).args))
    print('g(1, 2): {}'.format(g(1, 2)))

    change_func_args(g, ['p', 'q'])

    print('')
    print('After:')
    print('inspect.getargspec(g).args: {}'.format(inspect.getargspec(g).args))
    print('g(1, 2): {}'.format(g(1, 2)))

【讨论】:

    【解决方案2】:

    我感觉你想要这样的东西:

    import inspect
    import math
    
    def multiply(x, y):
        return x * y
    
    def add(a, b):
        return a + b
    
    def cube(x):
        return x**3
    
    def pythagorean_theorum(a, b, c):
        return math.sqrt(a**2 + b**2 + c**2)
    
    def rpc_command(fname, *args, **kwargs):
        # Get function by name
        f = globals().get(fname)
        # Make sure function exists
        if not f:
            raise NotImplementedError("function not found: %s" % fname)
        # Make a dict of argname: argvalue
        arg_names = inspect.getargspec(f).args
        f_kwargs = dict(zip(arg_names, args))
        # Add kwargs to the function's kwargs
        f_kwargs.update(kwargs)
        return f(**f_kwargs)
    

    用法:

    >>> # Positional args
    ... rpc_command('add', 1, 2)
    3
    >>> 
    >>> # Keyword args
    ... rpc_command('multiply', x=20, y=6)
    120
    >>> # Keyword args passed as kwargs
    ... rpc_command('add', **{"a": 1, "b": 2})
    3
    >>> 
    >>> # Mixed args
    ... rpc_command('multiply', 5, y=6)
    30
    >>> 
    >>> # Different arg lengths
    ... rpc_command('cube', 3)
    27
    >>> 
    >>> # Pass in a last as positional args
    ... rpc_command('pythagorean_theorum', *[1, 2, 3])
    3.7416573867739413
    >>> 
    >>> # Try a non-existent function
    ... rpc_command('doesntexist', 5, 6)
    Traceback (most recent call last):
      File "<stdin>", line 2, in <module>
      File "<stdin>", line 6, in rpc_command
    NotImplementedError: function not found: doesntexist
    

    【讨论】:

      【解决方案3】:

      使用关键字参数怎么样?

      >>> g = lambda **kwargs: kwargs
      >>> g(x=1, y=2)
      {'y': 2, 'x': 1}
      >>> g(a='a', b='b')
      {'a': 'a', 'b': 'b'}
      

      类似:

      g = lambda **kwargs: f(kwargs.get('a', 0), kwargs['b'])
      

      或者假设您只想使用值:

      >>> g = lambda **kwargs: f(*kwargs.values())
      >>> def f(*args): print sum(args)
      ... 
      >>> g(a=1, b=2, c=3)
      6
      

      在任何情况下,使用**kwargs 语法会导致kwargs 成为所有按名称传递的参数的字典。

      【讨论】:

      • **kwargs 的问题在于结果字典的键并不总是按照您传入的顺序。
      • 值的顺序是任意的,这意味着您的第二个示例可以很容易地以错误的顺序将它们传递给f(),这在大多数情况下都很重要(尽管它与显示的函数无关)。
      【解决方案4】:

      你可以使用 *args 和 **kwargs

      假设您在运行时生成一个动态函数

      def func():
          def dyn_func(*args, **kwargs):
              print args, kwargs
        return dyn_func
      

      然后可以在生成的函数中使用 args

      f = func()
      f(test=1)
      

      会给:

      () {'test': 1}
      

      那么就可以随心所欲地管理args了

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多