1

I would like to update the attributes of a class dynamically, but it seems that the combination of setattr and getattr does not work as I would like to use it.

Here is my main class:

class Container(object):
    def __init__(self):
        pass
container = Container()
attributes = ['a', 'b', 'c', 'd']
values = [[1, 2, 3, 4, 5], [True, False], ['red', 'blue', 'green'], [0, 1, -1, -5, 99]]

Please note that for the purpose of that example I explicitly constructed the list of attributes and their respective values. However, in the real application of this code, I do not know anything in advance. Neither their numbers, name, or values. This calls for the need to do that dynamically.

Here is the rest of the code:

for key, value in zip(attributes, values):
    setattr(container, key, [])
    for val in value:
        setattr(container, key, getattr(container, key).append(val))

this part does not work when I run the code. I could save the getattr part in a tmp variable and then call the append method for the list before calling the setattr, but I would like to condense it if possible.

Anyone could explain me why this does not work? What alternative(s) do I have?

Thanks for your help

1
  • "this part does not work when I run the code" how? Also, what do you expect to happen? Commented Mar 8, 2013 at 11:33

2 Answers 2

3

You are appending to a list in-place. Like all in-place mutation functions, .append() returns None, so your final line:

setattr(container, key, getattr(container, key).append(val))

ultimately evaluates to:

setattr(container, key, None)

Just set a copy of the list on the class:

for key, value in zip(attributes, values):
    setattr(container, key, values[:])

where the [:] creates a copy of values by slicing from 0 to len(values) in a short-hand notation without needing to loop.

If all you want to do is create an object that provides the keys and values as attributes (much like a dict would yet with attribute access instead of item access), you could also use:

class Container(object):
    def __init__(self, names, values):
        self.__dict__.update(zip(names, values))

then run:

Container(attributes, values)

which removes the need to call setattr() in a loop.

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you for the clarification. Would you know why getattr(container, key, value) seems to work the same as setattr(container, key, value) (once the attribute has been previously set once)?
@user1713952: it is getattr(container, key, default); if the attribute key has not been set on container, then default is returned instead. Since you already set, key, getattr(container, key) would suffice.
@JonClements: Why would defaultdict() help here? To replace Container you mean? The whole append loop is a red herring though, there are no lists being combined, only keys paired up with existing lists.
@Martijn I'm not 100% awake, but it does look like Container could just be that instead...
Yes, I though of using a dict instead. But I have a couple of specific methods I need to run on those objects and it is more convinient to have them defined within a class. Plus, the syntax of attribute access is more compact with classes isn't it?
|
1

It might help you understand what's going on if you can watch the progress of each step:

class Container(object):
    def __init__(self):
        pass
    def __str__(self):
        return '%s(%s)' % (self.__class__.__name__,
            ', '.join(['%s = %s' % (attr, getattr(self, attr))
                for attr in self.__dict__]))

Now you can print container. If you do this in the nested loop you'll see that after the first attempt to setattr you've clobbered the original list stored in container.a (as Martijn noted):

for key, value in zip(attributes, values):
    setattr(container, key, [])
    for val in value:
        print 'before:', container
        setattr(container, key, getattr(container, key).append(val))
        print 'after:', container

before: Container(a = [])
after: Container(a = None)
before: Container(a = None)
Traceback (most recent call last): ...

The "best" way to do this obviously depends on the real problem—given the shrunken problem Martijn's version is quite appropriate, but perhaps you have to append one item, or extend with multiple items, at some point well after creating some initial container instance.

Comments

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.