[rfc] An object that creates (nested) attributes automatically on assignment

Edd edd at nunswithguns.net
Fri Apr 10 22:04:38 EDT 2009


Hi folks,

I'd like to use Python itself as the configuration language for my
Python application. I'd like the user to be able to write something
like this in their config file(s):

   cfg.laser.on = True
   cfg.laser.colour = 'blue'
   cfg.discombobulated.vegetables = ['carrots', 'broccoli']
   # ...

To this end, I've created a class that appears to allow instance
variables to be created on the fly. In other words, I can to the
following to read a config file:

    cfg = Config()
    execfile(filename, {'cfg', cfg}, {})

However, I think my implementation of the Config class is a little
crappy. I'd really appreciate the critical eye of a pro. Here's the
sauce:

    class Config(object):
        def __init__(self, sealed=False):
            def seal():
                for v in self._attribs.values():
                    if isinstance(v, self.__class__): v.seal()
                del self.__dict__['seal']

            d =  {'_attribs': {}, '_a2p': None}
            if not sealed: d['seal'] = seal

            self.__dict__.update(d)

        def __getattr__(self, key):
            if not key in self._attribs:
                d = Config(sealed='seal' not in self.__dict__)
                def add2parent():
                    self._attribs[key] = d
                    if self._a2p:
                        self._a2p()
                        self._a2p = None

                # if anything is assigned to an attribute of d,
                # make sure that d is recorded as an attribute of this
Config
                d._a2p = add2parent
                return d
            else:
                return self._attribs[key]

        def __setattr__(self, key, value):
            if key in self.__dict__:
                self.__dict__[key] = value
            else:
                if not 'seal' in self.__dict__:
                    clsname = self.__class__.__name__
                    raise AttributeError("'%s' object attribute '%s'
is read-only (object is sealed)" % (clsname, key))
                self.__dict__['_attribs'][key] = value
                if self._a2p:
                    self._a2p()
                    self._a2p = None

        def __delattr__(self, key):
            if key in self.__dict__:
                clsname = self.__class__.__name__
                raise AttributeError("can't delete '%s' object
attribute '%s' as it is used for book-keeping!" % (clsname, key))
            else:
                if key in self._attribs:
                    del self._attribs[key]

        def __bool__(self):
            return bool(self._attribs)

        def __nonzero__(self):
            return bool(self._attribs)

    if __name__ == '__main__':
        cfg = Config()
        cfg.a = 1
        cfg.b.c = 2
        cfg.d.e.f.g.h = [1, 2, 3]
        print cfg.a
        print cfg.b.c
        print cfg.d.e.f.g.h

        del cfg.b.c
        print cfg.b.c

        try:
            del cfg.d.e._attribs
        except AttributeError, ex:
            print ex

        cfg.seal()
        try:
            cfg.k.l.z = []
        except AttributeError, ex:
            print ex

Once the config is loaded, it will be passed down to other user-
written scripts and it's important that these scripts don't
accidentally change the config. So the idea is that I'll call cfg.seal
() to prevent any further changes before passing it on to these other
scripts. Beyond the general fiddliness of the code, I think the way
seal() currently works is particularly pants.

I considered using a simpler approach:

    def mkdd(): return defaultdict(mkdd)
    cfg = mkdd()
    execfile(filename, {'cfg': cfg}, {})

But I quite like the way the '.' separators quite naturally (IMO)
indicate a hierarchy of settings.

Comments and suggestions welcome!

Kind regards,

Edd



More information about the Python-list mailing list