Dynamically create class attributes

18,079

Solution 1

You could do it without metaclasses using decorators. This way is a bit more clear IMO:

def apply_defaults(cls):
    defaults = {
        'default_value1':True,
        'default_value2':True,
        'default_value3':True,
    }
    for name, value in defaults.items():
        setattr(cls, name, some_complex_init_function(value, ...))
    return cls

@apply_defaults
class Settings(object):
    pass

Prior to Python 2.6 class decorators were unavailable. So you can write:

class Settings(object):
    pass
Settings = apply_defaults(Settings)

in older versions of python.

In the provided example apply_defaults is reusable… Well, except that the defaults are hard-coded in the decorator's body :) If you have just a single case you can even simplify your code to this:

defaults = {
    'default_value1':True,
    'default_value2':True,
    'default_value3':True,
}

class Settings(object):
    """Your implementation goes here as usual"""

for name, value in defaults.items():
    setattr(Settings, name, some_complex_init_function(value, ...))

This is possible since classes (in the sense of types) are objects themselves in Python.

Solution 2

I think that's case for metaclass:

class SettingsMeta(type):
    def __new__(cls, name, bases, dct):
        for name, value in defaults.items():
            dct[name] = some_complex_init_function(value, ...)
        return type.__new__(cls, name, bases, dct)

class Settings(object):
    __metaclass__ = SettingsMeta

Solution 3

When defining a class, the local namespace will be converted into the class namespace at the conclusion of the class body. As such, you can accomplish this with:

class Settings(object):
    for key, val in defaults.iteritems():
        locals()[key] = some_complex_init_function(val, ...)
Share:
18,079
xyz-123
Author by

xyz-123

Updated on June 06, 2022

Comments

  • xyz-123
    xyz-123 about 2 years

    I need to dynamically create class attributes from a DEFAULTS dictionary.

    defaults = {
        'default_value1':True,
        'default_value2':True,
        'default_value3':True,
    }
    
    class Settings(object):
        default_value1 = some_complex_init_function(defaults[default_value1], ...)
        default_value2 = some_complex_init_function(defaults[default_value2], ...)
        default_value3 = some_complex_init_function(defaults[default_value3], ...)
    

    I could also achive this by having sth. like __init__ for class creation, in order to dynamically create these attributes from dictionary and save a lot of code and stupid work.

    How would you do this?

    Thank you very much in advance!

  • martineau
    martineau over 13 years
    -1 The documentation for locals() says the contents of the dictionary returned should not be modified.
  • Simon
    Simon over 11 years
    You also need to return the "decorated" class (cls in the first example) at the end of the decorator function, don't you? Took me a while to figure out, but unless I do this the class will equal None after definition. Am I missing something, like for example some way of supplying the class by reference instead?
  • vijay shanker
    vijay shanker over 8 years
    how do we pass defaults, or how can we initialize any object of this Settings Class
  • TomSawyer
    TomSawyer over 6 years
    How do i pass defaults to the file like this a = Settings(defaults) settings.py is a particular file
  • TomSawyer
    TomSawyer over 6 years
    same question with @vijayshanker
  • Łukasz
    Łukasz over 6 years
    The code depends on the defaults dictionary being in scope when defining Settings class.
  • TomSawyer
    TomSawyer over 6 years
    SettingsMeta and Settings in the setting.py file? How do i pass defaults parameter to it?
  • Łukasz
    Łukasz over 6 years
    With the current code, you cannot pass anything, the defaults dictionary is taken from the scope, defined before Settings class. To be able to pass the defaults, you would need to restructure it. But it feels like a different question to the one asked originally.