name lookup failure using metaclasses with unittests

Steven D'Aprano steve+comp.lang.python at pearwood.info
Thu Apr 11 04:19:27 EDT 2013


On Thu, 11 Apr 2013 08:43:58 +0200, Ulrich Eckhardt wrote:


> The first thing I was wondering was why Python doesn't complain about a
> class property that is marked as special (leading and trailing double
> underscores) but that it knows nothing about. 

Because that breaks backward compatibility.

You write a class in Python 2.6 or 2.7, and make it backward compatible 
with 2.5:

class MyIterator(object):
    def __next__(self):
        ...
    next = __next__


Now you import it into Python 2.5, which has no idea about __next__ so it 
blows up. (For no good reason, since __next__ is harmless in 2.5.)


> Worse, Python 3 should be
> aware of its legacy and recognize the Python 2 metaclass syntax, even if
> only to reject it loudly. I'm pretty sure there is a reason for that,

That will break any future re-use of __metaclass__. It will also make 
version agnostic code much harder:


class Meta(type):
    ...

if sys.version >= '3':
    kwargs = {'metaclass': Meta}
else:
    kwargs = {}

class MyClass(object, **kwargs):
    __metaclass__ = Meta



> The second question that came up was if there is a way to keep a
> metaclass defined inside the class or if the only way is to provide it
> externally. The reason I like this in-class definition is that for my
> case of autogenerated test functions, everything is in one place which
> used to be in a loop that modified the class after its creation. Maybe
> I'm just too brainwashed by static languages though.

Not in general, since the metaclass has to exist independently of the 
class.

The class is an instance of your metaclass. That means that the metaclass 
must exist first, so it can be instantiated when you define the class.

However, there is a loophole: Python's metaclass machinery is actually 
more general than just class-of-classes. The metaclass doesn't have to be 
a class, it can be any callable with the same function signature as the 
three-argument version of type. So despite what I said above, you *can* 
embed the metaclass in the class, if the metaclass is a function created 
with lambda (or equivalent):


# Python 2 version
class MyClass(object):
    __metaclass__ = (lambda name, bases, dict: 
                         sys.stdout.write("Spam!\n") 
                         or type(name, bases, dict)
                    )


# Python 3 version
class MyClass(object, metaclass=lambda name, bases, dict: 
                  sys.stdout.write("Spam!\n") and type(name, bases, dict)
              ):
    pass


But really, except as a trick, why would you do that?


> To get the Python2 feeling back, I have a hack in mind that involves
> creating a baseclass which in turn provides a metaclass that invokes a
> specific function to post-initialize the class, similar to the way
> Python 2 does it automatically, but I'm wondering if there isn't
> anything better.

Seems awfully convoluted and complicated. Python 3 metaclasses work 
exactly the same as Python 2 metaclasses, except the syntax for declaring 
them is slightly different. So if you had this:

class Meta(type):
    ...

class MyClass:
    __metaclass__ = Meta
    ...


just change it to this, and it should work exactly the same:


class Meta(type):
    ...

class MyClass(metaclass=Meta):
    ...



> Also PEP 3115 "Metaclasses in Python 3000"[2] seems to
> consider postprocessing of a class definition as better handled by a
> class decorator, which is something I haven't looked at yet.

Generally, class decorators are less brain-melting than metaclasses.




-- 
Steven



More information about the Python-list mailing list