繁体   English   中英

如何使用装饰器检查参数

[英]How to use a decorator to check arguments

我有几个要提供默认参数的函数。 由于这些默认值应该是新初始化的对象,因此我不能仅在参数列表中将它们设置为默认值。 它引入了许多笨拙的代码,以将其默认设置为“无”并签入每个函数

def FunctionWithDefaults(foo=None):
  if foo is None:
    foo = MakeNewObject()

为了减少重复的代码,我制作了一个装饰器来初始化参数。

@InitializeDefaults(MakeNewObject, 'foo')
def FunctionWithDefaults(foo=None):
  # If foo wasn't passed in, it will now be equal to MakeNewObject()

我写的装饰器是这样的:

def InitializeDefaults(initializer, *parameters_to_initialize):
  """Returns a decorator that will initialize parameters that are uninitialized.

  Args:
    initializer: a function that will be called with no arguments to generate
        the values for the parameters that need to be initialized.
    *parameters_to_initialize: Each arg is the name of a parameter that
        represents a user key.
  """
  def Decorator(func):
    def _InitializeDefaults(*args, **kwargs):
      # This gets a list of the names of the variables in func.  So, if the
      # function being decorated takes two arguments, foo and bar,
      # func_arg_names will be ['foo', 'bar'].  This way, we can tell whether or
      # not a parameter was passed in a positional argument.
      func_arg_names = inspect.getargspec(func).args
      num_args = len(args)
      for parameter in parameters_to_initialize:
        # Don't set the default if parameter was passed as a positional arg.
        if num_args <= func_arg_names.index(parameter):
          # Don't set the default if parameter was passed as a keyword arg.
          if parameter not in kwargs or kwargs[parameter] is None:
            kwargs[parameter] = initializer()
      return func(*args, **kwargs)
    return _InitializeDefaults
  return Decorator

如果我只需要装饰一次,那么效果很好,但是会因多次装饰而中断。 具体来说,假设我要初始化foo和bar以分隔变量:

@InitializeDefaults(MakeNewObject, 'foo')
@InitializeDefaults(MakeOtherObject, 'bar')
def FunctionWithDefaults(foo=None, bar=None):
  # foo should be MakeNewObject() and bar should be MakeOtherObject()

中断是因为在第二个装饰器中,func_arg_defaults获取第一个装饰器的参数,而不是FunctionWithDefaults。 结果,我无法确定是否已经在位置上传递了参数。

有什么干净的方法可以解决这个问题?

无效的事情:

  • functools.wraps(func)将函数的__name__为装饰后的函数,但它不会更改参数(除非有其他方法可以使用)。
  • inspect.getcallargs在第二个装饰器中没有引发异常
  • 使用func.func_code.co_varnames不会在第二个装饰器中提供函数的参数

我可以只使用一个装饰器,但这看起来不太干净,感觉应该有一个更干净的解决方案。

谢谢!

这可能是关于python中的装饰器的最佳建议:使用wrapt.decorator

这将使编写装饰器的任务变得更加简单(您无需在函数内的函数内部编写函数),并且避免了编写简单装饰器时每个人都会犯的所有错误。 要了解这些内容,您可能需要阅读Graham Dumpleton的博客文章 “您如何实现Python装饰器是错误的”(他非常有说服力)

您可以向包含argspec的包装函数添加一个变量(例如__argspec ):

def Decorator(func):

    # Retrieve the original argspec
    func_arg_names = getattr(func, '__argspec', inspect.getargspec(func).args)

    def _InitializeDefaults(*args, **kwargs):
        # yada yada

    # Expose the argspec for wrapping decorators
    _InitializeDefaults.__argspec = func_arg_names

    return _InitializeDefaults

为什么不更改装饰器以接受参数字典和所需的默认值? 我不明白为什么它比具有不同参数的多个修饰要干净得多。 这是一个适合我的示例。 我对您的代码进行了较小的更改以将字典反映为参数:import inspect

class Person:
    def __init__(self):
        self.name = 'Personality'


class Human(Person):
    def __init__(self):
        self.name = 'Human'


class Animal(Person):
    def __init__(self):
        self.name = 'Animal'

#def InitializeDefaults(**initializerDict):
def InitializeDefaults(**initializerDict):
    """Returns a decorator that will initialize parameters that are uninitialized.

    Args:
      initializer: a function that will be called with no arguments to generate
          the values for the parameters that need to be initialized.
      *parameters_to_initialize: Each arg is the name of a parameter that
          represents a user key.
    """

    def Decorator(func):
        def _InitializeDefaults(*args, **kwargs):
            # This gets a list of the names of the variables in func.  So, if the
            # function being decorated takes two arguments, foo and bar,
            # func_arg_names will be ['foo', 'bar'].  This way, we can tell whether or
            # not a parameter was passed in a positional argument.
            func_arg_names = inspect.getargspec(func).args
            num_args = len(args)
            for parameter in initializerDict:
                # Don't set the default if parameter was passed as a positional arg.
                if num_args <= func_arg_names.index(parameter):
                    # Don't set the default if parameter was passed as a keyword arg.
                    if parameter not in kwargs or kwargs[parameter] is None:
                        kwargs[parameter] = initializerDict[parameter]()
            return func(*args, **kwargs)
        return _InitializeDefaults
    return Decorator


#@InitializeDefaults(Human, 'foo')

@InitializeDefaults(foo = Human, bar = Animal)
def FunctionWithDefaults(foo=None, bar=None):
    print foo.name
    print bar.name


#test by calling with no arguments
FunctionWithDefaults()

#program output
Human
Animal

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM