【问题标题】:function access variable declared in decorator在装饰器中声明的函数访问变量
【发布时间】:2016-02-08 08:38:19
【问题描述】:

我想记录很多指标,例如函数执行所需的时间。我希望解决方案是轻量级的。添加一个装饰器并完成。也许 90% 的时间是这样的:

@LogMetrics
def my_business_logic(var1):
  print 'done'

但是,我还希望能够在 10% 的时间内访问指标对象并执行类似的操作:

@LogMetrics
def my_business_logic(var1):
  metrics.inc_counter('counter_a')
  print 'done'

到目前为止,我有一个处理第一个场景的装饰器:

class LogMetrics(object):

  def __init__(self, module=None):
    self.module = module


  def __call__(self, target):

    def wrapped(*args, **kwargs):
      # set up metrics
      metrics = Metrics(module=module)

      metrics.start_timer()
      try:
        result = target(*args, **kwargs)
      except Exception as e:
        metrics.set_fault()
        raise e
      finally:
        metrics.stop_timer()
        metrics.push()

      return result

    return wrapped

如何支持方案 2?我想做类似answer 的事情,但共识似乎是不这样做。请帮忙...

【问题讨论】:

  • 神奇地与globals() 搞混确实是一个非常糟糕的主意,不要那样做。如果必须,只需创建一个老式的单例。
  • 当我需要在任何地方使用这个指标对象时,单例如何提供帮助?
  • 您可以添加一些全局/静态字典,它将存储您想要装饰的所有函数和方法的 {qualname: Metric} 字典。然后在 LogMetric.__call__ 中,您可以为包装函数获取适当的 Metric。

标签: python python-2.7 logging python-decorators


【解决方案1】:

您可以为函数添加属性。我做了一个非常简单的例子(在 Python3 中)。 add_counter 装饰器将 _counter 属性添加到原始函数(您将添加一个 metrics 对象)。此示例中的计数器在每次调用后递增。最后,一个_orig_func 属性被添加到装饰函数中以允许访问计数器。

def add_counter(func):
    func._counter = 0 
    def decorated(*args, **kwargs):
        result = func(*args, **kwargs)
        func._counter += 1
        return result
    decorated._orig_func = func
    return decorated

@add_counter
def func1():
    print("FUNC1")
    print(func1._orig_func._counter)

@add_counter
def func2():
    print("FUNC2")
    print(func2._orig_func._counter)

func1()
func1()
func1()
func2()
func2()
func1()

【讨论】:

    【解决方案2】:

    不涉及全局对象或禁止魔法的唯一解决方案是将您的指标对象作为可选参数传递给您的函数(我假设如果您可以编辑它,那么您可以更改它的签名):

    class LogMetrics(object):
    
      ...
    
      def __call__(self, target):
    
        def wrapped(*args, **kwargs):
          # set up metrics
          metrics = Metrics(module=module)
          kwargs['metrics'] = metrics  # add metrics object to kwargs
          metrics.start_timer()
          try:
            result = target(*args, **kwargs)
          except Exception as e:
            metrics.set_fault()
            raise e
          finally:
            metrics.stop_timer()
            metrics.push()
          return result
        return wrapped
    
    
    @LogMetrics
    def my_business_logic(var1, metrics=None):
      if metrics is not None:
          metrics.inc_counter('counter_a')
      print 'done'
    

    顺便问一下,您确定要在 wrapped 中而不是在 __call__ 中初始化 Metrics 吗?

    【讨论】:

    • 关于额外的参数:我担心是这样。我不关注您关于 LogMetrics 的问题。 LogMetrics 未在包装中初始化..
    • 关于额外参数:这意味着方法的签名显示了一个指标输入。我想这意味着该方法需要命名空间。即该方法的公共版本没有度量 arg,但它调用该方法的私有版本,该方法采用度量 arg。
    • 我的错误:我的意思是Metric,而不是LogMetric。关于带有度量参数的私有函数:是的,这可能是最干净的方法。
    • 嗯.. 我并不总是需要公制参数。我想我只需要在函数签名中有一个指标 arg 时才将它添加到 kwargs
    猜你喜欢
    • 1970-01-01
    • 2016-11-24
    • 2020-05-02
    • 1970-01-01
    • 2019-12-21
    • 1970-01-01
    • 2019-08-07
    • 2023-03-26
    • 1970-01-01
    相关资源
    最近更新 更多