如何在不破坏Python内置属性类的情况下为其添加参数来扩展它?

时间:2019-05-20 21:12:38

标签: python python-decorators

我正在尝试编写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对象。但是如何解决这个问题并完成我想要的?

2 个答案:

答案 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