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]
- object1 具有以下属性:
self.country = "Kenya", self.disease = "breast cancer"
- object2 具有以下属性:
self.country = "Kenya", self.disease = "diabetes"
- object3 具有以下属性:
self.country = 'Ireland', self.risk_factor.smoking = "Current"
- object4 具有以下属性:
self.country = 'Kenya', self.risk_factor.smoking = "Previous"
这些对象是根据以下 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
方法有了运行后,我就可以进行如下操作了:
Controller.country.Kenya
将包含 self.country 为“肯尼亚”的所有对象的列表,即 object1、object2、object4
Controller.disease
将包含具有属性 self.disease 的所有对象的列表,即 object1 和 object2
Controller.risk_factor.smoking.Current
将包含具有该组属性的对象列表,即 object3
问题是 create_hierarchy_of_objects
将如何工作?
我澄清几点
- 嵌套任意长,值可以相同,例如
self.risk_factor.attribute1.attribute2 = "foo"
self.risk_factor.attribtue3.attribute4 = "foo"
也是。
- 可能有更简单的方法,欢迎任何建议。
如果您必须处理超过一百万个对象,生成额外的层次结构可能不是最佳解决方案。这将需要许多额外的对象并浪费大量时间来创建层次结构。每当 list_of_objects
发生变化时,层次结构也需要更新。
因此,我建议使用 迭代器 和类似 XPath 的原则来采用更通用和动态的方法。我们称它为 OPath
。 OPath
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
。
我有一个对象列表。这些对象都有嵌套的属性,这些属性是按照hpaulj在这个post中的响应的一个方法生成的:Object-like attribute access for nested dictionary.
我希望能够在这些对象中找到属性和属性值,并操作它们保存的数据。然而,在实际场景中,可能有超过一百万个对象,并且属性可能嵌套很深,这使得在需要进行大量操作时通过平面列表进行搜索是一项代价高昂的工作。
例如,假设对象列表如下:
list_of_objects = [object1, object2, object3, object4]
- object1 具有以下属性:
self.country = "Kenya", self.disease = "breast cancer"
- object2 具有以下属性:
self.country = "Kenya", self.disease = "diabetes"
- object3 具有以下属性:
self.country = 'Ireland', self.risk_factor.smoking = "Current"
- object4 具有以下属性:
self.country = 'Kenya', self.risk_factor.smoking = "Previous"
这些对象是根据以下 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
方法有了运行后,我就可以进行如下操作了:
Controller.country.Kenya
将包含 self.country 为“肯尼亚”的所有对象的列表,即 object1、object2、object4Controller.disease
将包含具有属性 self.disease 的所有对象的列表,即 object1 和 object2Controller.risk_factor.smoking.Current
将包含具有该组属性的对象列表,即 object3
问题是 create_hierarchy_of_objects
将如何工作?
我澄清几点
- 嵌套任意长,值可以相同,例如
self.risk_factor.attribute1.attribute2 = "foo"
self.risk_factor.attribtue3.attribute4 = "foo"
也是。
- 可能有更简单的方法,欢迎任何建议。
如果您必须处理超过一百万个对象,生成额外的层次结构可能不是最佳解决方案。这将需要许多额外的对象并浪费大量时间来创建层次结构。每当 list_of_objects
发生变化时,层次结构也需要更新。
因此,我建议使用 迭代器 和类似 XPath 的原则来采用更通用和动态的方法。我们称它为 OPath
。 OPath
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
。