name lookup failure using metaclasses with unittests

Ulrich Eckhardt ulrich.eckhardt at dominolaser.com
Fri Apr 12 03:17:39 EDT 2013


Am 11.04.2013 10:19, schrieb Steven D'Aprano:
> if sys.version >= '3':

Use sys.version_info >= (3,), otherwise your code breaks when upgrading 
to Python 10 and greater. ;^)


>> 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. [...]
>
> Not in general, since the metaclass has to exist independently of the
> class.

Thanks for your explanations, they are appreciated.


 > 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.

I don't like the approach to define the code to post-process a class 
before defining the class. It's a bit like top-posting, it messes up the 
reading order. Since I really intend to post-process the class, it seems 
that metaclasses are simply not the right tool.

At the moment, this leaves me with two options:

1. post-process the class

class X:
     pass
# attach constants to clas X
for i in (1, 2, 3):
     setattr(X, 'f{}' % i, i)

2. generate code inline

class Y: pass
     # generate constants in local (class-)namespace
     for i in (1, 2, 3):
         locals()['f{}' % i] = i

In both cases, variables (loop variable 'i') are leaked into the 
surrounding namespace, which is kind-of ugly. The second approach also 
seems a bit hackish and I can't use the class-in-definition there, which 
is limiting when you want to attach e.g. constants of type X to X.


>> 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.

Alas, they also need to be defined before the class, messing with the 
mentioned order of declaration. They can be used to call a class 
function though which then does the necessary postprocessing...

3. post-process the class triggered with decorator

def postprocess_class(cls):
     """invoke postprocess() on the decorated object"""
     cls.postprocess()
     del cls.postprocess
     return cls

@postprocess_class
class Z:
     @classfunction
     def postprocess(cls):
         # attach constants to class
         for i in (1, 2, 3):
             setattr(cls, 'f{}' % i, i)


I guess I'll stay with variant 1 for now, since it requires the least 
amount of code and the least amount of questions from other developers here.

Thanks everybody!

Uli






More information about the Python-list mailing list