A possible clever trick for extension writing
Greg Ward
greg at gerg.ca
Sat Nov 26 20:17:55 CST 2011
A common pattern in Mercurial extensions is the "dynamic subclass"
trick. E.g. here is the world's simplest extension using that trick:
"""
def reposetup(ui, repo):
class simplerepo(repo.__class__):
def tags(self):
result = super(simplerepo, self).tags()
result['first'] = self.lookup(0)
return result
repo.__class__ = simplerepo
"""
That works great, but it's a bit annoying that the simplerepo class is
indented 4 spaces more than most other Python classes. Wouldn't it be
nice if there was a way to write that class at module scope and save 4
precious spaces on the left hand of the screen? Take a look in
hgext/largefiles/reposetup.py to see what I'm talking about; there's
some freakishly deep-indented code in there.
So I started thinking about ways to make this better. Here's my first
idea: create the dynamic subclass more, well, dynamically. It relies
on the fact that type() has a second personality: call it like
type(name, bases, dict) and it creates a new class object. Here's the
code:
"""
# template is a classic class because simplerepo.__dict__
# is less work than dict(vars(simplerepo))
class simplerepo_template:
def tags(self):
self.ui.write('hello from simplerepo.tags()\n')
result = super(self.__class__, self).tags()
result['first'] = self.lookup(0)
return result
def reposetup(ui, repo):
repo.__class__ = type('simplerepo',
(repo.__class__,),
simplerepo_template.__dict__)
"""
Problem solved! By writing a "class" that is really just a namespace
used to populate the *real* class, which is created dynamically in
reposetup(), I have regained my precious 4 spaces on the left. This is
slightly more obscure than the original dynamic subclass trick, but
anyone who can wrap their head around
class simplerepo(repo.__class__)
should be fine.
But of course, this doesn't solve the *real* problem. The *real*
problem is that I first heard about metaclasses in 1998, back when Jim
Fulton was still trying convince people there was a problem with
Python's type system. Ever since Python 2.2 came out I've been looking
for an excuse to write a metaclass. I may have finally found my
excuse: Mercurial extensions.
So here it is:
"""
class extensionmetaclass(type):
def __new__(metacls, name, bases, dict):
print "meta __new__: name=%r, bases=%r, dict=%r" % (name, bases, dict)
(repo, template) = bases
bases = (repo.__class__,)
dict.update(template.__dict__)
cls = type.__new__(metacls, name, bases, dict)
print "cls=%r, __mro__=%r" % (cls, cls.__mro__)
repo.__class__ = cls
return cls
def reposetup(ui, repo):
class simplerepo(repo, simplerepo_template):
__metaclass__ = extensionmetaclass
"""
The idea is that extensionmetaclass would go in
mercurial/extensions.py for all extensions to use for dynamic
subclassing. Then each extension's reposetup() would do something like
the above.
If you don't have a clue what's going on here, the first two Google
hits from "python metaclass" are pretty good:
http://www.ibm.com/developerworks/linux/library/l-pymeta/index.html
http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
In a nutshell, extensionmetaclass changes what happens when a "class"
statement is processed to create a class object at runtime.
Opinions? Is this a piece of pure evil that should be cast back into
the outer darkness? A brilliant idea that should be immediately
adopted and promoted? Cute, but overly clever for the problem at hand?
(Personally, I think it's all three, which probably means it's not
actually #2 -- but worth exploring and considering.)
Greg
More information about the Mercurial-devel
mailing list