我有一个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类/方法应该是什么样子.我从一个类开始并调用了accept方法,但这导致了一个错误:
function
object has no attribute ‘func_code’
目前来源:
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
最佳答案 我们其中一个人对
decorators,
descriptors(例如房产)和
abstracts的工作方式感到困惑 – 我希望不是我. 😉
这是一个粗略的工作示例:
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
要考虑几点:
> ValidateProperty更简单,因为属性设置器只接受两个参数:self和new_value
>当一个类用于装饰器,并且装饰器接受参数时,则需要__init__来保存参数,而__call__实际上用于处理defd函数
>调用基类属性setter很难看,但你可以在辅助函数中隐藏它
>您可能希望使用自定义元类来确保运行验证代码(这也可以避免丑陋的基类属性调用)
我建议使用上面的元类来消除直接调用基类的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
这是ABCMeta的子类,并且ABCMeta首先完成所有工作,然后进行一些额外的处理.即:
>浏览创建的类并查找属性
>检查基类以查看它们是否具有匹配的abstractproperty
>检查abstractproperty的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中直接有验证器代码:
class AbstractTestClass(metaclass=AbstractTestClassMeta):
@abstractproperty
def luminance(self):
# abstract property
pass
@luminance.setter
@ValidateProperty(Exception, int)
def luminance(self, value):
# abstract property validator
return 0 <= value <= 15
最后一堂课是一样的:
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