将实例变量传递给装饰器

Posted

技术标签:

【中文标题】将实例变量传递给装饰器【英文标题】:Passing instance variables to a decorator 【发布时间】:2021-08-20 00:18:19 【问题描述】:

我发现这个有用的装饰器允许你传入一些可选参数

def mlflow_experiment(
    _func=None,
    *,
    experiment_name=None
  ):
      def experiment_decorator(func):
          @functools.wraps(func)
          def experiment_wrapper(self, *args, **kwargs):
              nonlocal experiment_name

              experiment_id = (
                  mlflow.set_experiment(experiment_name)
                  if experiment_name is not None
                  else None
              )
                ...

              value = func(self, *args, **kwargs)

              return value

          return experiment_wrapper

      if _func is None:
          return experiment_decorator
      else:
          return experiment_decorator(_func)

因此,在这样的用例中,我只需将字符串传递给experiment_name,代码就可以完美运行。

@mlflow_experiment(autolog=True, experiment_name = 'blarg')    
def train_mlflow(self, maxevals=50, model_id=0):
  ...

我一直很难确定装饰器的作用域,但我并不感到惊讶的是,使用传递 __init__ 中定义的实例变量不起作用。

class LGBOptimizerMlfow:
    def __init__(self, arg):
        self.arg = arg

    @mlflow_experiment(autolog=True, experiment_name = self.arg)    
    def train_mlflow(self, maxevals=50, model_id=0):
        ...

>>> `NameError: name 'self' is not defined`

只是为了看看作用域是否存在问题,我在类之外声明了变量并且它起作用了。

为了它,我决定在类中声明一个全局变量,它也可以工作,但它不太理想,特别是如果我想将它作为可选参数传递给类或方法。

class LGBOptimizerMlfow:
    global arg
    arg = 'hi'

    @mlflow_experiment(autolog=True, experiment_name = arg)    
    def train_mlflow(self, maxevals=50, model_id=0):
    ...
  

修改代码以使装饰器接受实例变量的任何帮助都会很可爱。

谢谢!

【问题讨论】:

你不能这样做,因为装饰器是在定义 class 时调用的,远在类的任何实例存在之前。 您省略了实际使用 experiment_name 参数的装饰器部分,因此很难推荐一种解决方法。 @chepner,对此感到抱歉。我意识到我确实遗漏了它。我会重新编辑它。还发现了这个:***.com/questions/11731136/… 【参考方案1】:

装饰器在类被定义时被调用,self 只是用于每个实例方法的参数,而不是类本身提供的东西。因此,self 在您需要用作装饰器的参数时未定义。

您需要修改 experiment_wrapper 以直接从 self 参数中获取名称,而不是从 mflow_experiment 的参数中获取名称。类似的东西

def mlflow_experiment(
    _func=None,
    *,
    experiment_name=None,
    tracking_uri=None,
    autolog=False,
    run_name=None,
    tags=None,
  ):
      def experiment_decorator(func):
          @functools.wraps(func)
          def experiment_wrapper(self, *args, **kwargs):
              nonlocal tracking_uri

              experiment_name = getattr(self, 'experiment_name', None)
              experiment_id = (
                  mlflow.set_experiment(experiment_name)
                  if experiment_name is not None
                  else None
              )
                ...

              with mlflow.start_run(experiment_id=experiment_id
                                  , run_name=run_name
                                  , tags=tags):
              value = func(self, *args, **kwargs)

              return value

          return experiment_wrapper

      if _func is None:
          return experiment_decorator
      else:
          return experiment_decorator(_func)

然后您需要确保每个实例都有一个与之关联的实验名称(或无)。

class LGBOptimizerMlfow:
    def __init__(self, arg, experiment_name=None):
        self.arg = arg
        self.experiment_name = experiment_name

    @mlflow_experiment(autolog=True, experiment_name = self.arg)    
    def train_mlflow(self, maxevals=50, model_id=0):
        ...

另一种选择是让experiment_name 成为train_mflow 的参数,这样可以更轻松地使用相同的方法创建不同的名称。 (这可能更接近您的意图。)

class LGBOptimizerMlfow:
    def __init__(self, arg):
        self.arg = arg

    @mlflow_experiment(autolog=True)    
    def train_mlflow(self, maxevals=50, model_id=0, experiment_name=None):
        if experiment_name is None:
            self.experiment_name = self.arg
        ...

装饰器的定义和上面一样。

【讨论】:

谢谢,说self 对象不在mlflow_experiment 的范围内是否正确,所以当我尝试传递它时,它不知道self 是什么? 对。 self 只是方法本身的另一个参数名称;当您调用该方法时,描述符协议确保名称绑定到调用对象,但它在语言中没有特殊状态。它有助于对装饰器语法进行脱糖;相当于把train_mlflow定义成一个普通的方法,然后写train_mlflow = mlflow_experiment(autolog=True)(train_mlflow)

以上是关于将实例变量传递给装饰器的主要内容,如果未能解决你的问题,请参考以下文章

如何将装饰器中的变量传递给装饰函数中的函数参数?

将变量传递给注入的服务以用作装饰器参数

使用定义为实例变量的装饰器函数

如何将变量传递给自定义 Django 模板加载器?

从 Sitemesh 装饰器传递一个变量

PHP:如何将实例变量传递给闭包?