我正在尝试编写Python内置property
的专用子类,该子类在装饰这样的函数时采用输入参数:
@special_property(int)
def my_number(self):
return self._number
我一直在使用https://realpython.com/primer-on-python-decorators/上的示例作为参考,以尝试达到以下目的:
class special_property(property):
def __init__(self, property_type):
super().__init__()
self.type = property_type
def __call__(self, fn):
fn.type = self.type
return fn
此设置允许我检索使用type
的类中为属性指定的显式special_property
,如下所示:
class Object(object):
def __init__(self):
super().__init__()
self._number = 0
@special_property(int)
def my_number(self):
return self._number
def load_from_json(self, json_file):
with open(json_file, 'r') as f:
state = json.load(f)
for name, value in state.items():
if hasattr(self, name):
klass = self.__class__.__dict__[name].type
try:
self.__setattr__(name, klass(value))
except:
ValueError('Error loading from JSON')
之所以这样做,是因为通过装饰应该存储/加载在JSON文件中的属性,可以创建JSON可序列化的类。在此示例中,无需明确确保my_number
的类型为int
,因为json
模块可以自动处理它。但是在我的实际情况中,我将一些更复杂的对象标记为可通过装饰器序列化的JSON,并实现自定义序列化/反序列化方法。为了使它起作用,代码确实需要知道属性的类型。
例如,这允许我创建JSON可序列化类的层次结构。我目前的实现方式允许从JSON存储和加载整个数据结构,而不会丢失任何信息。
现在,我想更进一步,并在尝试设置specialized_property
的值时也可以验证数据格式。因此,我希望能够做到这一点:
@specialized_property(int)
def my_number(self):
return self._number
@my_number.setter
def my_number(self, value):
if value < 0:
raise ValueError('Value of `my_number` should be >= 0')
self._number = value
例如,这将使我能够确保从JSON文件加载的数字列表具有正确的大小。
但是,由于代码使添加property_type
参数有效,因此现在无法使用@my_number.setter
。如果我尝试运行代码,则会得到:
AttributeError: 'function' object has no attribute 'setter'
这对我来说很有意义,因为它重写了__call__
方法并返回了function
对象。但是如何解决这个问题并完成我想要的?
答案 0 :(得分:2)
这是我的实现。它使用Descriptor HOWTO中概述的property
的Python实现。我为此添加了一个包装器,该包装器接受在设置或获取值时将被调用的函数或类型。在包装程序的闭包中,我定义了具有special_property_descriptor
的{{1}}类。这是赋予包装器外部的功能/类型。最后,包装了.type
属性的包装器将返回此属性描述符类。
.type
很显然,您可以在此处修改功能。在我的示例中,描述符将在设置/获取值之前尝试将值转换为所需的类型。如果需要,可以执行def special_property(cls):
class special_property_descriptor(object):
type = cls
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError('unreadable attribute')
r = self.fget(obj)
try:
return self.type(r)
except Exception:
raise TypeError(f'attribute {self.name} must '
f'of type {self.type.__name__}')
def __set__(self, obj, value):
try:
value = self.type(value)
except Exception:
raise TypeError(f'attribute {self.name} must '
f'of type {self.type.__name__}')
if self.fset is None:
raise AttributeError('can\'t set attribute')
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError('can\'t delete attribute')
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
return special_property_descriptor
仅执行类型,而不尝试转换无效值。
答案 1 :(得分:-2)
不要搞乱财产。在您自己的类变量中分别跟踪类型。
有关说明,请参见下面的prop_type类变量。
import json
class Object(object):
prop_type = {}
def __init__(self):
super().__init__()
self._number = 0
@property
def my_number(self):
return self._number
prop_type['my_number'] = int
@my_number.setter
def my_number(self, value):
if self.prop_type['my_number'] != int:
raise ValueError("Not an int")
if value < 0:
raise ValueError('Value of `my_number` should be >= 0')
self._number = value