Using metaclasses to inherit class variables

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • telesphore4@gmail.com

    Using metaclasses to inherit class variables

    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__.ha s_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__.ha s_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(ty pe):
    cvars = dict(name=None, desc=None, required=True, minlen=1,
    maxlen=25, idDown=99999999 9, idNext=0)
    def __init__(cls, name, bases, dict):
    if not cls.__dict__.ha s_key('name'): cls.name = None
    if not cls.__dict__.ha s_key('desc'): cls.desc = None
    if not cls.__dict__.ha s_key('required '): cls.required = True
    if not cls.__dict__.ha s_key('minlen') : cls.minlen = 1
    if not cls.__dict__.ha s_key('maxlen') : cls.maxlen = 25
    if not cls.__dict__.ha s_key('idDown') : cls.idDown = 999999999
    if not cls.__dict__.ha s_key('idNext') : cls.idNext = 0

    # It would be more convenient to loop thru a dictionary
    #for var, val in SetClassVars.cv ars.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__.ha s_key(var): setattr(cls, var, val)

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

    thanks
    t4

  • Steven Bethard

    #2
    Re: Using metaclasses to inherit class variables

    telesphore4@gma il.com wrote:[color=blue]
    > 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.[/color]

    getattr() does, setattr() doesn't.
    [color=blue]
    > Is there an easy way around this? Or am I stuck listing out the
    > variables one per line?
    >
    > class SetClassVars(ty pe):
    > cvars = dict(name=None, desc=None, required=True, minlen=1,
    > maxlen=25, idDown=99999999 9, idNext=0)
    > def __init__(cls, name, bases, dict):
    > if not cls.__dict__.ha s_key('name'): cls.name = None
    > if not cls.__dict__.ha s_key('desc'): cls.desc = None
    > if not cls.__dict__.ha s_key('required '): cls.required = True
    > if not cls.__dict__.ha s_key('minlen') : cls.minlen = 1
    > if not cls.__dict__.ha s_key('maxlen') : cls.maxlen = 25
    > if not cls.__dict__.ha s_key('idDown') : cls.idDown = 999999999
    > if not cls.__dict__.ha s_key('idNext') : cls.idNext = 0[/color]

    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__.
    [color=blue][color=green][color=darkred]
    >>> class SetClassVars(ty pe):[/color][/color][/color]
    .... cvars = dict(name=None, desc=None, required=True)
    .... def __init__(cls, name, bases, classdict):
    .... for name, value in SetClassVars.cv ars.iteritems() :
    .... if not name in classdict:
    .... setattr(cls, name, value)
    ....[color=blue][color=green][color=darkred]
    >>> class C(object):[/color][/color][/color]
    .... __metaclass__ = SetClassVars
    .... name = 'foo'
    ....[color=blue][color=green][color=darkred]
    >>> class D(C):[/color][/color][/color]
    .... __metaclass__ = SetClassVars
    .... desc = 'bar'
    ....[color=blue][color=green][color=darkred]
    >>> print C.name, C.desc, C.required[/color][/color][/color]
    foo None True[color=blue][color=green][color=darkred]
    >>> print D.name, D.desc, D.required[/color][/color][/color]
    None bar True


    STeVe

    Comment

    • telesphore4@gmail.com

      #3
      Re: Using metaclasses to inherit class variables

      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__.ha s_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:
      [color=blue][color=green][color=darkred]
      >>> class SetClassVars(ty pe):[/color][/color][/color]
      .... cvars = dict(lst=[], desc=None)
      .... def __init__(cls, name, bases, classdict):
      .... for name, value in SetClassVars.cv ars.iteritems() :
      .... if not name in classdict: setattr(cls, name, value)
      [color=blue][color=green][color=darkred]
      >>> class C(object):[/color][/color][/color]
      .... __metaclass__ = SetClassVars
      .... desc = 'foo'
      ....[color=blue][color=green][color=darkred]
      >>> class D(C):[/color][/color][/color]
      .... desc = bar
      [color=blue][color=green][color=darkred]
      >>> C.lst.append('c cccc')
      >>> D.lst.append('d ddd')
      >>> C.lst[/color][/color][/color]
      ['ccccc', 'dddd'][color=blue][color=green][color=darkred]
      >>> D.lst[/color][/color][/color]
      ['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

      Comment

      • telesphore4@gmail.com

        #4
        Re: Using metaclasses to inherit class variables

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

        Comment

        • telesphore4@gmail.com

          #5
          Re: Using metaclasses to inherit class variables

          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).class Vars.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':di ct})


          To use in another module:

          import ClassVars

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

          # Rest of class definition ...


          Thanks for the help.
          t4

          Comment

          • Steven Bethard

            #6
            Re: Using metaclasses to inherit class variables

            telesphore4@gma il.com wrote:[color=blue]
            > 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).class Vars.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':di ct})
            >
            >
            > To use in another module:
            >
            > import ClassVars
            >
            > class MyClass(str):
            > __metaclass__ = ClassVars.are(d ict(name=None, desc=None,
            > myList=[]))
            >
            > # Rest of class definition ...[/color]

            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(n ame=None, desc=None, myList=[])

            Which is actually quite easily done with nested functions:
            [color=blue][color=green][color=darkred]
            >>> def set_classvars(* *kwargs):[/color][/color][/color]
            .... def __metaclass__(n ame, bases, classdict):
            .... for name, value in kwargs.iteritem s():
            .... if name not in classdict:
            .... classdict[name] = value
            .... return type(name, bases, classdict)
            .... return __metaclass__
            ....[color=blue][color=green][color=darkred]
            >>> class C(object):[/color][/color][/color]
            .... __metaclass__ = set_classvars(n ame='foo', desc='bar', list=[])
            .... name = 'not foo'
            ....[color=blue][color=green][color=darkred]
            >>> C.name, C.desc, C.list[/color][/color][/color]
            ('not foo', 'bar', [])[color=blue][color=green][color=darkred]
            >>> class D(C):[/color][/color][/color]
            .... __metaclass__ = set_classvars(n ame='foo', list=[])
            ....[color=blue][color=green][color=darkred]
            >>> D.name, D.desc, D.list, D.list is C.list[/color][/color][/color]
            ('foo', 'bar', [], False)


            STeVe

            Comment

            • telesphore4@gmail.com

              #7
              Re: Using metaclasses to inherit class variables

              Much better. Thanks again.

              Comment

              • telesphore4@gmail.com

                #8
                Re: Using metaclasses to inherit class variables

                Oops! This isn't working. As the sequence I'm trying for is....[color=blue][color=green][color=darkred]
                >>> def set_classvars(* *kwargs):[/color][/color][/color]
                .... def __metaclass__(n ame, bases, classdict):
                .... for name, value in kwargs.iteritem s():
                .... if name not in classdict:
                .... classdict[name] = value
                .... return type(name, bases, classdict)
                .... return __metaclass__
                ....[color=blue][color=green][color=darkred]
                >>> class C(object):[/color][/color][/color]
                .... __metaclass__ = set_classvars(n ame='foo', desc='bar', list=[])
                .... name = 'not foo'
                ....[color=blue][color=green][color=darkred]
                >>> C.name, C.desc, C.list[/color][/color][/color]
                ('not foo', 'bar', [])[color=blue][color=green][color=darkred]
                >>> class D(C):[/color][/color][/color]
                .... pass #<<<<<< Use Super's metaclass
                ....[color=blue][color=green][color=darkred]
                >>> D.name, D.desc, D.list, D.list is C.list[/color][/color][/color]
                ('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).class Vars.iteritems( ):
                if name not in dict:
                setattr(cls, name, copy.copy(value ))

                def are(**kwargs):
                return type('', (ClassVars,), {'classVars':kw args})

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

                Comment

                • Steven Bethard

                  #9
                  Re: Using metaclasses to inherit class variables

                  telesphore4@gma il.com wrote:[color=blue]
                  > Oops! This isn't working. As the sequence I'm trying for is....[color=green][color=darkred]
                  >>>> def set_classvars(* *kwargs):[/color][/color]
                  > ... def __metaclass__(n ame, bases, classdict):
                  > ... for name, value in kwargs.iteritem s():
                  > ... if name not in classdict:
                  > ... classdict[name] = value
                  > ... return type(name, bases, classdict)
                  > ... return __metaclass__
                  > ...[color=green][color=darkred]
                  >>>> class C(object):[/color][/color]
                  > ... __metaclass__ = set_classvars(n ame='foo', desc='bar', list=[])
                  > ... name = 'not foo'
                  > ...[color=green][color=darkred]
                  >>>> C.name, C.desc, C.list[/color][/color]
                  > ('not foo', 'bar', [])[color=green][color=darkred]
                  >>>> class D(C):[/color][/color]
                  > ... pass #<<<<<< Use Super's metaclass
                  > ...[color=green][color=darkred]
                  >>>> D.name, D.desc, D.list, D.list is C.list[/color][/color]
                  > ('not foo', 'bar', [], True)[/color]

                  What should the "right" answer be here? Maybe

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

                  or

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

                  or something else?


                  STeVe

                  Comment

                  • telesphore4@gmail.com

                    #10
                    Re: Using metaclasses to inherit class variables

                    Sorry for not being clear.

                    Fresh copies of class vars so the first one is the correct: ('foo',
                    'bar', [], False)
                    [color=blue][color=green][color=darkred]
                    >>> import copy
                    >>>
                    >>> class ClassVars(type) :[/color][/color][/color]
                    .... def __init__(cls, name, bases, dict):
                    .... for name, value in type(cls).class Vars.iteritems( ):
                    .... if name not in dict:
                    .... setattr(cls, name, copy.copy(value ))
                    ....[color=blue][color=green][color=darkred]
                    >>> def are(**kwargs):[/color][/color][/color]
                    .... return type('', (ClassVars,), {'classVars':kw args})
                    ....[color=blue][color=green][color=darkred]
                    >>> class C(object):[/color][/color][/color]
                    .... __metaclass__ = are(name='foo', desc='bar', list=[])
                    .... name = 'not foo' #<<< Changing a copy only owned by this class
                    ....[color=blue][color=green][color=darkred]
                    >>> class D(C):[/color][/color][/color]
                    .... pass
                    ....[color=blue][color=green][color=darkred]
                    >>> C.name, C.desc, C.list[/color][/color][/color]
                    ('not foo', 'bar', []) <<<<<<<<<<<<<<< <<<<<<< Separate copy we changed[color=blue][color=green][color=darkred]
                    >>> D.name, D.desc, D.list, D.list is C.list[/color][/color][/color]
                    ('foo', 'bar', [], False) <<<<<<<<<<<<<<< <<<<< Defaults are not changed

                    Both prints are correct here

                    Thanks for your help btw.
                    t4

                    Comment

                    • Steven Bethard

                      #11
                      Re: Using metaclasses to inherit class variables

                      telesphore4@gma il.com wrote:[color=blue]
                      > Fresh copies of class vars so the first one is the correct: ('foo',
                      > 'bar', [], False)[/color]

                      Ahh, yeah, then you definitely need the copy.copy call.
                      [color=blue][color=green][color=darkred]
                      >>>> import copy
                      >>>>
                      >>>> class ClassVars(type) :[/color][/color]
                      > ... def __init__(cls, name, bases, dict):
                      > ... for name, value in type(cls).class Vars.iteritems( ):
                      > ... if name not in dict:
                      > ... setattr(cls, name, copy.copy(value ))
                      > ...[color=green][color=darkred]
                      >>>> def are(**kwargs):[/color][/color]
                      > ... return type('', (ClassVars,), {'classVars':kw args})
                      > ...[color=green][color=darkred]
                      >>>> class C(object):[/color][/color]
                      > ... __metaclass__ = are(name='foo', desc='bar', list=[])
                      > ... name = 'not foo' #<<< Changing a copy only owned by this class
                      > ...[color=green][color=darkred]
                      >>>> class D(C):[/color][/color]
                      > ... pass
                      > ...[color=green][color=darkred]
                      >>>> C.name, C.desc, C.list[/color][/color]
                      > ('not foo', 'bar', []) <<<<<<<<<<<<<<< <<<<<<< Separate copy we changed[color=green][color=darkred]
                      >>>> D.name, D.desc, D.list, D.list is C.list[/color][/color]
                      > ('foo', 'bar', [], False) <<<<<<<<<<<<<<< <<<<< Defaults are not changed[/color]

                      Hmm... I don't think I can get away with just a function for
                      __metaclass__ in this situation since D doesn't invoke it:
                      [color=blue][color=green][color=darkred]
                      >>> class C(object):[/color][/color][/color]
                      .... def __metaclass__(* args):
                      .... print '__metaclass__% r' % (args,)
                      .... return type(*args)
                      ....
                      __metaclass__(' C', (<type 'object'>,), {'__module__': '__main__',
                      '__metaclass__' : <function __metaclass__ at 0x00FA89F0>})[color=blue][color=green][color=darkred]
                      >>> class D(C):[/color][/color][/color]
                      .... pass
                      ....[color=blue][color=green][color=darkred]
                      >>>[/color][/color][/color]

                      I'm not sure why this is. Anyone else out there know? D *does* invoke
                      __metaclass__ if it's a subclass of type:
                      [color=blue][color=green][color=darkred]
                      >>> class C(object):[/color][/color][/color]
                      .... class __metaclass__(t ype):
                      .... def __init__(*args) :
                      .... print '__init__%r' % (args,)
                      ....
                      __init__(<class '__main__.C'>, 'C', (<type 'object'>,), {'__module__':
                      '__main__', '__metaclass__' : <class '__main__.__met aclass__'>})[color=blue][color=green][color=darkred]
                      >>> class D(C):[/color][/color][/color]
                      .... pass
                      ....
                      __init__(<class '__main__.D'>, 'D', (<class '__main__.C'>,) ,
                      {'__module__': '__main__'})[color=blue][color=green][color=darkred]
                      >>>[/color][/color][/color]

                      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:
                      [color=blue][color=green][color=darkred]
                      >>> def set_classvars(* *kwargs):[/color][/color][/color]
                      .... class __metaclass__(t ype):
                      .... def __init__(cls, name, bases, classdict):
                      .... for name, value in kwargs.iteritem s():
                      .... if name not in classdict:
                      .... setattr(cls, name, copy.copy(value ))
                      .... return __metaclass__
                      ....[color=blue][color=green][color=darkred]
                      >>> class C(object):[/color][/color][/color]
                      .... __metaclass__ = set_classvars(n ame='foo', desc='bar', list=[])
                      .... name = 'not foo'
                      ....[color=blue][color=green][color=darkred]
                      >>> class D(C):[/color][/color][/color]
                      .... pass
                      ....[color=blue][color=green][color=darkred]
                      >>> C.name, C.desc, C.list[/color][/color][/color]
                      ('not foo', 'bar', [])[color=blue][color=green][color=darkred]
                      >>> D.name, D.desc, D.list, D.list is C.list[/color][/color][/color]
                      ('foo', 'bar', [], False)

                      [color=blue]
                      > Thanks for your help btw.[/color]

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

                      STeVe

                      Comment

                      Working...