在python中包装装饰器中的构造函数

时间:2017-12-29 18:39:20

标签: python constructor python-decorators

我希望有一个方便的装饰器来检查传递给方法的属性值是否不是None。然后我想在类方法中将它用作通用装饰器。为此,我写道:

def check_empty(name):
  def wrap_outer(function):
    def wrapped(*args, **kwargs):
      if kwargs.get(name, None) is None:
        raise Exception('{item} cannot be empty'.format(item=name))
      return function(args, kwargs)
    return wrapped
  return wrap_outer

并且,该类定义为:

class Player(object):
  @check_empty('name')
  def __init__(self, name):
    self.name = name

def __str__(self):
  return self.name

然而,这不起作用。构造函数和装饰器的模板不匹配。我们如何为构造函数创建函数装饰器?或者基于类的装饰器是更好的选择?

感谢@ ajax1234,我发现了问题。这就是我传递argskwargs的方式。应该是:

def check_empty(attr):
  def wrap_outer(function):
    def wrapped(*args, **kwargs):
      if (len(args) > 1 and args[1] is None) or (len(args)==1 and len(kwargs)==0):
        raise Exception('{item} cannot be empty'.format(item=attr))
      elif attr in kwargs and kwargs.get(attr, None) is None:
        raise Exception('{item} cannot be empty'.format(item=attr))
    return function(*args, **kwargs)
  return wrapped
return wrap_outer

如果参数作为关键字参数传递或仅作为位置参数传递,则负责检查参数。

注意:它确实有一个限制,即位置参数逻辑在保持通用性质的同时不能很好地扩展。 (我们需要使用inspect.getargspec查看位置参数的位置,然后从那里进行构建。)

2 个答案:

答案 0 :(得分:1)

我能看到你能够做你想做的事情的唯一方法,包括位置和关键字参数,是提供位置到名称的映射,如下所示:

class NoneException(Exception):
    pass


def check_none(**check):
    def wrapper(fn):
        def wrapped(*args, **kwargs):
            for key, pos in check.items():
                try:
                    if kwargs[key] == None:
                        raise NoneException(f'{key} cannot be empty')
                except KeyError:
                    pass

                try:
                    if args[pos] == None:
                        raise NoneException(f'{key} cannot be empty')
                except IndexError:
                    pass

            return fn(*args, **kwargs)

        return wrapped

    return wrapper


class A:
    @check_none(name=1)
    def __init__(self, name):
        self.name = name

    @check_none(phrase=1)
    def test(self, phrase):
        print(f'My name is {self.name}. {phrase}!')


a = A('A')

a.test('Hello world!')

try:
    a.test(None)
except NoneException:
    print('successfully failed')
else:
    print('something went wrong')

这应该涵盖你在位置上明确命名的参数的情况,然而请注意,考虑到必须保持位置参数索引的映射,这是一个脆弱的解决方案。

如果有某种方法可以反映出来自Python内部的映射self=0, name=1, et cetera,我建议改用它。

或者,更简单的解决方案是在您要检查的每个功能的顶部进行函数调用。

class NoneException(Exception):
    pass

def check_none(*args, **kwargs):
    for key, value in (*enumerate(args), *kwargs.items()):
        if value == None:
            raise NoneException(f'key {key} == None')

class A:
    def __init__(self, name):
        check_none(name)

        self.name = name

答案 1 :(得分:0)

你可以试试这个:

def check_empty(attr):
   def check_method(function):
     def wrapped(cls, **kwargs):
       if kwargs.get(attr) is None:
         raise Exception('{item} cannot be empty'.format(item=attr))
       return function(cls, **kwargs)
     return wrapped
   return check_method

class Player(object):
  @check_empty('name')
  def __init__(self, **kwargs):
    self.__dict__ = kwargs
  def __str__(self):
     return self.name

p = Player(name=None)

输出:

Exception: Name cannot be empty

但是,None以外的任何参数都可以:

p = Player(name = 'Foo')
print(p.name)

输出:

'Foo'