Metaclass conundrum - binding value from an outer scope

Skip Montanaro skip.montanaro at gmail.com
Thu Apr 20 15:41:51 EDT 2017


For various reasons, I have a class which delegates much functionality to a
singleton instance of another class (exposed by pybind11) instead of
inheriting from that class. So, the construction looks like this (this is
in Python 2.7):

from someothermodule import SomeOtherClass as _SomeOtherClass

class SomeClass(object):
    _instance = None

    def __init__(self):
        if self.__class__._instance is None:
            self._instance = _SomeOtherClass.instance()

    def __getattr__(self, key):
        return getattr(self._instance, key)

    ... and so on ...

If someone tries help(SomeClass) or dir(SomeClass) today, none of the
attributes or docstrings defined in SomeOtherClass are shown. It was almost
a straightforward exercise to write a metaclass which defines methods on
SomeClass which delegate to the underlying method on SomeOtherClass.

My problem is evaluating the return value of getattr(SomeOtherClass, attr)
immediately. The code in __new__ looks like this (somewhat abbreviated, but
only lightly):

class SomeMeta(type):
    def __new__(cls, name, parents, dct):
        for a in dir(SomeOtherClass):
            if a[0] == "_": continue
            underlying = getattr(SomeOtherClass, a)
            def _meth(self, *args):
                return underlying(self._instance, *args)
            _meth.__doc__ = underlying.__doc__
            dct[a] = _meth
        return super(SomeMeta, cls).__new__(cls, name, parents, dct)

(Hopefully the indentation is correct. I have no Emacs on this stupid
Windows machine, so had to actually count my spaces.)

Suppose SomeOtherClass has methods m1, m2, and m3, processed in that order
coming out of the dir() call. All calls to SomeClass.m1, SomeClass.m2 and
SomeClass.m3 will wind up calling SomeOtherClass.m3, because the variable
"underlying" is evaluated late. I need it to be evaluated early (at the
time _meth is defined), when it has the value corresponding to attributes
"m1", "m2", or "m3". Thinking about it for a minute or two, I thought
functools.partial() might save my bacon, but that only partially applies
arguments. It won't work to bind the function name. I thought I might be
able to use it like this:

def _meth(self, underlying=None, *args):
    return underlying(...)

then assign a partial function object to the dictionary:

dct[a] = functools.partial(_meth, underlying=underlying)

but that didn't seem to work (e.g., calling m1(1000) complained that int
objects have no _instance attribute, so clearly self was not getting set
properly), and it changes the runtime interface of the method (so it would
look different to people using SomeClass). Maybe I need to construct the
actual function assigned to dct[a] using types.FunctionType?

There's bound to be a way to do this, and I'm almost certainly going to
:dopeslap: myself when it's revealed, but I'm stuck on this. Any ideas?

Thx,

Skip


More information about the Python-list mailing list