After wondering what the easiest what to access one object from another was, the following functions were written to automatically discover the shortest path possible. The find
function takes the item to find and the object to find it from and tries finding out the best access path. The optional arguments control the search depth, memory usage, et cetera. The nth
function is a helper function for accessing data containers that cannot be indexed into. As a final note, line thirteen (while candidates:
) will probably never evaluate to false.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | import itertools
import collections
def nth(iterable, n, default=None):
"Returns the nth item or a default value"
return next(itertools.islice(iterable, n, None), default)
def find(item, source, depth=1, queue_limit=1000000, found_limit=1000000):
"Tries to find a pathway to access item from source."
assert depth > 0, 'Cannot find item in source!'
candidates = collections.deque([(source, 'source', 1)])
locations = {id(source)}
while candidates:
if len(candidates) > queue_limit or len(locations) > found_limit:
break
source, path, level = candidates.pop()
# Search container.
try:
iterator = iter(source)
except TypeError:
pass
else:
for key, value in enumerate(iterator):
if isinstance(source, dict):
key, value = value, source[value]
addr = id(value)
if addr not in locations:
try:
assert source[key] is value
except (AssertionError, KeyError, TypeError):
attr_path = 'nth({}, {})'.format(path, key)
else:
attr_path = '{}[{!r}]'.format(path, key)
if value is item:
return attr_path
if level < depth:
candidates.appendleft((value, attr_path, level + 1))
locations.add(addr)
# Search attributes.
for name in dir(source):
try:
attr = getattr(source, name)
except AttributeError:
pass
else:
addr = id(attr)
if addr not in locations:
attr_path = '{}.{}'.format(path, name)
if attr is item:
return attr_path
if level < depth:
candidates.appendleft((attr, attr_path, level + 1))
locations.add(addr)
|
This is a short example of how to use the find
function:
>>> class A:
def B(self):
pass
>>> source = [1, {'X': frozenset((A(), 1, 3, 4)), 'Y': None}, (2, 3), 4]
>>> find(A.B, source, 5)
"nth(source[1]['X'], 3).B.__func__"
>>> eval(_) is A.B
True
>>>