Using metaclasses to inherit class variables

Steven Bethard steven.bethard at gmail.com
Mon May 22 23:56:11 CEST 2006


telesphore4 at gmail.com wrote:
> Fresh copies of class vars so the first one is the correct: ('foo',
> 'bar', [], False)

Ahh, yeah, then you definitely need the copy.copy call.

>>>> import copy
>>>>
>>>> class ClassVars(type):
> ...     def __init__(cls, name, bases, dict):
> ...         for name, value in type(cls).classVars.iteritems():
> ...             if name not in dict:
> ...                 setattr(cls, name, copy.copy(value))
> ...
>>>> def are(**kwargs):
> ...     return type('', (ClassVars,), {'classVars':kwargs})
> ...
>>>> class C(object):
> ...     __metaclass__ = are(name='foo', desc='bar', list=[])
> ...     name = 'not foo' #<<< Changing a copy only owned by this class
> ...
>>>> class D(C):
> ...     pass
> ...
>>>> C.name, C.desc, C.list
> ('not foo', 'bar', []) <<<<<<<<<<<<<<<<<<<<<< Separate copy we changed
>>>> D.name, D.desc, D.list, D.list is C.list
> ('foo', 'bar', [], False) <<<<<<<<<<<<<<<<<<<< Defaults are not changed

Hmm...  I don't think I can get away with just a function for 
__metaclass__ in this situation since D doesn't invoke it:

 >>> class C(object):
...     def __metaclass__(*args):
...         print '__metaclass__%r' % (args,)
...         return type(*args)
...
__metaclass__('C', (<type 'object'>,), {'__module__': '__main__', 
'__metaclass__': <function __metaclass__ at 0x00FA89F0>})
 >>> class D(C):
...     pass
...
 >>>

I'm not sure why this is.  Anyone else out there know?  D *does* invoke 
__metaclass__ if it's a subclass of type:

 >>> class C(object):
...     class __metaclass__(type):
...         def __init__(*args):
...             print '__init__%r' % (args,)
...
__init__(<class '__main__.C'>, 'C', (<type 'object'>,), {'__module__': 
'__main__', '__metaclass__': <class '__main__.__metaclass__'>})
 >>> class D(C):
...     pass
...
__init__(<class '__main__.D'>, 'D', (<class '__main__.C'>,), 
{'__module__': '__main__'})
 >>>

So it seems you pretty much have to go with the approach you're 
currently using.  It doesn't make any real difference, but I'd tend to 
do it with a nested class statement instead of a call to type:

 >>> def set_classvars(**kwargs):
...     class __metaclass__(type):
...         def __init__(cls, name, bases, classdict):
...             for name, value in kwargs.iteritems():
...                 if name not in classdict:
...                     setattr(cls, name, copy.copy(value))
...     return __metaclass__
...
 >>> class C(object):
...     __metaclass__ = set_classvars(name='foo', desc='bar', list=[])
...     name = 'not foo'
...
 >>> class D(C):
...     pass
...
 >>> C.name, C.desc, C.list
('not foo', 'bar', [])
 >>> D.name, D.desc, D.list, D.list is C.list
('foo', 'bar', [], False)


> Thanks for your help btw.

No problem.  This is much more fun than doing real work. ;-)

STeVe



More information about the Python-list mailing list