Andrea Censi
Andrea Censi

Reputation: 162

Associating properties to Class objects

I would like to define properties for classes, and be able to access them before I actually instantiate an object of that class.

I'll give some context. My application handles a library of components. Each component is mapped to a Python class. Now I want to know what configuration a component needs, before actually instancing the class.

One solution is to write something like this:

class Component:
    @classmethod
    def config(cls, name, description, default=None):
        ''' Define one configuration switch for the class. '''
        # now put this information in a class-specific dictionary

class Model1(Component):
    @classmethod
    def define_configuration(cls):
        cls.config('n', 'number of burzs to instigate')
        cls.config('skip', 'skip any barzs among the burzs', default=True)
    # ...

component_class = Model1
component_class.define_configuration()

However, it seems pretty ugly. Ideally I would like to be able to write something like the following, and still be able to put the configuration switches in a class-specific dictionary to be accessed later.

class Model1(Component):
    config('n', 'number of burz to instigate')
    config('skip', 'skip any barz in the data', default=True)

My initial solution was to write something like:

class Model1(Component):
    Model1.config('n', 'number of burz to instigate')
    Model1.config('skip', 'skip any barz in the data', default=True)

however I discovered on other questions here on SO that the class name is not defined yet when the body is executed.

What should I do?

tl;dr: How can I get a nice syntax for defining class-specific properties (before I instance an object of that class)?


Here is (for the record) the solution that was suggested (a bit elaborated). Yai! I could get exactly what I wanted. :-)

from collections import namedtuple
Config = namedtuple('Config', 'name desc default')

def config(name, description, default=None):
    ComponentMeta.tmp_config_storage.append(Config(name, description, default))

class ComponentMeta(type):
    tmp_config_storage = []
    def __init__(cls, clsname, bases, clsdict):
        for config in ComponentMeta.tmp_config_storage:
            if not 'my_config' in cls.__dict__:
                setattr(cls, 'my_config', [])
            cls.my_config.append(config)
        ComponentMeta.tmp_config_storage = []

class Component(object):
    __metaclass__ = ComponentMeta

class Model1(Component):
    config('x1', 'for model1')
    config('y1', 'for model1')

class Model2(Component):
    config('x2', 'for model2')

print 'config for Model1:', Model1.my_config
print 'config for Model2:', Model2.my_config

Upvotes: 2

Views: 215

Answers (3)

Alex Martelli
Alex Martelli

Reputation: 882113

Why not something simple like, say:

def makeconfigdict(cls):
    thedict = {}
    for name, value in cls.__dict__.items():
       if isaconfig(value):
           addconfigtodict(thedict, name, value)
           delattr(cls, name)
    cls.specialdict = thedict
    return cls

@makeconfigdict
class Model1(Component):
    n = config('number of burz to instigate')
    skip = config('skip any barz in the data', default=True)
    ...

As long as the config function returns objects that an isaconfig function can recognize, and an addconfigtodict function can properly set into the special dictionary in whatever format you want, you're in clover.

If I understand your specs correctly you don't want a normal attribute like Model1.skip, right? That's why I have that delattr call in makeconfigdict (and also why I use .items() on the class's dictionary -- since the dictionary is modified during the loop, it's better to use .items(), which takes a "snapshot" list of all names and values, rather than the usual .iteritems(), which just iterates on the dictionary, thus not allowing it to be modified during the loop).

Upvotes: 1

aaronasterling
aaronasterling

Reputation: 71044

corrected

def config(name, description, default=None):
    ComponentMeta.config_items.append((name, description, default))

class ComponentMeta(type):
    config_items = []
    def __init__(cls, clsname, bases, clsdict):
        for options in ComponentMeta.config_items:
                cls.add_config(*options)
        ComponentMeta.config_items = []

class Component(object):
    __metaclass__ = ComponentMeta
    config_items = [] # this is only for testing. you don't need it
    @classmethod
    def add_config(cls, name, description, default=None):
        #also for testing
        cls.config_items.append((name, description, default))

class Model1(Component):
    config('n', 'number of burz to instigate')
    config('skip', 'skip any barz in the data', default=True)

print Model1.config_items

This works by making config an external function that adds the items to ComponentMeta.config_instances. ComponentMeta then checks this list when creating a class and calls config_item on the items. Note that this is not thread safe (although it could me made so). Also, if a subclass of ComponentMeta fails to call super or empty ComponentMeta.config_items itself, the next created class will get the config items.

Upvotes: 1

pyfunc
pyfunc

Reputation: 66729

A class name is defined once the body is executed. This happens when the file containing the class is imported. This is not the same as creation of an instance of a class.

class A(object):
    """ doc """
    config = []
    def __init__(def):
        pass
A.config.append(('n', 'number of burz to instigate'))
A.config.append(('skip', 'skip any barz in the data', default=True))
# access class specific config before object instantiation
x = A.config[0]
# instantiating your class, post configuration access
a_obj = A()

Wouldn't this approach serve your purpose? The class config can be stored in a class variable which can be modified and added before you instantiate any objects of this class.

Using class variables should serve your purpose.

Upvotes: 1

Related Questions