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)