Using metaclasses to inherit class variables

Discussion in 'Python' started by telesphore4@gmail.com, May 19, 2006.

  1. Guest

    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, 2006
    #1
    1. Advertising

  2. 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
    Steven Bethard, May 20, 2006
    #2
    1. Advertising

  3. Guest

    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, 2006
    #3
  4. Guest

    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, 2006
    #4
  5. Guest

    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, 2006
    #5
  6. 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
    Steven Bethard, May 22, 2006
    #6
  7. Guest

    Much better. Thanks again.
    , May 22, 2006
    #7
  8. Guest

    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, 2006
    #8
  9. 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
    Steven Bethard, May 22, 2006
    #9
  10. Guest

    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, 2006
    #10
  11. 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
    Steven Bethard, May 22, 2006
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Kevin Spencer
    Replies:
    6
    Views:
    6,220
    Charlie@CBFC
    Apr 23, 2004
  2. Replies:
    0
    Views:
    355
  3. Daniele Varrazzo
    Replies:
    0
    Views:
    255
    Daniele Varrazzo
    Jul 19, 2004
  4. Jan-Ole Esleben

    Metaclasses and class variables

    Jan-Ole Esleben, Aug 4, 2005, in forum: Python
    Replies:
    8
    Views:
    329
    Bengt Richter
    Aug 5, 2005
  5. David Hirschfield

    Can one class have multiple metaclasses?

    David Hirschfield, Jan 16, 2006, in forum: Python
    Replies:
    1
    Views:
    374
    Alex Martelli
    Jan 16, 2006
Loading...

Share This Page