如何在抽象类级别提供值验证?

时间:2016-02-12 18:35:46

标签: validation python-3.x setter python-decorators

我有一个ABC BaseAbstract类,定义了几个getter / setter属性。

我想要求设置的值为int且从0到15。

@luminance.setter
@abstractproperty
@ValidateProperty(Exception, types=(int,), valid=lambda x: True if 0 <= x <= 15 else False)
def luminance(self, value):
    """
    Set a value that indicate the level of light emitted from the block

    :param value: (int): 0 (darkest) - 15 (brightest)
    :return:
    """
    pass

有人可以帮我弄清楚我的ValidateProperty类/方法应该是什么样子。我从一个类开始并调用accepts方法,但这导致错误:

  

function对象没有属性&#39; func_code&#39;

当前来源:

class ValidateProperty(object):
    @staticmethod
    def accepts(exception, *types, **kwargs):
        def check_accepts(f, **kwargs):
            assert len(types) == f.func_code.co_argcount

            def new_f(*args, **kwds):
                for i, v in enumerate(args):
                    if f.func_code.co_varnames[i] in types and\
                            not isinstance(v, types[f.func_code.co_varnames[i]]):
                        arg = f.func_code.co_varnames[i]
                        exp = types[f.func_code.co_varnames[i]]
                        raise exception("arg '{arg}'={r} does not match {exp}".format(arg=arg,
                                                                                      r=v,
                                                                                      exp=exp))
                        # del exp       (unreachable)

                    for k,v in kwds.__iter__():
                        if k in types and not isinstance(v, types[k]):
                            raise exception("arg '{arg}'={r} does not match {exp}".format(arg=k,
                                                                                          r=v,
                                                                                          exp=types[k]))

                    return f(*args, **kwds)

            new_f.func_name = f.func_name
            return new_f

        return check_accepts

1 个答案:

答案 0 :(得分:1)

我们中的一个人对decoratorsdescriptors(例如属性)和abstracts的工作方式感到困惑 - 我希望不是我。 ;)

这是一个粗略的工作示例:

].join('\r\n');

结果是:

from abc import ABCMeta, abstractproperty

class ValidateProperty:
    def __init__(inst, exception, arg_type, valid):
        # called on the @ValidateProperty(...) line
        #
        # save the exception to raise, the expected argument type, and
        # the validator code for later use
        inst.exception = exception
        inst.arg_type = arg_type
        inst.validator = valid
    def __call__(inst, func):
        # called after the def has finished, but before it is stored
        #
        # func is the def'd function, save it for later to be called
        # after validating the argument
        def check_accepts(self, value):
            if not inst.validator(value):
                raise inst.exception('value %s is not valid' % value)
            func(self, value)
        return check_accepts

class AbstractTestClass(metaclass=ABCMeta):
    @abstractproperty
    def luminance(self):
        # abstract property
        return
    @luminance.setter
    @ValidateProperty(Exception, int, lambda x: 0 <= x <= 15)
    def luminance(self, value):
        # abstract property with validator
        return

class TestClass(AbstractTestClass):
    # concrete class
    val = 7
    @property
    def luminance(self):
        # concrete property
        return self.val
    @luminance.setter
    def luminance(self, value):
        # concrete property setter
        # call base class first to activate the validator
        AbstractTestClass.__dict__['luminance'].__set__(self, value)
        self.val = value

tc = TestClass()
print(tc.luminance)
tc.luminance = 10
print(tc.luminance)
tc.luminance = 25
print(tc.luminance)

要考虑几点:

  • 7 10 Traceback (most recent call last): File "abstract.py", line 47, in <module> tc.luminance = 25 File "abstract.py", line 40, in luminance AbstractTestClass.__dict__['luminance'].__set__(self, value) File "abstract.py", line 14, in check_accepts raise inst.exception('value %s is not valid' % value) Exception: value 25 is not valid 更简单,因为属性设置器只有两个参数:ValidatePropertyself

  • 当使用new_value作为装饰器,并且装饰器接受参数时,您需要class来保存参数,并__init__来实际处理{ {1}} d功能

  • 调用基类属性setter很难看,但你可以在辅助函数中隐藏它

  • 您可能希望使用自定义元类来确保运行验证代码(这也可以避免丑陋的基类属性调用)

我建议使用上面的元类来消除直接调用基类__call__的需要,这里有一个例子:

def

这个子类abstractproperty,让from abc import ABCMeta, abstractproperty class AbstractTestClassMeta(ABCMeta): def __new__(metacls, cls, bases, clsdict): # create new class new_cls = super().__new__(metacls, cls, bases, clsdict) # collect all base class dictionaries base_dicts = [b.__dict__ for b in bases] if not base_dicts: return new_cls # iterate through clsdict looking for properties for name, obj in clsdict.items(): if not isinstance(obj, (property)): continue prop_set = getattr(obj, 'fset') # found one, now look in bases for validation code validators = [] for d in base_dicts: b_obj = d.get(name) if ( b_obj is not None and isinstance(b_obj.fset, ValidateProperty) ): validators.append(b_obj.fset) if validators: def check_validators(self, new_val): for func in validators: func(new_val) prop_set(self, new_val) new_prop = obj.setter(check_validators) setattr(new_cls, name, new_prop) return new_cls 先完成所有工作,然后进行一些额外的处理。即:

  • 浏览创建的类并查找属性
  • 检查基类以查看它们是否具有匹配的abstractproperty
  • 检查abstractproperty的ABCMeta代码以查看它是否是ABCMeta的实例
  • 如果是,请将其保存在验证器列表中
  • 如果验证器列表不为空
    • 制作一个包装器,在调用实际属性的fset代码
    • 之前调用每个验证器
    • 将找到的属性替换为使用包装器作为ValidateProperty代码
    • 的新属性

fset也有点不同:

setter

基础ValidateProperty现在使用新的class ValidateProperty: def __init__(self, exception, arg_type): # called on the @ValidateProperty(...) line # # save the exception to raise and the expected argument type self.exception = exception self.arg_type = arg_type self.validator = None def __call__(self, func_or_value): # on the first call, func_or_value is the function to use # as the validator if self.validator is None: self.validator = func_or_value return self # every subsequent call will be to do the validation if ( not isinstance(func_or_value, self.arg_type) or not self.validator(None, func_or_value) ): raise self.exception( '%r is either not a type of %r or is outside ' 'argument range' % (func_or_value, type(func_or_value)) ) ,并且在AbstractTestClass中直接包含验证码:

AbstractTestClassMeta

最后一堂课是相同的:

abstractproperty