Python - 通过对象的属性创建对象的引用树

Python - creating a reference tree of objects by their attributes

我有一个对象列表。这些对象都有嵌套的属性,这些属性是按照hpaulj在这个post中的响应的一个方法生成的:Object-like attribute access for nested dictionary.

我希望能够在这些对象中找到属性和属性值,并操作它们保存的数据。然而,在实际场景中,可能有超过一百万个对象,并且属性可能嵌套很深,这使得在需要进行大量操作时通过平面列表进行搜索是一项代价高昂的工作。

例如,假设对象列表如下: list_of_objects = [object1, object2, object3, object4]

这些对象是根据以下 State class 创建的:

class State:
    def __init__(self, state_dictionary):
        self._state_dictionary = state_dictionary
        for key, value in self._state_dictionary.items():
            if isinstance(value, dict):
                value = State(value)
            setattr(self, key, value)

一个例子state_dictionary如下,在object3的情况下:

state_dictionary = {
    "country":"Ireland",
    "risk_factor":{
        "smoking":"Current"
    }
}

重要的是,嵌套属性也是状态对象。

我想影响所有拥有一个属性、一组嵌套属性或拥有一个具有指定值的属性的对象。

我的想法是创建一个 'Controller',它将原始列表作为单独的列表存储在控制器 class 的对象实例中。每个原始属性和值将指向包含这些属性或值的对象列表,基本设计如下:

class Controller:

    def __init__(self, list_of_objects):
        self.list_of_objects = list_of_objects  # Our list of objects from above
        self.create_hierarchy_of_objects()

    def create_hierarchy_of_objects(self):
        for o in self.list_of_objects:
            #  Does something here

create_hierarchy_of_objects方法有了运行后,我就可以进行如下操作了:

问题是 create_hierarchy_of_objects 将如何工作? 我澄清几点

如果您必须处理超过一百万个对象,生成额外的层次结构可能不是最佳解决方案。这将需要许多额外的对象并浪费大量时间来创建层次结构。每当 list_of_objects 发生变化时,层次结构也需要更新。

因此,我建议使用 迭代器 和类似 XPath 的原则来采用更通用和动态的方法。我们称它为 OPathOPath class 是一个轻量级对象,它只是将属性连接到一种 属性路径 。它还保留对 条目对象 原始列表的引用。最后,它仅基于属性,因此适用于任何类型的对象。

当我们开始遍历 OPath 对象时(例如,将对象放入 list(),使用 for 循环,...) . OPath returns 一个迭代器,它根据原始提供的列表中的实际内容,根据属性路径递归查找匹配的对象。它 yield 一个接一个地匹配对象,以避免创建包含完全填充的匹配对象的不必要列表。

class OPath:
    def __init__(self, objects, path = []):
        self.__objects = objects
        self.__path = path

    def __getattr__(self, __name):
        return OPath(self.__objects, self.__path + [__name])

    def __iter__(self):
        yield from (__object for __object in self.__objects if self.__matches(__object, self.__path))

    @staticmethod
    def __matches(__object, path):
        if path:
            if hasattr(__object, path[0]):
                return OPath.__matches(getattr(__object, path[0]), path[1:])
            if __object == path[0] and len(path) <= 1:
                return True
            return False
        return True

if __name__ == '__main__':
    class State:
        def __init__(self, state_dictionary):
            self._state_dictionary = state_dictionary
            for key, value in self._state_dictionary.items():
                if isinstance(value, dict):
                    value = State(value)
                setattr(self, key, value)

    o1 = State({ "country":"Kenya", "disease": "breast cancer" })
    o2 = State({ "country":"Kenya", "disease": "diabetes" })
    o3 = State({ "country":"Ireland", "risk_factor": { "smoking":"Current" } })
    o4 = State({ "country":"Kenya", "risk_factor": { "smoking":"Previous" } })

    # test cases with absolute paths
    print("Select absolute")
    path = OPath([o1, o2, o3, o4])
    print(list(path) == [o1, o2, o3, o4])
    print(list(path.country) == [o1, o2, o3, o4])
    print(list(path.country.Kenya) == [o1, o2, o4])
    print(list(path.disease) == [o1, o2])
    print(list(path.disease.diabetes) == [o2])
    print(list(path.risk_factor.smoking) == [o3, o4])
    print(list(path.risk_factor.smoking.Current) == [o3])
    print(list(path.doesnotexist.smoking.Current) == [])
    print(list(path.risk_factor.doesnotexist.Current) == [])
    print(list(path.risk_factor.smoking.invalidvalue) == [])
    print(list(path.risk_factor.doesnotexist.Current.invalidpath) == [])

    # test cases with relative paths
    country = OPath([o1, o2, o3, o4], ["country"])
    print("Select relative from country:")
    print(list(country) == [o1, o2, o3, o4])
    print(list(country.Kenya) == [o1, o2, o4])

    print("Select all with country=Kenya")
    kenya = OPath([o1, o2, o3, o4], ['country', 'Kenya'])
    print(list(kenya) == [o1, o2, o4])

所有测试用例的输出预计为 True