Emilio M Bumachar
Emilio M Bumachar

Reputation: 2613

Access nested list element, depth known in runtime

I want to write to an element in a nested list named foo, but the nesting depth and indexes is only known at runtime, in a (non-nested!) list variable named indexes.

Examples:

If indexes is [4], I want foo[4].

If indexes is [4,7], I want foo[4][7].

If indexes is [4,7,3], I want foo[4][7][3].

What I could think of is to put together the command string ("foo[4][7][3]" in the last example), then call eval. That would be eval("foo["+']['.join([str(n) for n in indexes])+']').

That works, and is short enough, but I was hoping for a simpler, more pythonic way.

Does anyone know any alternatives?

Is there a way to not treat the empty list separately? As in:

If indexes is [], I want the whole foo.

This needs an if in the eval solution.

EDIT: I need to write to, not read from, the element. I don't think either of the two existing answers, nor the answers to the indicated duplicate, can be used for writing. Apologies for misleading.

Upvotes: 2

Views: 298

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1122042

You can use the reduce() function:

from functools import reduce  # Python 3 forward compatibility
import operator

def access(lst, indexes):
    return reduce(operator.getitem, indexes, lst)

You could use list.__getitem__ instead of operator.getitem, but then you limit the application to list objects only (it wouldn't work for tuples or dictionaries, the latter with keys rather than integer indices).

This repeatedly applies the indices, in order, to the previous result (starting the original list). This meets all your criteria, including the empty-list case:

>>> foo = ['a', 'b', 'c', 'd', ['foo', 'bar', 'baz', 'spam', 'eggs', 'ham', 'monty', ['alpha', 'beta', 'delta', 'gamma']]]
>>> access(foo, [])
['a', 'b', 'c', 'd', ['foo', 'bar', 'baz', 'spam', 'eggs', 'ham', 'monty', ['alpha', 'beta', 'delta', 'gamma']]]
>>> access(foo, [4])
['foo', 'bar', 'baz', 'spam', 'eggs', 'ham', 'monty', ['alpha', 'beta', 'delta', 'gamma']]
>>> access(foo, [4, 7])
['alpha', 'beta', 'delta', 'gamma']
>>> access(foo, [4, 7, 3])
'gamma'

If you needed to assign to the last element, keep the last index aside to then assign to that:

if indexes:
    target = access(foo, indexes[:-1])
    target[indexes[-1]] = new_value
else:
    foo = new_value

Upvotes: 4

Uriel
Uriel

Reputation: 16184

You can set item to foo, then proceeds with the indexes list to access deeper nested elements:

def access (foo, indexes):
    item = foo
    for index in indexes:
        item = item[index]
    return item

Upvotes: 2

Related Questions