我有一个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
答案 0 :(得分:1)
我们中的一个人对decorators,descriptors(例如属性)和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
更简单,因为属性设置器只有两个参数:ValidateProperty
和self
当使用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
先完成所有工作,然后进行一些额外的处理。即:
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