类装饰器自动更新磁盘上的属性字典?

时间:2014-11-05 20:18:05

标签: python class decorator

我正在开发一个项目,我有一些自定义类,可以与用户系统上的各种数据集进行交互。这些类仅具有properties作为面向用户的属性。其中一些属性是资源密集型的,所以我只想运行生成代码一次,并将返回的值存储在磁盘上(缓存它,即),以便在后续运行中更快地检索。就目前而言,这就是我实现这一目标的方式:

def stored_property(func):
    """This ``decorator`` adds on-disk functionality to the `property`
    decorator. This decorator is also a Method Decorator.

    Each key property of a class is stored in a settings JSON file with
    a dictionary of property names and values (e.g. :class:`MyClass`
    stores its properties in `my_class.json`). 
    """
    @property
    @functools.wraps(func)
    def func_wrapper(self):
        print('running decorator...')
        try:
            var = self.properties[func.__name__]
            if var:
                # property already written to disk
                return var
            else:
                # property written to disk as `null`
                return func(self)
        except AttributeError:
            # `self.properties` does not yet exist
            return func(self)
        except KeyError:
            # `self.properties` exists, but property is not a key
            return func(self)
    return func_wrapper

class MyClass(object):
    def __init__(self, wf):
        self.wf = wf
        self.properties = self._properties()

    def _properties(self):
        # get name of class in underscore format
        class_name = convert(self.__class__.__name__)
        # this is a library used (in Alfred workflows) for interacted with data stored on disk
        properties = self.wf.stored_data(class_name)
        # if no file on disk, or one of the properties has a null value
        if properties is None or None in properties.values():
            # get names of all properties of this class
            propnames = [k for (k, v) in self.__class__.__dict__.items()
                         if isinstance(v, property)]
            properties = dict()
            for prop in propnames:
                # generate dictionary of property names and values
                properties[prop] = getattr(self, prop)
            # use the external library to save that dictionary to disk in JSON format
            self.wf.store_data(class_name, properties,
                               serializer='json')
        # return either the data read from file, or data generated in situ
        return properties

    #this decorator ensures that this generating code is only run if necessary
    @stored_property
    def only_property(self):
        # some code to get data
        return 'this is my property'

这段代码正如我需要的那样工作,但它仍然迫使我手动将_properties(self)方法添加到我需要此功能的每个类中(目前,我有3个)。我想要的是一种方法来插入"我喜欢这个功能到任何课程。我认为一个类装饰者可以完成这项工作,但尽可能地尝试,我无法弄清楚如何纠缠它。

为了清楚起见(如果装饰器不是获得我想要的最佳方式),我将尝试解释我所追求的整体功能。我想写一个包含一些属性的类。这些属性的值是通过不同程度的复杂代码生成的(在一个实例中,我搜索某个应用程序的pref文件,然后搜索3个不同的首选项(其中任何一个可能存在,也可能不存在)并确定这些偏好的最佳单一结果)。我想要物业的主体'代码仅包含用于查找数据的算法。但是,每次访问该属性时,我都不想运行该算法代码。一旦我生成了一次值,我想将它写入磁盘,然后在所有后续调用中读取它。但是,我不希望每个值写入自己的文件;我想要将一个类的所有属性的所有值的字典写入一个文件(因此,在上面的示例中,my_class.json将包含一个带有一个键,值对的JSON字典)。直接访问属性时,应首先检查它是否已存在于磁盘上的字典中。如果是,只需读取并返回该值。如果它存在但是具有空值,则尝试运行生成代码(即实际在属性方法中编写的代码)并查看是否可以立即找到它(如果没有,该方法将返回None并且这将再次被写入文件)。如果字典存在并且该属性不是密钥(我当前的代码并不能真正实现这一点,但更安全而不是遗憾),运行生成代码并添加密钥,值对。如果字典不存在(即在类的第一个实例化时),则运行所有属性的所有代码并创建JSON文件。理想情况下,代码将能够更新JSON文件中的一个属性,而无需重新运行所有生成代码(即再次运行_properties())。

我知道这有点奇怪,但我需要速度,人类可读的内容和优雅的代码。我真的不想妥协我的目标。希望,描述我想要的东西足够清楚。如果没有,请在评论中告诉我什么是没有意义的,我会尽力澄清。但我确实认为类装饰器可能会让我在那里(主要是通过将_properties()方法插入任何类,在实例化中运行它,并将其值映射到类的properties属性)。

1 个答案:

答案 0 :(得分:1)

也许我错过了一些东西,但似乎你的_properties方法并不特定于给定类的属性。我将它放在一个基类中,并使每个类都带有@stored_property方法的子类。然后,您不需要复制_properties方法。

class PropertyBase(object):
    def __init__(self, wf):
        self.wf = wf
        self.properties = self._properties()

    def _properties(self):
        # As before...

class MyClass(PropertyBase):
    @stored_property
    def expensive_to_calculate(self):
        # Calculate it here

如果由于某种原因你不能直接子类PropertyBase(也许你已经需要一个不同的基类),你可以使用mixin。如果做不到这一点,请_properties接受一个实例/类和一个工作流对象,并在__init__中为每个类显式调用它。