Literal answer to "how to find where my data is stored" :
class Blank:
pass
obj = Blank()
a = obj
b = a
c = [1, 3, obj]
import gc
def find_all_transitive_references_to(something):
something_id = id(something)
for referrer in gc.get_referrers(something):
if isinstance(referrer, dict):
# find the corresponding key(s)
keys = set(key for key, value in referrer.items() if id(value) == something_id)
this_function_local_scope = id(locals())
if id(referrer) == this_function_local_scope:
pass # redundant
elif '__name__' in referrer.keys(): # assume it is a module
print(f"module {referrer['__name__']}:", keys)
else:
print("dict keys:", keys) # TODO
else:
print("else:", type(referrer), referrer) # TODO
find_all_transitive_references_to(obj)
else: <class 'list'> [1, 3, <__main__.Blank object at 0x7fca0fb9be50>]
module __main__: {'b', 'obj', 'a'}
That's a good start. Most of the work is done by gc.get_refferers which does half the work. But as it is intended to be used by a garbage collector, it does not produce a clean and nice output.
But going farther gets immensely difficult, because you have to recursively explore referrers up the refs chains. And barring cycles and joints in the ref graph, you just can't easily guess a name.
Here is what I got so far :
class Blank:
pass
obj = Blank()
a = obj
b = a
c = [1, 3, obj]
d = {'1': obj, '2': 'foo'}
e = {obj: '1', '2': 'foo'}
f = {'1': obj, '2': obj}
g = {obj}
def function_with_default_mutable_argument(o=obj):
pass
import dataclasses
import enum
import gc
class ReferenceSource(enum.Enum):
MODULE_VAR = enum.auto
LIST_ELEM = enum.auto
TUPLE_ELEM = enum.auto
DICT_KEY = enum.auto
DICT_VAL = enum.auto
UNKNOWN = enum.auto
@dataclasses.dataclass
class Reference:
python_name: str
display_name: str
source: ReferenceSource
# def get
def find_all_transitive_references_to(something, excludes=None):
if excludes is None:
excludes = []
something_id = id(something)
results = []
for referrer in gc.get_referrers(something):
if referrer in excludes:
continue
if isinstance(referrer, dict):
# find the corresponding key(s)
keys = set(key for key, value in referrer.items() if id(value) == something_id)
this_function_local_scope = id(locals())
if id(referrer) == this_function_local_scope:
pass # redundant
elif '__name__' in referrer.keys(): # assume it is a module # FIXME: functions scopes are alike
for key in keys:
if key == 'something':
continue
results.append(Reference(f"{referrer['__name__']!s}.{key!s}", f"module {referrer['__name__']} variable {key!r}", ReferenceSource.MODULE_VAR))
else:
for key, value in referrer.items():
if id(key) == something_id:
results.append(Reference(f"TODO:NAME key", f"TODO:NAME dict key", ReferenceSource.DICT_KEY))
elif id(value) == something_id:
results.append(Reference(f"TODO:NAME [{key!r}]", f"TODO:NAME dict [{key!r}]", ReferenceSource.DICT_VAL))
elif isinstance(referrer, list):
for index in (index for index, value in enumerate(referrer) if id(value) == something_id):
results.append(Reference(f"TODO:NAME [{index!s}]", f"TODO:NAME list [{index!s}]", ReferenceSource.LIST_ELEM))
elif isinstance(referrer, tuple):
# may be a function parameter default value
for index in (index for index, value in enumerate(referrer) if id(value) == something_id):
results.append(Reference(f"TODO:NAME [{index!s}]", f"TODO:NAME tuple [{index!s}]", ReferenceSource.TUPLE_ELEM))
elif isinstance(referrer, set):
referrer_referrers = find_all_transitive_references_to(referrer, excludes=[id(locals())])
print("ref_ref count=", len(referrer_referrers), str(referrer_referrers))
module_referrer_referrer = next((ref_ref for ref_ref in referrer_referrers if ref_ref.source is ReferenceSource.MODULE_VAR), None)
parent_name = module_referrer_referrer.python_name if module_referrer_referrer is not None else "?"
count = sum(1 for value in referrer if id(value) == something_id)
results.append(Reference(f"{parent_name!s} ({count!s} times)", f"TODO:NAME ({count!s} times)", ReferenceSource.TUPLE_ELEM))
else:
results.append(Reference(f"?", f"TODO:unknown", ReferenceSource.UNKNOWN))
return results
print("\n".join(map(str, find_all_transitive_references_to(obj))))
ref_ref count= 3 [Reference(python_name='TODO:NAME [4]', display_name='TODO:NAME list [4]', source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>), Reference(python_name="TODO:NAME ['referrer']", display_name="TODO:NAME dict ['referrer']", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>), Reference(python_name='__main__.g', display_name="module __main__ variable 'g'", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)]
Reference(python_name='TODO:NAME [2]', display_name='TODO:NAME list [2]', source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name="TODO:NAME ['1']", display_name="TODO:NAME dict ['1']", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name='TODO:NAME key', display_name='TODO:NAME dict key', source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name="TODO:NAME ['1']", display_name="TODO:NAME dict ['1']", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name="TODO:NAME ['2']", display_name="TODO:NAME dict ['2']", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name='TODO:NAME [4] (1 times)', display_name='TODO:NAME (1 times)', source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name='TODO:NAME [0]', display_name='TODO:NAME tuple [0]', source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name='__main__.a', display_name="module __main__ variable 'a'", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name='__main__.obj', display_name="module __main__ variable 'obj'", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
Reference(python_name='__main__.b', display_name="module __main__ variable 'b'", source=<ReferenceSource.MODULE_VAR: <class 'enum.auto'>>)
But it gets very hairy because gc is not a very good tool for the problem. I suggest you listen to the suggestion by @Marat and explain why you would need it. There must a be a MUCH MUCH simpler way to achieve what you actually want.