Reputation: 162
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
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
Reputation: 71044
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
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