Search code examples
pythonmetaclass

What are some (concrete) use-cases for metaclasses?


I have a friend who likes to use metaclasses, and regularly offers them as a solution.

I am of the mind that you almost never need to use metaclasses. Why? because I figure if you are doing something like that to a class, you should probably be doing it to an object. And a small redesign/refactor is in order.

Being able to use metaclasses has caused a lot of people in a lot of places to use classes as some kind of second rate object, which just seems disastrous to me. Is programming to be replaced by meta-programming? The addition of class decorators has unfortunately made it even more acceptable.

So please, I am desperate to know your valid (concrete) use-cases for metaclasses in Python. Or to be enlightened as to why mutating classes is better than mutating objects, sometimes.

I will start:

Sometimes when using a third-party library it is useful to be able to mutate the class in a certain way.

(This is the only case I can think of, and it's not concrete)


Solution

  • I have a class that handles non-interactive plotting, as a frontend to Matplotlib. However, on occasion one wants to do interactive plotting. With only a couple functions I found that I was able to increment the figure count, call draw manually, etc, but I needed to do these before and after every plotting call. So to create both an interactive plotting wrapper and an offscreen plotting wrapper, I found it was more efficient to do this via metaclasses, wrapping the appropriate methods, than to do something like:

    class PlottingInteractive:
        add_slice = wrap_pylab_newplot(add_slice)
    

    This method doesn't keep up with API changes and so on, but one that iterates over the class attributes in __init__ before re-setting the class attributes is more efficient and keeps things up to date:

    class _Interactify(type):
        def __init__(cls, name, bases, d):
            super(_Interactify, cls).__init__(name, bases, d)
            for base in bases:
                for attrname in dir(base):
                    if attrname in d: continue # If overridden, don't reset
                    attr = getattr(cls, attrname)
                    if type(attr) == types.MethodType:
                        if attrname.startswith("add_"):
                            setattr(cls, attrname, wrap_pylab_newplot(attr))
                        elif attrname.startswith("set_"):
                            setattr(cls, attrname, wrap_pylab_show(attr))
    

    Of course, there might be better ways to do this, but I've found this to be effective. Of course, this could also be done in __new__ or __init__, but this was the solution I found the most straightforward.