You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
3.2 KiB
120 lines
3.2 KiB
#!/usr/bin/env python
|
|
|
|
import copy
|
|
|
|
|
|
class Ref(object):
|
|
def __init__(self, obj):
|
|
self._id = self.id(obj)
|
|
|
|
def get_id(self):
|
|
return self._id
|
|
|
|
@staticmethod
|
|
def id(obj):
|
|
return str(id(obj))
|
|
|
|
|
|
def flatten(obj):
|
|
"""Flatten an object hierarchy.
|
|
|
|
Walks an object hierarchy while keeping a dictionary of
|
|
non-Ref objects. Replaces all non-Ref objects with Ref
|
|
objects.
|
|
|
|
Parameters
|
|
----------
|
|
obj : object (new-style)
|
|
Root of the object hierarchy to flatten.
|
|
|
|
Returns
|
|
-------
|
|
(flat_obj, refs) : tuple
|
|
flat_obj is a deep-copy of obj with all non-Ref objects
|
|
replaced with Ref objects. refs is a dictionary of non-Ref
|
|
objects.
|
|
"""
|
|
refs = {}
|
|
cpy = copy.deepcopy(obj)
|
|
todo = [cpy]
|
|
|
|
while len(todo) > 0:
|
|
current = todo.pop(0)
|
|
|
|
if isinstance(current, object) and hasattr(current, "__dict__"):
|
|
todo.append(current.__dict__)
|
|
|
|
elif isinstance(current, dict):
|
|
for k, v in list(current.items()):
|
|
if isinstance(v, object) and hasattr(v, "__dict__") and not isinstance(v, Ref):
|
|
todo.append(v)
|
|
refs.setdefault(Ref.id(v), v)
|
|
current[k] = Ref(v)
|
|
|
|
elif isinstance(current, list):
|
|
for i, v in enumerate(current):
|
|
if isinstance(v, object) and hasattr(v, "__dict__") and not isinstance(v, Ref):
|
|
todo.append(v)
|
|
refs.setdefault(Ref.id(v), v)
|
|
current[i] = Ref(v)
|
|
|
|
elif (
|
|
isinstance(current, set)
|
|
or isinstance(current, frozenset)
|
|
or isinstance(current, tuple)
|
|
):
|
|
raise ValueError("unsupported: %s", current)
|
|
|
|
return (cpy, refs)
|
|
|
|
|
|
def expand(obj, refs):
|
|
"""Expand a flattened object hierarchy.
|
|
|
|
Walks a flattened object hierarchy and replaces Ref objects
|
|
with the actual objects referred to from refs.
|
|
|
|
Parameters
|
|
----------
|
|
obj : object (new-style)
|
|
Root of the object hierarchy flattened by flatten(). All
|
|
objects are modified in place.
|
|
refs : dict
|
|
Dictionary of object references used by Ref objects in obj.
|
|
|
|
Returns
|
|
-------
|
|
obj : object (new-style)
|
|
For convenience, the expanded obj is returned.
|
|
|
|
"""
|
|
todo = [obj]
|
|
|
|
while len(todo) > 0:
|
|
current = todo.pop(0)
|
|
|
|
if isinstance(current, object) and hasattr(current, "__dict__"):
|
|
assert not isinstance(current, Ref)
|
|
todo.append(current.__dict__)
|
|
|
|
elif isinstance(current, dict):
|
|
for k, v in list(current.items()):
|
|
if isinstance(v, Ref):
|
|
current[k] = refs[v.get_id()]
|
|
todo.append(current[k])
|
|
|
|
elif isinstance(current, list):
|
|
for i, v in enumerate(current):
|
|
if isinstance(v, Ref):
|
|
current[i] = refs[v.get_id()]
|
|
todo.append(current[i])
|
|
|
|
elif (
|
|
isinstance(current, set)
|
|
or isinstance(current, frozenset)
|
|
or isinstance(current, tuple)
|
|
):
|
|
raise ValueError("unsupported: %s", current)
|
|
|
|
return obj
|