如何通过向Python的内置属性类添加参数而不破坏它来扩展它?

2024-04-23 09:00:08 发布

您现在位置: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

此设置允许我检索在使用my special_property的类中为属性指定的显式type,如下所示:

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模块可以自动处理该类型。但在我的实际案例中,有一些更复杂的对象,我将其标记为可使用decorator序列化的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对象。但我该如何绕过这一点,实现我想要的?你知道吗


Tags: nameselfjsonnumberreturn序列化initvalue
2条回答

这是我的实现。它使用了Descriptor HOWTO中概述的property的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)操作,只强制类型,而不尝试转换无效值。你知道吗

不要乱动财物。在自己的类变量中分别跟踪类型。你知道吗

请参见下面的prop_type class变量以获取说明。你知道吗

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

相关问题 更多 >