Using metaclasses to inherit class variables

T

telesphore4

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
 
S

Steven Bethard

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__.
.... 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)
........ __metaclass__ = SetClassVars
.... name = 'foo'
........ __metaclass__ = SetClassVars
.... desc = 'bar'
....None bar True


STeVe
 
T

telesphore4

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:
.... 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)
.... __metaclass__ = SetClassVars
.... desc = 'foo'
........ 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
 
T

telesphore4

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

telesphore4

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
 
S

Steven Bethard

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 __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__
........ __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=[])
....('foo', 'bar', [], False)


STeVe
 
T

telesphore4

Oops! This isn't working. As the sequence I'm trying for is........ 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__
........ __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
....('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
 
S

Steven Bethard

Oops! This isn't working. As the sequence I'm trying for is....... 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__
...... __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
...('not foo', 'bar', [], True)

What should the "right" answer be here? Maybe

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

or

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

or something else?


STeVe
 
T

telesphore4

Sorry for not being clear.

Fresh copies of class vars so the first one is the correct: ('foo',
'bar', [], False)
.... 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))
........ return type('', (ClassVars,), {'classVars':kwargs})
........ __metaclass__ = are(name='foo', desc='bar', list=[])
.... name = 'not foo' #<<< Changing a copy only owned by this class
........ 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
 
S

Steven Bethard

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.
... 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))
...... return type('', (ClassVars,), {'classVars':kwargs})
...... __metaclass__ = are(name='foo', desc='bar', list=[])
... name = 'not foo' #<<< Changing a copy only owned by this class
...... 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:
.... def __metaclass__(*args):
.... print '__metaclass__%r' % (args,)
.... return type(*args)
....
.... 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 __metaclass__(type):
.... def __init__(*args):
.... print '__init__%r' % (args,)
....
[/QUOTE]
.... pass
....
[/QUOTE]

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:
.... 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__
........ __metaclass__ = set_classvars(name='foo', desc='bar', list=[])
.... name = 'not foo'
........ 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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,697
Latest member
AugustNabo

Latest Threads

Top