【问题标题】:How to get method parameter names?如何获取方法参数名称?
【发布时间】:2010-09-18 03:09:09
【问题描述】:

给定 Python 函数:

def a_method(arg1, arg2):
    pass

如何提取参数的数量和名称。即,鉴于我引用了func,我希望func.[something] 返回("arg1", "arg2")

这个使用场景是我有一个装饰器,我希望使用方法参数的顺序与它们作为键出现在实际函数中的顺序相同。即,当我调用a_method("a", "b") 时,打印"a,b" 的装饰器看起来如何?

【问题讨论】:

  • 对于几乎相同问题的不同答案列表,请参阅this other stackoverflow post
  • 您的标题具有误导性:当人们用“函数”一词说“方法”时,通常会想到类方法。对于功能,您选择的答案(来自 Jouni K. Seppanen)很好。但是对于(类)方法,它不起作用,应该使用检查解决方案(来自 Brian)。

标签: python decorator introspection python-datamodel


【解决方案1】:

在 CPython 中,参数的数量是

a_method.func_code.co_argcount

他们的名字都在开头

a_method.func_code.co_varnames

这些是 CPython 的实现细节,因此这可能不适用于 Python 的其他实现,例如 IronPython 和 Jython。

承认“传递”参数的一种可移植方式是使用签名func(*args, **kwargs) 定义您的函数。这在例如matplotlib,外部 API 层将大量关键字参数传递给较低级别​​的 API。

【讨论】:

  • co_varnames 确实适用于标准 Python,但这种方法并不可取,因为它也会显示内部参数。
  • 为什么不使用aMethod.func_code.co_varnames[:aMethod.func_code.co_argcount]?
  • *args 之后的参数无效,例如:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
  • 请改用检查。否则,您的代码在 3.4+ 中无法与 functools.wraps 一起使用。见stackoverflow.com/questions/147816/…
【解决方案2】:

看看inspect 模块 - 这将为您检查各种代码对象属性。

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

其他结果是 *args 和 **kwargs 变量的名称,以及提供的默认值。即。

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

请注意,某些可调用对象在某些 Python 实现中可能是不可自省的。例如,在 CPython 中,C 中定义的一些内置函数不提供有关其参数的元数据。因此,如果您在内置函数上使用inspect.getfullargspec(),您将获得ValueError

从 Python 3.3 开始,您可以使用inspect.signature() 查看可调用对象的调用签名:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

【讨论】:

  • 代码怎么可能知道默认参数(4,)具体对应关键字参数c
  • @fatuhoku 我也在想同样的事情。事实证明这并不模棱两可,因为您只能在连续块的末尾添加默认参数。来自文档:“如果这个元组有 n 个元素,它们对应于 args 中列出的最后 n 个元素”
  • 我认为自从 Python 3.x getargspec(...) 被 inspector.signature(func) 取代
  • 2.6 版更改:返回命名元组 ArgSpec(args, varargs, keywords, defaults)。
  • 没错,@DiegoAndrésDíazEspinoza - 在 Python 3 中,inspect.getargspecdeprecated,但替换为 inspect.getfullargspec
【解决方案3】:

我认为使用装饰器可以满足您的需求。

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

运行它,它将产生以下输出:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.

【讨论】:

    【解决方案4】:

    我认为您正在寻找的是 locals 方法 -

    
    In [6]: def test(a, b):print locals()
       ...: 
    
    In [7]: test(1,2)              
    {'a': 1, 'b': 2}
    

    【讨论】:

    • 这在函数之外是无用的,这是这里感兴趣的上下文(装饰器)。
    • 实际上正是我正在寻找的东西,尽管它不是这里问题的答案。
    【解决方案5】:

    在装饰器方法中,您可以这样列出原始方法的参数:

    import inspect, itertools 
    
    def my_decorator():
    
            def decorator(f):
    
                def wrapper(*args, **kwargs):
    
                    # if you want arguments names as a list:
                    args_name = inspect.getargspec(f)[0]
                    print(args_name)
    
                    # if you want names and values as a dictionary:
                    args_dict = dict(itertools.izip(args_name, args))
                    print(args_dict)
    
                    # if you want values as a list:
                    args_values = args_dict.values()
                    print(args_values)
    

    如果**kwargs对你很重要,那会有点复杂:

            def wrapper(*args, **kwargs):
    
                args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
                args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
                args_values = args_dict.values()
    

    例子:

    @my_decorator()
    def my_function(x, y, z=3):
        pass
    
    
    my_function(1, y=2, z=3, w=0)
    # prints:
    # ['x', 'y', 'z', 'w']
    # {'y': 2, 'x': 1, 'z': 3, 'w': 0}
    # [1, 2, 3, 0]
    

    【讨论】:

    • 这个答案已经部分过时,应该更新。
    • 这段代码甚至无法运行
    【解决方案6】:

    Python 3 版本是:

    def _get_args_dict(fn, args, kwargs):
        args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
        return {**dict(zip(args_names, args)), **kwargs}
    

    该方法返回一个包含 args 和 kwargs 的字典。

    【讨论】:

    • 请注意,[:fn.__code__.co_argcount] 在查找函数参数时非常重要——否则它也包含在函数中创建的名称。
    • 这样做的一个问题是它不显示参数是*args 还是**kwargs
    • 简洁的解决方案。如果可以概括为实例和类方法会更好,因为它们的偏移量需要从 1 开始以跳过 self/cls arg
    【解决方案7】:

    dir()vars() 现在呢?

    似乎完全按照要求做的事情超级简单……

    必须在函数范围内调用。

    但要小心,它会返回所有个局部变量,所以如果需要,请务必在函数的最开头执行此操作。

    还请注意,正如 cmets 中所指出的,这不允许从范围之外完成。所以不完全是 OP 的场景,但仍然与问题标题相匹配。因此我的回答。

    【讨论】:

    • dir() 返回所有变量名的列表 ['var1', 'var2'],vars() 以 {'var1': 0, 'var2': 'something'} 的形式返回字典在当前本地范围内。如果有人想稍后在函数中使用参数变量名称,他们应该保存在另一个局部变量中,因为稍后在可以声明另一个局部变量的函数中调用它会“污染”这个列表。如果他们想在函数之外使用它,他们必须至少运行一次函数并将其保存在全局变量中。所以最好使用inspect模块。
    【解决方案8】:

    Brian's answer 更新:

    如果 Python 3 中的函数只有关键字参数,那么您需要使用 inspect.getfullargspec

    def yay(a, b=10, *, c=20, d=30):
        pass
    inspect.getfullargspec(yay)
    

    产生这个:

    FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})
    

    【讨论】:

      【解决方案9】:

      返回参数名称列表,处理部分函数和常规函数:

      def get_func_args(f):
          if hasattr(f, 'args'):
              return f.args
          else:
              return list(inspect.signature(f).parameters)
      

      【讨论】:

        【解决方案10】:

        Python 3.5+:

        DeprecationWarning:inspect.getargspec() 自 Python 3.0 起已弃用,请使用 inspect.signature() 或 inspect.getfullargspec()

        以前是这样的:

        func_args = inspect.getargspec(function).args
        

        现在:

        func_args = list(inspect.signature(function).parameters.keys())
        

        测试:

        'arg' in list(inspect.signature(function).parameters.keys())
        

        鉴于我们有函数“function”,它接受参数“arg”,这将评估为 True,否则为 False。

        Python 控制台示例:

        Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
        >>> import inspect
        >>> 'iterable' in list(inspect.signature(sum).parameters.keys())
        True
        

        【讨论】:

        • 如果你只想要一个参数列表,那么list(inspect.signature(function).parameters)就足够了,你不需要调用.keys()方法。无论如何,这是一个很好的答案。
        【解决方案11】:

        在带有 Signature 对象的 Python 3.+ 中,获取参数名称到值之间的映射的一种简单方法是使用 Signature 的 bind() 方法!

        例如,这里有一个用于打印这样的地图的装饰器:

        import inspect
        
        def decorator(f):
            def wrapper(*args, **kwargs):
                bound_args = inspect.signature(f).bind(*args, **kwargs)
                bound_args.apply_defaults()
                print(dict(bound_args.arguments))
        
                return f(*args, **kwargs)
        
            return wrapper
        
        @decorator
        def foo(x, y, param_with_default="bars", **kwargs):
            pass
        
        foo(1, 2, extra="baz")
        # This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}
        

        【讨论】:

          【解决方案12】:

          在python 3中,下面是把*args**kwargs变成dict(pythonOrderedDict维护dict命令):

          from functools import wraps
          
          def display_param(func):
              @wraps(func)
              def wrapper(*args, **kwargs):
          
                  param = inspect.signature(func).parameters
                  all_param = {
                      k: args[n] if n < len(args) else v.default
                      for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
                  }
                  all_param .update(kwargs)
                  print(all_param)
          
                  return func(**all_param)
              return wrapper
          

          【讨论】:

            【解决方案13】:

            为了稍微更新一下Brian's answer,现在有一个不错的inspect.signature 向后移植,您可以在较旧的python 版本中使用它:funcsigs。 所以我个人的偏好会去

            try:  # python 3.3+
                from inspect import signature
            except ImportError:
                from funcsigs import signature
            
            def aMethod(arg1, arg2):
                pass
            
            sig = signature(aMethod)
            print(sig)
            

            为了好玩,如果您有兴趣玩 Signature 对象,甚至动态创建带有随机签名的函数,您可以看看我的 makefun 项目。

            【讨论】:

              【解决方案14】:

              这是另一种不使用任何模块获取函数参数的方法。

              def get_parameters(func):
                  keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
                  sorter = {j: i for i, j in enumerate(keys[::-1])} 
                  values = func.__defaults__[::-1]
                  kwargs = {i: j for i, j in zip(keys, values)}
                  sorted_args = tuple(
                      sorted([i for i in keys if i not in kwargs], key=sorter.get)
                  )
                  sorted_kwargs = {
                      i: kwargs[i] for i in sorted(kwargs.keys(), key=sorter.get)
                  }   
                  return sorted_args, sorted_kwargs
              
              
              def f(a, b, c="hello", d="world"): var = a
                  
              
              print(get_parameters(f))
              

              输出:

              (('a', 'b'), {'c': 'hello', 'd': 'world'})
              

              【讨论】:

                【解决方案15】:

                inspect.signature 非常慢。最快的方法是

                def f(a, b=1, *args, c, d=1, **kwargs):
                   pass
                
                f_code = f.__code__
                f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')
                

                【讨论】:

                  【解决方案16】:

                  我在谷歌上搜索如何打印函数名称和为分配提供的参数我必须创建一个装饰器来打印它们,我使用了这个:

                  def print_func_name_and_args(func):
                      
                      def wrapper(*args, **kwargs):
                      print(f"Function name: '{func.__name__}' supplied args: '{args}'")
                      func(args[0], args[1], args[2])
                      return wrapper
                  
                  
                  @print_func_name_and_args
                  def my_function(n1, n2, n3):
                      print(n1 * n2 * n3)
                      
                  my_function(1, 2, 3)
                  
                  #Function name: 'my_function' supplied args: '(1, 2, 3)'
                  

                  【讨论】:

                    【解决方案17】:

                    是否可以在下面的代码中使用inspect API 从 lambda func fun 中读取常量参数值 -1

                    def my_func(v, axis):
                      pass
                    
                    fun = lambda v: my_func(v, axis=-1)
                    

                    【讨论】:

                      猜你喜欢
                      • 2010-09-17
                      • 2015-10-01
                      • 2011-10-09
                      • 2016-10-21
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多