【问题标题】:Function Decorators函数装饰器
【发布时间】:2010-10-27 16:59:17
【问题描述】:

我喜欢能够衡量我编写的 Python 函数的性能,所以我经常做类似的事情......

import time

def some_function(arg1, arg2, ..., argN, verbose = True) :
    t = time.clock() # works best in Windows
    # t = time.time() # apparently works better in Linux

    # Function code goes here

    t = time.clock() - t
    if verbose :
        print "some_function executed in",t,"sec."

    return return_val

是的,我知道您应该使用 timeit 来衡量性能,但这正好满足我的需求,并且允许我非常顺利地打开和关闭此信息以进行调试。

那段代码当然是在我知道函数装饰器之前......我现在对它们了解不多,但我认为我可以使用 **kwds 字典编写一个执行以下操作的装饰器:

some_function(arg1, arg2, ..., argN) # Does not time function
some_function(arg1, arg2, ..., argN, verbose = True) # Times function

不过,我还是想复制我之前的函数的工作,这样工作就会更像:

some_function(arg1, arg2, ..., argN) # Does not time function
some_function(arg1, arg2, ..., argN, False) # Does not time function
some_function(arg1, arg2, ..., argN, True) # Times function

我想这需要装饰器计算参数的数量,知道原始函数将占用多少,去掉多余的,将正确数量的参数传递给函数......我不确定如何告诉python这样做......有可能吗?有没有更好的方法来达到同样的效果?

【问题讨论】:

    标签: python decorator argument-passing


    【解决方案1】:

    虽然inspect 可能会让您有所帮助,但您想要的通常不可能

    def f(*args):
        pass
    

    现在f 有多少个参数?由于*args**kwargs 允许任意数量的参数,因此无法确定函数所需的参数数量。事实上,在某些情况下,函数真正处理的数量与抛出的数量一样多!


    编辑:如果您愿意将verbose 作为一个特殊的关键字参数,您可以这样做:

    import time
    
    def timed(f):
        def dec(*args, **kwargs):
            verbose = kwargs.pop('verbose', False)
            t = time.clock()
    
            ret = f(*args, **kwargs)
    
            if verbose:
                print("%s executed in %ds" % (f.__name__, time.clock() - t))
    
            return ret
    
        return dec
    
    @timed
    def add(a, b):
        return a + b
    
    print(add(2, 2, verbose=True))
    

    (感谢Alex Martelli 提供kwargs.pop 提示!)

    【讨论】:

    • 但是“some_function(arg1, arg2, ..., argN, True)”还必须运行什么?
    • 嗯,这需要另一种装饰器。一种使用检查或采用额外的数值来指示修饰函数采用的固定数量的参数。
    • 感谢斯蒂芬为我写出来!我在下班回家的路上正在考虑这个问题,我得出的结论是,为此使用关键字参数是一种更好的方法,因为它允许我包含其他参数,例如函数的计时次数,以及打印输出是否应该反映平均时间、最短时间或更详细的统计数据...
    【解决方案2】:

    然而,Stephan202 的答案 +1(将其放在单独的答案中,因为 cmets 不能很好地格式化代码!),该答案中的以下代码:

    verbose = False
    if 'verbose' in kwargs:
         verbose = True
         del kwargs['verbose']
    

    可以更清晰简洁地表达为:

    verbose = kwargs.pop('verbose', False)
    

    【讨论】:

      【解决方案3】:

      这可能很困难,但您可以在这些方面做一些事情。下面的代码尝试删除任何额外的参数并将它们打印出来。

      def mydeco(func):
          def wrap(*args, **kwargs):
              """
              we want to eat any extra argument, so just count args and kwargs
              and if more(>func.func_code.co_argcount) first take it out from kwargs 
              based on func.func_code.co_varnames, else last one from args
              """
              extraArgs = []
      
              newKwargs = {}
              for name, value in kwargs.iteritems():
                  if name in func.func_code.co_varnames:
                      newKwargs[name] = value
                  else:
                      extraArgs.append(kwargs[name])
      
              diff = len(args) + len(newKwargs) - func.func_code.co_argcount
              if diff:
                  extraArgs.extend(args[-diff:])
                  args = args[:-diff]
      
              func(*args, **newKwargs)
              print "%s has extra args=%s"%(func.func_name, extraArgs)
      
          return wrap
      
      @mydeco
      def func1(a, b, c=3):
          pass
      
      func1(1,b=2,c=3, d="x")
      func1(1,2,3,"y")
      

      输出是

      func1 has extra args=['x']
      func1 has extra args=['y']
      

      【讨论】:

        猜你喜欢
        • 2011-10-04
        • 2019-05-26
        • 2011-06-06
        • 2011-06-25
        • 2014-07-21
        • 2020-05-20
        • 2021-04-19
        • 1970-01-01
        相关资源
        最近更新 更多