Lazy data-flow (spreadsheet like) properties with dependencies in Python

前端 未结 3 2038
孤城傲影
孤城傲影 2020-12-29 10:00

My problem is the following: I have some python classes that have properties that are derived from other properties; and those should be cached once they are calculated, and

3条回答
  •  谎友^
    谎友^ (楼主)
    2020-12-29 10:14

    Here, this should do the trick. The descriptor mechanism (through which the language implements "property") is more than enough for what you want.

    If the code bellow does not work in some corner cases, just write me.

    class DependentProperty(object):
        def __init__(self, calculate=None, default=None, depends_on=()):
            # "name" and "dependence_tree" properties are attributes
            # set up by the metaclass of the owner class
            if calculate:
                self.calculate = calculate
            else:
                self.default = default
            self.depends_on = set(depends_on)
    
        def __get__(self, instance, owner):
            if hasattr(self, "default"):
                return self.default
            if not hasattr(instance, "_" + self.name):
                setattr(instance, "_" + self.name,
                    self.calculate(instance, getattr(instance, "_" + self.name + "_last_value")))
            return getattr(instance, "_" + self.name)
    
        def __set__(self, instance, value):
            setattr(instance, "_" + self.name + "_last_value", value)
            setattr(instance, "_" + self.name, self.calculate(instance, value))
            for attr in self.dependence_tree[self.name]:
                delattr(instance, attr)
    
        def __delete__(self, instance):
            try:
                delattr(instance, "_" + self.name)
            except AttributeError:
                pass
    
    
    def assemble_tree(name,  dict_, all_deps = None):
        if all_deps is None:
            all_deps = set()
        for dependance in dict_[name].depends_on:
            all_deps.add(dependance)
            assemble_tree(dependance, dict_, all_deps)
        return all_deps
    
    def invert_tree(tree):
        new_tree = {}
        for key, val in tree.items():
            for dependence in val:
                if dependence not in new_tree:
                    new_tree[dependence] = set()
                new_tree[dependence].add(key)
        return new_tree
    
    class DependenceMeta(type):
        def __new__(cls, name, bases, dict_):
            dependence_tree = {}
            properties = []
            for key, val in dict_.items():
                if not isinstance(val, DependentProperty):
                    continue
                val.name = key
                val.dependence_tree = dependence_tree
                dependence_tree[key] = set()
                properties.append(val)
            inverted_tree = {}
            for property in properties:
                inverted_tree[property.name] = assemble_tree(property.name, dict_)
            dependence_tree.update(invert_tree(inverted_tree))
            return type.__new__(cls, name, bases, dict_)
    
    
    if __name__ == "__main__":
        # Example and visual test:
    
        class Bla:
            __metaclass__ = DependenceMeta
    
            def calc_b(self, x):
                print "Calculating b"
                return x + self.a
    
            def calc_c(self, x):
                print "Calculating c"
                return x + self.b
    
            a = DependentProperty(default=10)    
            b = DependentProperty(depends_on=("a",), calculate=calc_b)
            c = DependentProperty(depends_on=("b",), calculate=calc_c)
    
    
    
    
        bla = Bla()
        bla.b = 5
        bla.c = 10
    
        print bla.a, bla.b, bla.c
        bla.b = 10
        print bla.b
        print bla.c
    

提交回复
热议问题