gnr
gnr

Reputation: 2414

python dynamically set non-instance class attributes

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?

Upvotes: 3

Views: 637

Answers (3)

Rick
Rick

Reputation: 45231

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)

Upvotes: 2

kindall
kindall

Reputation: 184071

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

Upvotes: 5

Daren Thomas
Daren Thomas

Reputation: 70314

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?

Upvotes: 2

Related Questions