python - 如何通过向 Python 的内置属性类添加参数而不破坏它来扩展它?
问题描述
我正在尝试编写一个 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 存储和加载整个数据结构而不会丢失任何信息。
现在我想更进一步,在尝试设置 a 的值时也可以验证数据的格式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
对象。但是我该如何解决这个问题并完成我想要的呢?
解决方案
这是我的实现。它使用Descriptor HOWTOproperty
中概述的 Python 实现。我为此添加了一个包装器,它接受在设置或获取值时将调用的函数或类型。在包装器的闭包中,我定义了具有. 这是给外部包装器的功能/类型。最后,这个属性描述符类由设置了属性的包装器返回。special_property_descriptor
.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
显然,您可以在此处修改功能。在我的示例中,描述符将在设置/获取之前尝试将值转换为所需的类型。如果你愿意,你可以isinstance(value, self.type)
只强制类型而不尝试转换无效值。
推荐阅读
- sql - 在 table1 中选择值、Group By、Sum 的存储过程,然后将这些值插入到 table2 中
- ios - 如何将 dateFormatter 值(例如 2019 年 7 月 7 日)从时间戳转换为 2019 年 7 月 7 日?
- python - 在 Python Pygame 中调用对象和传递变量不工作错误说变量未定义
- html - 自举对齐输入字段
- javascript - 如何将变量 js 发布到 Django
- javascript - 从匿名函数返回值时遇到问题
- antlr4 - 我可以让我的 ANTLR4 Lexer 从输入流中丢弃一个字符吗?
- mql4 - StringGetChar 和 CharToStr 不能互换
- javascript - 全局变量在 JQuery Ajax 成功函数中不受影响
- c++ - GNAT CE 2019 链接失败 Om OSX Mojave