By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
445,824 Members | 1,255 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 445,824 IT Pros & Developers. It's quick & easy.

Using metaclasses to inherit class variables

P: n/a
I want to inherit fresh copies of some class variables. So I set up a
metaclass and meddle with the class variables there.

Now it would be convenient to run thru a dictionary rather than
explicitly set each variable. However getattr() and setattr() are out
because they chase the variable thru the class hierarchy.

So, I read the dictionary directly with cls.__dict__.has_key(var).
Reading works but when I go to set the object's dictionary directly
with:

cls.__dict__[var] = val

I get the following error:

File Table.py, line 10, in __init__
if not cls.__dict__.has_key(var): cls.__dict__[var] = val
TypeError: Error when calling the metaclass bases
object does not support item assignment

Is there an easy way around this? Or am I stuck listing out the
variables one per line?

class SetClassVars(type):
cvars = dict(name=None, desc=None, required=True, minlen=1,
maxlen=25, idDown=999999999, idNext=0)
def __init__(cls, name, bases, dict):
if not cls.__dict__.has_key('name'): cls.name = None
if not cls.__dict__.has_key('desc'): cls.desc = None
if not cls.__dict__.has_key('required'): cls.required = True
if not cls.__dict__.has_key('minlen'): cls.minlen = 1
if not cls.__dict__.has_key('maxlen'): cls.maxlen = 25
if not cls.__dict__.has_key('idDown'): cls.idDown = 999999999
if not cls.__dict__.has_key('idNext'): cls.idNext = 0

# It would be more convenient to loop thru a dictionary
#for var, val in SetClassVars.cvars.iteritems():

# getattr() and setattr() run thru the MRO
# which is not what I want
#if not getattr(cls, var): setattr(cls, var, val)
#if not cls.__dict__.has_key(var): setattr(cls, var, val)

# Setting the dictionary directly generates an error
#if not cls.__dict__.has_key(var): cls.__dict__[var] = val

thanks
t4

May 19 '06 #1
Share this Question
Share on Google+
10 Replies


P: n/a
te*********@gmail.com wrote:
I want to inherit fresh copies of some class variables. So I set up a
metaclass and meddle with the class variables there.

Now it would be convenient to run thru a dictionary rather than
explicitly set each variable. However getattr() and setattr() are out
because they chase the variable thru the class hierarchy.
getattr() does, setattr() doesn't.
Is there an easy way around this? Or am I stuck listing out the
variables one per line?

class SetClassVars(type):
cvars = dict(name=None, desc=None, required=True, minlen=1,
maxlen=25, idDown=999999999, idNext=0)
def __init__(cls, name, bases, dict):
if not cls.__dict__.has_key('name'): cls.name = None
if not cls.__dict__.has_key('desc'): cls.desc = None
if not cls.__dict__.has_key('required'): cls.required = True
if not cls.__dict__.has_key('minlen'): cls.minlen = 1
if not cls.__dict__.has_key('maxlen'): cls.maxlen = 25
if not cls.__dict__.has_key('idDown'): cls.idDown = 999999999
if not cls.__dict__.has_key('idNext'): cls.idNext = 0


Does this do what you want? Note that I don't even bother with __dict__
since the class dict is already available as the final argument to __init__.
class SetClassVars(type): .... cvars = dict(name=None, desc=None, required=True)
.... def __init__(cls, name, bases, classdict):
.... for name, value in SetClassVars.cvars.iteritems():
.... if not name in classdict:
.... setattr(cls, name, value)
.... class C(object): .... __metaclass__ = SetClassVars
.... name = 'foo'
.... class D(C): .... __metaclass__ = SetClassVars
.... desc = 'bar'
.... print C.name, C.desc, C.required foo None True print D.name, D.desc, D.required

None bar True
STeVe
May 20 '06 #2

P: n/a
Hmm. setattr() only does a shallow search. Good to know.

Your

if not name in dict: setattr(cls, name, value)

is a more succinct/better way of writing

if not cls.__dict__.has_key(var): setattr(cls, var, val)

Which i tested a fair bit.

OK it appears that both are working for the simple types. However, if I
do:
class SetClassVars(type): .... cvars = dict(lst=[], desc=None)
.... def __init__(cls, name, bases, classdict):
.... for name, value in SetClassVars.cvars.iteritems():
.... if not name in classdict: setattr(cls, name, value)
class C(object): .... __metaclass__ = SetClassVars
.... desc = 'foo'
.... class D(C): .... desc = bar
C.lst.append('ccccc')
D.lst.append('dddd')
C.lst ['ccccc', 'dddd'] D.lst

['ccccc', 'dddd']

I get the piling on behavior.

OK. So it seems to be a problem only with the mutable list. I made the
mistake of thinking that the list behavior was the same as for
non-mutables.

This must be a newbie mistake and it is probably documented somewhere.
*Ding* I'll bet it is the same one that bites newbies when they define
functions like:

def myfunc(lst=[]):

Looking for complicated problems with metaclasses when simple mistakes
about mutables are the issue. Occam wags his finger at me.

Thank you. That helped.
t4

May 20 '06 #3

P: n/a
If some one ever wants to build on this in the future, the current form
and use is:

import copy

class ClassVars(type):
classVars = dict(fields=[], longest=0) # <<<< adjust this >>>>
def __init__(cls, name, bases, dict):
for name, value in ClassVars.classVars.iteritems():
if name not in dict: setattr(cls, name, copy.copy(value))

class Table(object):
__metaclass__ = ClassVars
# Rest of class follows...

I use these class varaibles in 2 places and attempted, breifly, to
abstract them into a module. But I ran into a chicken and egg situation
were I wanted to use fresh copies of class variables to define the
abstracted class. Given that I'd onlly save (2*3) 6 lines of code in my
application, I figured that move on for now.

May 21 '06 #4

P: n/a
OK no question. I'm only posting b/c it may be something another newbie
will want to google in the future. Now that I've worked thru the
process this turns out to be fairly easy.

However, if there are better ways please let me know.

Module = ClassVars.py

import copy

class ClassVars(type):
classVars = {}
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))

count = 0 # Not really needed but it semed nice to name the new types
def are(dict):
global count
count += 1
return type('ClassVars%d' % count, (ClassVars,),
{'classVars':dict})
To use in another module:

import ClassVars

class MyClass(str):
__metaclass__ = ClassVars.are(dict(name=None, desc=None,
myList=[]))

# Rest of class definition ...
Thanks for the help.
t4

May 22 '06 #5

P: n/a
te*********@gmail.com wrote:
OK no question. I'm only posting b/c it may be something another newbie
will want to google in the future. Now that I've worked thru the
process this turns out to be fairly easy.

However, if there are better ways please let me know.

Module = ClassVars.py

import copy

class ClassVars(type):
classVars = {}
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))

count = 0 # Not really needed but it semed nice to name the new types
def are(dict):
global count
count += 1
return type('ClassVars%d' % count, (ClassVars,),
{'classVars':dict})
To use in another module:

import ClassVars

class MyClass(str):
__metaclass__ = ClassVars.are(dict(name=None, desc=None,
myList=[]))

# Rest of class definition ...


Hmm... That still seems more complicated than you need. I think you
really want to be able to write something like:

class C(object):
__metaclass__ = set_classvars(name=None, desc=None, myList=[])

Which is actually quite easily done with nested functions:
def set_classvars(**kwargs): .... def __metaclass__(name, bases, classdict):
.... for name, value in kwargs.iteritems():
.... if name not in classdict:
.... classdict[name] = value
.... return type(name, bases, classdict)
.... return __metaclass__
.... class C(object): .... __metaclass__ = set_classvars(name='foo', desc='bar', list=[])
.... name = 'not foo'
.... C.name, C.desc, C.list ('not foo', 'bar', []) class D(C): .... __metaclass__ = set_classvars(name='foo', list=[])
.... D.name, D.desc, D.list, D.list is C.list

('foo', 'bar', [], False)
STeVe
May 22 '06 #6

P: n/a
Much better. Thanks again.

May 22 '06 #7

P: n/a
Oops! This isn't working. As the sequence I'm trying for is....
def set_classvars(**kwargs): .... def __metaclass__(name, bases, classdict):
.... for name, value in kwargs.iteritems():
.... if name not in classdict:
.... classdict[name] = value
.... return type(name, bases, classdict)
.... return __metaclass__
.... class C(object): .... __metaclass__ = set_classvars(name='foo', desc='bar', list=[])
.... name = 'not foo'
.... C.name, C.desc, C.list ('not foo', 'bar', []) class D(C): .... pass #<<<<<< Use Super's metaclass
.... D.name, D.desc, D.list, D.list is C.list

('not foo', 'bar', [], True)

So... I just changed my stuff to be:

import copy

class ClassVars(type):
classVars = {}
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})

Altho I'd like to see what you come up with too... if you persue this

May 22 '06 #8

P: n/a
te*********@gmail.com wrote:
Oops! This isn't working. As the sequence I'm trying for is....
def set_classvars(**kwargs): ... def __metaclass__(name, bases, classdict):
... for name, value in kwargs.iteritems():
... if name not in classdict:
... classdict[name] = value
... return type(name, bases, classdict)
... return __metaclass__
... class C(object): ... __metaclass__ = set_classvars(name='foo', desc='bar', list=[])
... name = 'not foo'
... C.name, C.desc, C.list ('not foo', 'bar', []) class D(C): ... pass #<<<<<< Use Super's metaclass
... D.name, D.desc, D.list, D.list is C.list

('not foo', 'bar', [], True)


What should the "right" answer be here? Maybe

('foo', 'bar', [], False)

or

('not foo', 'bar', [], False)

or something else?
STeVe
May 22 '06 #9

P: n/a
Sorry for not being clear.

Fresh copies of class vars so the first one is the correct: ('foo',
'bar', [], False)
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

Both prints are correct here

Thanks for your help btw.
t4

May 22 '06 #10

P: n/a
te*********@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
May 22 '06 #11

This discussion thread is closed

Replies have been disabled for this discussion.