0

I'm stumped on how to construct a function that works on lists within lists from inside out (I guess that's how you could poorly describe it).

I'm trying to dynamically turn a list like

res = SomeDjangoQuerySet
x = ['neighborhood', ['city', ['metro', 'metro']]]

into:

getattr(getattr(getattr(getattr(res, 'neighborhood'), 'city'), 'metro'), 'metro')

AKA:

getattr(getattr(getattr(getattr(res, x[0]), x[1][0]), x[1][1][0]), x[1][1][1])

Basically, the first value will always be a string, the second value will either be a string or a list. Each list will follow this pattern (string, string OR list). The depth of lists within lists is indeterminate. The innermost first value of the getattr() will be an outside variable ('res' in this case). Any advice?

3 Answers 3

3

This sounds like recursion and iteration might be useful. Does this do what you want?

def flatten(data):
    res = []
    if hasattr(data, '__iter__'):
        for el in data:
            res.extend(flatten(el))
    else:
        res.append(data)
    return res


reduce(getattr, flatten(x), res)
Sign up to request clarification or add additional context in comments.

2 Comments

Recursion is definitely key here. This does work but I found it to be a bit hard to follow, I ended up coming up with another method (but thanks for your reply all the same!)
After taking a second look, this is definitely better. There's some cool stuff I learned here (hasattr() and reduce()). Thanks.
1

I ended up putting in some time and learning about recursion and found this to be the simplest solution (although, credit to David Zwicker who also provided a working solution).

def recursion(a, b):
    if type(b) is list:
        return recursion(getattr(a, b[0]), b[1])
    else:
        return getattr(a, b)

recursion(res, x)

1 Comment

Your version only works, if the lists have exactly two elements! If you know that this will always be the case, it is of course legitimate, but it is important to note! Also, your method does not work completely, if there is a list nested at the first position of another list.
1
def nestattr(x, y):
    if isinstance(y, str):
        return getattr(x, y)
    elif isinstance(y, list):
        return nestattr(getattr(x, y[0]), y[1])

nestattr(res, x)

So you start off with the first string in the list, and you have the getattr of (1) the query with (2) that string. Then you recurse using the rest of that list, and if it's a string, you just do the getattr on (1) the result of the previous getattr with (2) this string. Otherwise, if it's still a list, you repeat. I think this is what you're looking for? Correct me if I'm wrong.

3 Comments

I think this version breaks, if you reach the end of a list, where then an empty list would be fed into the function, throwing an exception for y[0].
This didn't work for me, I get a 'TypeError: getattr(): attribute name must be string' when it hits the y[0] at the end.
Whoops, I guess there was no need for that colon in y[1:]. Didn't need to take a slice. It seems you've found your solution, but I'll make the correction anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.