Metaclasses & docstrings in 2.2

Jeremy Bowers jerf at jerf.org
Wed Jul 24 17:56:49 EDT 2002


On Wed, 24 Jul 2002 05:54:33 -0500, Alex Martelli wrote:

> ...would you please share your uses for metaclasses?  I can use all the
> good examples I learn about -- it's hard to teach people how to use
> metaclasses without actual motivation, and the more real-life examples I
> can collect and quote, the more likely I can help people achieve that
> motivation!

I'm currently working on a hobby project which with luck will actually be
released someday. It's a type of editor that will be easy to extend, and
will be able to knowledgably manipulate many kinds of files. As a result,
this thing will requre lots of modules. In theory, it could end up
importing hundreds of modules, just for the user to jot down a couple of
notes and close the program again. This will quickly become unacceptable
performance, so I will need to dynamically load the modules as they become
necessary.

(In fact I'm not 100% certain how to accomplish this perfectly correctly
and as I get closer to the problem I may post some more questions on the
newsgroup. I know it's possible because I've got some 75% solutions, but I
don't like them. That's not today's issue.)

In conjunction with this, I have a module I've developed that I'm using
for cheap and easy persistence, enclosed at the end of this message. It
uses the pickle module. One of the things I'm sticking in this persistence
space is bindings between keypresses and Command objects (an extension of
the Command pattern).

One problem with that approach is that pickling a refernce to the actual
class of the Command will cause Python to import the module containing
that class when the reference is unpickled. This is a Good Thing, but it
means that the act of unpickling the keymap could invoke the
aforementioned Bad Behavior of loading tens or even hundreds of
unnecessary modules.

The solution to every problem is another layer of indirection, right? I
can store the name of the Command class (or module name + class name,
which is what I do) in the keymap. When the user presses a key, I can
retrieve the name and go get the class to instantiate, right? So I do this
in my dispatcher module:

str2obj = {}
class ClassId(type):
    def __init__(cls, name, bases, dict):
        super(ClassId, cls).__init__(name, bases, dict) cls.classId =
        dict["__module__"] + "." + name

        str2obj[cls.classId] = cls

and in my command base class, there's a

__metaclass__ = dispatcher.ClassId

Now I get a nice dict which I can index off of the .classId of the command
objects, unpickling the strings does not load the modules, and life is
good, with just a few lines.

Why not use the module name and class name directly with sys.modules? I
had a reason last night but I've forgotten it now. I guess please just
take it on faith that that's not a sufficient answer in the *larger context
of my complete program*. I'm certainly not suggesting this would be good
for everybody in all cases.

One thing I do remember is that I'm concentrating a lot of (untested) try:
	cls = sys.modules[commandCls.__class__.__module__][commandCls.__class__.__name__]
else:
	handle class not being loaded

code into this one place, and I'll trade a small metaclass for several
instances of the above any day. 

It's not the greatest of uses. What really rocked my world was how easy it
was, once I understood the concept.






-------

registry.py
It's not necessarily done and people may not like the __getattr__ and
__setattr__ trick. Just know this: It's used in a controlled environment
and does what I want. ;-)

-------

"""There was a nice docstring here, but I tossed it because it largely
didn't apply to the newsgroup posting. Basically, this is a dict that
knows how to dump itself to disk, reload itself, create a place for
somebody else to store data (initSubkey) like a registry, and while you
may not like it, allow access through dot notation rather then [], which
makes better semantic sense in the context this is being used in. It's not
a general dict replacement, it's a focused extension that works for me.
"""

from cPickle import dump, load
from types import *
from gzip import GzipFile

class Table(dict):
    def __init__(self):
        dict.__init__(self)

    # You may find the following two functions morally repugnant. If # so,
    remove them and please don't give grief. ;-) def __getattr__(self,
    name):
        # For pickling correctly
        if not self.has_key(str(name)):
            raise AttributeError(str(name) + " does not exist.")
        return self[name]
    def __setattr__(self, name, value):
        self[name] = value

    def __getitem__(self, name):
        # force to string
        return dict.__getitem__(self, str(name))
    def __setitem__(self, name, value):
        if value == {}:
            dict.__setitem__(self, str(name), Table())
        else:
            dict.__setitem__(self, str(name), value)
    def saveToFile(self, filename = None):
        if filename == None and self.__dict__.has_key("file"):
            filename = self.__dict__["file"]
        elif filename == None:
            raise TypeError("Need filename in second arg.")
        f = GzipFile(filename, "w")
        dump(self, f, 1)
        f.close()
        self.__dict__["file"] = filename
    def loadFromFile(self, filename):
        self.clear()
        f = GzipFile(filename)
        tmp = load(f)
        f.close()
        self.__dict__["file"] = filename
        self.update(tmp)
    def initSubkey(self, subkeyStr, initDict):
        keys = subkeyStr.split(".")
        current = self

        # get to the given subkey
        for key in keys:
            if not current.has_key(key):
                current[key] = Table()
            current = current[key]

        # load up the initial values, if they don't exist for key in
        initDict:
            if not current.has_key(key):
                current[key] = initDict[key]

        return current

_root = Table()
try:
    _root.loadFromFile("your file here")
except:
    _root.saveToFile("your file here")

# in case I change this to a class or something someday def registry():
    return _root

class RegistryUser:
    """RegistryUser is a mixin class that provides easy registry
    initialization for a registry user.

    To provide default values for the registry entries, provide them as
    keyword arguments on the __init__ call, or prepare a dict in advance
    and use ** notation.

    The registry will be located at self.reg for the class."""

    def __init__(self, reg_key, **kwds):
        reg = registry()
        self.reg = reg.initSubkey(reg_key, kwds)




More information about the Python-list mailing list