简体   繁体   中英

Use a dict to access nested instances of classes in Python

I'm trying to define an attribute in a nested class and then access it later using a string or maybe a list of strings. Here's code for what I'm trying to do

class MyNestedClass(object):
    def __init__(self):
        self.att1 = 5.

class MyClass(object):
    def __init__(self):
        self.my_nested_inst = MyNestedClass()

my_inst = MyClass()

I want to change the value of my_inst.my_nested_inst.att1 when all I have is a list like this: my_list = ['my_inst','my_nested_inst','att1'] .

If I use this:

vars(vars(vars()[my_list[0]])[my_list[1]])[my_list[2]]

This works, but the problem is that I need to extend it to an arbitrary depth of nested instances. I can't figure out a good way to make this work with a for loop. Any help is greatly appreciated.

Also, note that converting a string to a variable name in the global namespace has been well addressed, but none of the answers seem to apply here.

EDIT1: I'll try to explain why I'm doing this, and let me know if I do a poor job of explaining. I am using scipy.optimize.fmin, and I have been using only 4 parameters for optimzation. However, I now want to expand my optimization code to handle an arbitrary number of parameters, some of which are nested attributes several layers into the class/instance hierarchy. I want to be able to create a list or dictionary at the top level to tell fmin how to unpack the parameter array for setting the nested attributes.

You can use operator.attrgetter to get nested attributes by specifying an attribute name containing dots (requires Python 2.6+):

After f = attrgetter('date.month') , the call f(b) returns b.date.month .

For convenience, you can create a pair of helper functions:

def get_nested_attr(vars_dict, attrs):
    inst = vars_dict[attrs[0]]
    return operator.attrgetter('.'.join(attrs[1:]))(inst)

def set_nested_attr(vars_dict, attrs, value):
    setattr(get_nested_attr(vars_dict, attrs[0:-1]), attrs[-1], value)

Here's a complete example (tested with Python 2.7.2):

import operator

class MyNestedClass(object):
    def __init__(self):
        self.att1 = 5.

class MyClass(object):
    def __init__(self):
        self.my_nested_inst = MyNestedClass()

def get_nested_attr(vars_dict, attrs):
    inst = vars_dict[attrs[0]]
    return operator.attrgetter('.'.join(attrs[1:]))(inst)

def set_nested_attr(vars_dict, attrs, value):
    setattr(get_nested_attr(vars_dict, attrs[0:-1]), attrs[-1], value)


my_inst = MyClass()
my_list = ['my_inst','my_nested_inst','att1']

assert(my_inst.my_nested_inst.att1 == 5.)
set_nested_attr(vars(), my_list, 10.)
assert(my_inst.my_nested_inst.att1 == 10.)

Assuming the last ) in your vars... was a typo, you have various choices for implementing a function like:

def namespace(names):
    """
        returns vars(vars(..vars(locals())[names[0]])..[names[-2]])[names[-1]]
    """
  • Use reduce (as someone wrote in an answer which he then deleted...)
  • A recursive function
  • A function which explicitly uses a stack and a while-loop

Perhaps the easiest to understand is the recursive implementation:

def namespace(myList):
    if len(myList)==0:
        return locals()
    else:
        oneLevelUp = namespace(myList[:-1])
        return vars(oneLevelUp[myList[-1]])

The reduce implementation equally elegant. Here's how reduce works:

>>> functools.reduce(lambda a,b:[a,b], range(4), 'x')
[[[['x', 0], 1], 2], 3]

The implementation:

def namespace(myList):
    return reduce(lambda ns,item:vars(ns[item]), myList, locals())

Edit: As pointed out in the comments, attrgetter doesn't work the way I used it. You have to use reduce or a loop.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM