Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 53 lines
 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
>>>