3

I am trying to add class attributes dynamically, but not at the instance level. E.g. what I can do manually as:

class Foo(object):
    a = 1
    b = 2
    c = 3

I'd like to be able to do with:

class Foo(object):
    dct = {'a' : 1, 'b' : 2, 'c' : 3}
    for key, val in dct.items():
        <update the Foo namespace here>

I'd like to be able to do this without a call to the class from outside the class (so it's portable), or without additional classes/decorators. Is this possible?

gnr
  • 2,324
  • 1
  • 22
  • 24

3 Answers3

5

Judging from your example code, you want to do this at the same time you create the class. In this case, assuming you're using CPython, you can use locals().

class Foo(object):
    locals().update(a=1, b=2, c=3)

This works because while a class is being defined, locals() refers to the class namespace. It's implementation-specific behavior and may not work in later versions of Python or alternative implementations.

A less dirty-hacky version that uses a class factory is shown below. The basic idea is that your dictionary is converted to a class by way of the type() constructor, and this is then used as the base class for your new class. For convenience of defining attributes with a minimum of syntax, I have used the ** convention to accept the attributes.

def dicty(*bases, **attrs):
    if not bases:
        bases = (object,)
    return type("<from dict>", bases, attrs)

class Foo(dicty(a=1, b=2, c=3)):
    pass

# if you already have the dict, use unpacking

dct = dict(a=1, b=2, c=3)

class Foo(dicty(**dct)):
    pass

This is really just syntactic sugar for calling type() yourself. This works fine, for instance:

 class Foo(type("<none>", (object,), dict(a=1, b=2, c=3))):
     pass
kindall
  • 178,883
  • 35
  • 278
  • 309
  • While this works on current CPython, it is [not guaranteed to work on other Python implementations or future versions of CPython](http://docs.python.org/library/functions.html#locals). You should never modify the dicitonary returned by `locals()`. (Modifying `globals()` is fine, though. At least it is guaranteed to work.) – Sven Marnach Dec 13 '11 at 15:28
  • +1: this is great! So much better than the messy metaclass solution I was proposing. I'd give +10 for the reasoning ("refers to the class namespace") behind it if I could! TIL tons of stuff. Thank you internet! – Daren Thomas Dec 13 '11 at 15:30
  • @DarenThomas: I wouldn't call something explicitly forbidden by the documentation a "great" solution. I would rather call it a dirty hack that happens to work by mere chance. – Sven Marnach Dec 13 '11 at 16:37
  • 1
    Well, the documentation says this dictionary "should not" be modified, which is hardly "forbidden." (I'm pretty sure it says this because much of the time, it doesn't do what you expect.) It doesn't work by "chance" either -- it's an implementation detail. Dirty hack? Yes, I'll own that. – kindall Dec 13 '11 at 17:12
  • Added a second method that's less dirty and hacky. :-) – kindall Dec 13 '11 at 17:40
  • @kindall: My last comment was mainly meant to damp Daren's enthusiasm with this solution a bit. And I think if I summon undefined behaviour without thinking about it and the result happens to be what I expected, this is to some extent "by chance". :) – Sven Marnach Dec 13 '11 at 18:26
2

The accepted answer is a nice approach. However, one downside is you end up with an additional parent object in the MRO inheritance chain that isn't really necessary and might even be confusing:

>>> Foo.__mro__
(<class '__main__.Foo'>, <class '__main__.<from dict>'>, <class 'object'>)

Another approach would be to use a decorator. Like so:

def dicty(**attrs):
    def decorator(cls):
        vars(cls).update(**attrs)
        return cls
    return decorator

@dicty(**some_class_attr_namespace)
class Foo():
    pass

In this way, you avoid an additional object in the inheritance chain. The @decorator syntax is just a pretty way of saying:

Foo = dicty(a=1, b=2, c=3)(Foo)
Rick
  • 43,029
  • 15
  • 76
  • 119
2

Do you mean something like this:

def update(obj, dct):
    for key, val in dct.items():
        obj.setattr(key, val)

Then just go

update(Foo, {'a': 1, 'b': 2, 'c': 3})

This works, because a class is just an object too ;)

If you want to move everything into the class, then try this:

class Foo(object):
    __metaclass__ = lambda t, p, a: return type(t, p, a['dct'])
    dct = {'a': 1, 'b': 2, 'c': 3}

This will create a new class, with the members in dct, but all other attributes will not be present - so, you want to alter the last argument to type to include the stuff you want. I found out how to do this here: What is a metaclass in Python?

Community
  • 1
  • 1
Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
  • It's a matter of taste, but I'd prefer to use a class method instead of a function. – jcollado Dec 13 '11 at 14:30
  • Both of these (using setattr through an update function or classmethod) are fine, but they require a call outside of the class - for portability i'd prefer to do it all within the class's startup, if it's possible – gnr Dec 13 '11 at 14:55
  • oh, ok, then I think I misunderstood you. Your solution above works just fine: The code will be run when the class definition is executed. This is the same mechanism that sets up class variables and methods. – Daren Thomas Dec 13 '11 at 15:06
  • but i can't figure out the mechanism to set the attributes. In other words, I can easily manually set a = 1, but with just a dictionary {'a' : 1} this is becomes more difficult, since the setattr function requires the object as its first argument (which i can't provide within the class definition). I feel like something with the __setattr__ method, or using metaclasses is the way to go, but I can't figure it out. – gnr Dec 13 '11 at 15:14
  • I think our posts just crossed: yes, metaclasses is the way to go. The article I link to should get you up to date very quickly. – Daren Thomas Dec 13 '11 at 15:29
  • @mlauria: "for portability i'd prefer to do it all within the class's startup" -- this is completely unrelated to portability. Just add the call to `update()` directly after the class definition. – Sven Marnach Dec 13 '11 at 15:31
  • @sven: maybe portability isn't the right word, i mean that i'd like for a "from mymodule import Foo" to work correctly (even though it's frowned upon to use the from a import b style), without having to run additional commands in the module. in any case, would you go with daren's metaclass implementation or kindall's use of locals()? – gnr Dec 13 '11 at 15:39
  • 1
    if you run update in your module mymodule the `from mymodule import Foo` will work correctly – Xavier Combelle Dec 13 '11 at 15:56
  • duh! forgot about that fact, thx @XavierCombelle - i guess i'd just prefer to have it all in one place, but maybe there's some larger problem with the setup i'm avoiding. – gnr Dec 13 '11 at 16:01
  • @mlauria: It *is* all in one place if you put the call to `update()` call directly under the class definition. But you are right that you probably have a design problem here. Variable names and class attributes should be static, not dynamic. There are situations where dynamic *instance* attributes are useful; they are easy to achieve by modifying the dictionary `vars(obj)` for an instance `obj`. Otherwise, using a dictionary instead of trying to dynamically modify variable or class attribute names is generally the better approach. – Sven Marnach Dec 13 '11 at 16:43
  • @SvenMarnach good point about placing the call to update inside the class definition, and yes i think there are some design issues i may need to think through. thx for the help! – gnr Dec 13 '11 at 17:36
  • @mlauria: Not *inside* the class definition -- directly *after* the class definition. Inside the class definition, the class is not defined yet, so you cannot pass it as first argument to `update()`. – Sven Marnach Dec 13 '11 at 18:16