Default attribute values pattern

Discussion in 'Python' started by George Sakkis, Jan 19, 2008.

  1. A situation that often comes up is having to initialize several
    instance attributes that accept a default value. For a single class,
    passing the default values in __init__ is fine:

    class Base(object):
    def __init__(self, x=0, y=None):
    self.x = x
    self.y = y

    For inherited classes that need to override __init__ while keeping a
    compatible interface though, the default values have to be repeated:

    class Derived(Base):
    def __init__(self, x=0, y=None, z=''):
    super(Derived,self).__init__(self,x,y)
    self.z = ''

    For just two attributes and two classes that's maybe not too bad but
    for many attributes and/or derived classes that may span multiple
    modules, that doesn't seem to scale from a maintenance point of view,
    especially if the defaults change over time.

    A pattern I've been using lately instead is store the defaults in
    class attributes and let __init__ accept keyword arguments:

    class Base(object):

    x = 0
    y = None

    def __init__(self, **kwds):
    setattrs(self, kwds)

    where setattrs is:

    def setattrs(self, attrvals, strict=True):
    if strict:
    # raise AttributeError if some attr doesn't exist already
    for attr in attrvals.iterkeys():
    getattr(self,attr)
    for attr,val in attrvals.iteritems():
    setattr(self, attr, val)

    This way, only the new and overriden default attributes have to
    repeated in derived classes:

    class Derived(Base):

    x = 1
    z = ''

    def __init__(self, **kwds):
    super(Derived,self).__init__(**kwds)
    print 'In Derived.__init__'


    Is this a good way of doing it ? Is there a better pattern ?

    George
    George Sakkis, Jan 19, 2008
    #1
    1. Advertising

  2. George Sakkis

    David Tweet Guest

    Hello,

    Seems to me that setattrs sort of assumes that you want to have all your
    initialization arguments set as attributes of the same name. I would think
    you'd sometimes want to be able to process the extra arguments inside of each
    __init__, assign them to attributes with different names, etc.

    My approach would be to treat each __init__ as a wrapping function, grabbing
    the items it needs out of the keyword dictionary and then calling the next
    __init__. Curious to hear other approaches though:


    def Grab(argdict, key, default):
    """Like argdict.get(key, default), but also deletes key from argdict."""
    if key in argdict:
    retval = argdict["key"]
    del(argdict[key])
    else:
    retval = default
    return retval


    class Base(object):
    def __init__(self, x=0, y=None):
    print "in Base init"
    self.x = x
    self.y = y


    class Derived1(Base):
    def __init__(self, **kwargs):
    print "in Derived1 init"
    self.z = Grab(kwargs, "z", None)
    super(Derived1, self).__init__(**kwargs)


    class Derived2(Derived1):
    def __init__(self, **kwargs):
    print "in Derived2 init"
    self.a = Grab(kwargs, "a", 0)
    self.b = Grab(kwargs, "b", False)
    super(Derived2, self).__init__(**kwargs)
    print self.__dict__


    newthing = Derived2(x=234, y="blah", a=55555)


    On Jan 19, 2008 10:14 AM, George Sakkis <> wrote:
    > A situation that often comes up is having to initialize several
    > instance attributes that accept a default value. For a single class,
    > passing the default values in __init__ is fine:
    >
    > class Base(object):
    > def __init__(self, x=0, y=None):
    > self.x = x
    > self.y = y
    >
    > For inherited classes that need to override __init__ while keeping a
    > compatible interface though, the default values have to be repeated:
    >
    > class Derived(Base):
    > def __init__(self, x=0, y=None, z=''):
    > super(Derived,self).__init__(self,x,y)
    > self.z = ''
    >
    > For just two attributes and two classes that's maybe not too bad but
    > for many attributes and/or derived classes that may span multiple
    > modules, that doesn't seem to scale from a maintenance point of view,
    > especially if the defaults change over time.
    >
    > A pattern I've been using lately instead is store the defaults in
    > class attributes and let __init__ accept keyword arguments:
    >
    > class Base(object):
    >
    > x = 0
    > y = None
    >
    > def __init__(self, **kwds):
    > setattrs(self, kwds)
    >
    > where setattrs is:
    >
    > def setattrs(self, attrvals, strict=True):
    > if strict:
    > # raise AttributeError if some attr doesn't exist already
    > for attr in attrvals.iterkeys():
    > getattr(self,attr)
    > for attr,val in attrvals.iteritems():
    > setattr(self, attr, val)
    >
    > This way, only the new and overriden default attributes have to
    > repeated in derived classes:
    >
    > class Derived(Base):
    >
    > x = 1
    > z = ''
    >
    > def __init__(self, **kwds):
    > super(Derived,self).__init__(**kwds)
    > print 'In Derived.__init__'
    >
    >
    > Is this a good way of doing it ? Is there a better pattern ?
    >
    > George
    > --
    > http://mail.python.org/mailman/listinfo/python-list
    >




    --
    -David
    David Tweet, Jan 19, 2008
    #2
    1. Advertising

  3. On Jan 19, 11:02 pm, "David Tweet" <> wrote:

    > def Grab(argdict, key, default):
    >   """Like argdict.get(key, default), but also deletes key from argdict."""
    >   if key in argdict:
    >     retval = argdict["key"]
    >     del(argdict[key])
    >   else:
    >     retval = default
    >   return retval
    >


    Dictionaries already have a method for this. It's called pop. It's a
    good idea to have a look at methods of builtin types before
    reimplementing the wheel!

    Grab(argdict, key, default) is argdict.pop(key, default)

    --
    Arnaud
    Arnaud Delobelle, Jan 20, 2008
    #3
  4. George Sakkis

    David Tweet Guest

    Ah! nice, thanks, knew I was probably missing something.

    On Jan 19, 2008 5:01 PM, Arnaud Delobelle <> wrote:
    > On Jan 19, 11:02pm, "David Tweet" <> wrote:
    >
    > > def Grab(argdict, key, default):
    > > """Like argdict.get(key, default), but also deletes key from argdict."""
    > > if key in argdict:
    > > retval = argdict["key"]
    > > del(argdict[key])
    > > else:
    > > retval = default
    > > return retval
    > >

    >
    > Dictionaries already have a method for this. It's called pop. It's a
    > good idea to have a look at methods of builtin types before
    > reimplementing the wheel!
    >
    > Grab(argdict, key, default) is argdict.pop(key, default)
    >
    > --
    > Arnaud
    >
    >
    > --
    > http://mail.python.org/mailman/listinfo/python-list
    >




    --
    -David
    David Tweet, Jan 20, 2008
    #4
  5. David Tweet a écrit :
    (<ot>please, don't top-post</ot>)
    >
    > def Grab(argdict, key, default):


    cf pep08 for naming conventions...

    > """Like argdict.get(key, default), but also deletes key from argdict."""
    > if key in argdict:
    > retval = argdict["key"]
    > del(argdict[key])
    > else:
    > retval = default
    > return retval


    def grab(kw, key, default=None):
    try:
    return kw.pop(key)
    except KeyError:
    return default

    (snip)
    Bruno Desthuilliers, Jan 21, 2008
    #5
  6. George Sakkis

    Guest

    > Grab(argdict, key, default) is argdict.pop(key, default)

    "pop() raises a KeyError when no default value is given and the key is
    not found."

    > def grab(kw, key, default=None):
    > try:
    > return kw.pop(key)
    > except KeyError:
    > return default


    So Bruno's technique seems to me to be the correct one as it catches
    the KeyError.
    , Jan 21, 2008
    #6
  7. On Jan 21, 10:09 am, wrote:
    > > Grab(argdict, key, default) is argdict.pop(key, default)

    >
    > "pop() raises a KeyError when no default value is given and the key is
    > not found."


    And it doesn't if a default is provided, which is always the case in
    the uses of Grab(...), so it seems the right tool for the job.

    > > def grab(kw, key, default=None):
    > >    try:
    > >      return kw.pop(key)
    > >    except KeyError:
    > >      return default

    >
    > So Bruno's technique seems to me to be the correct one as it catches
    > the KeyError.


    If you really really want to write a grab function (IMHO pop is good
    enough):

    def grab(kw, key, default=None):
    return kw.pop(key, default)

    --
    Arnaud
    Arnaud Delobelle, Jan 21, 2008
    #7
  8. a écrit :
    >> Grab(argdict, key, default) is argdict.pop(key, default)

    >
    > "pop() raises a KeyError when no default value is given and the key is
    > not found."


    Then use it with a default value !-)

    >> def grab(kw, key, default=None):
    >> try:
    >> return kw.pop(key)
    >> except KeyError:
    >> return default

    >
    > So Bruno's technique seems to me to be the correct one as it catches
    > the KeyError.


    Note that I cancelled the message (too bad, doesn't work everywhere). I
    totally agree with Arnaud on this, and should sometimes re-read the doc
    for stuff I use so often I think I know them.
    Bruno Desthuilliers, Jan 21, 2008
    #8
  9. On Jan 19, 6:02 pm, "David Tweet" <> wrote:
    > Hello,
    >
    > Seems to me that setattrs sort of assumes that you want to have all your
    > initialization arguments set as attributes of the same name. I would think
    > you'd sometimes want to be able to process the extra arguments inside of each
    > __init__, assign them to attributes with different names, etc.
    >
    > My approach would be to treat each __init__ as a wrapping function, grabbing
    > the items it needs out of the keyword dictionary and then calling the next
    > __init__. Curious to hear other approaches though:
    >
    > def Grab(argdict, key, default):
    > """Like argdict.get(key, default), but also deletes key from argdict."""
    > if key in argdict:
    > retval = argdict["key"]
    > del(argdict[key])
    > else:
    > retval = default
    > return retval
    >
    > class Base(object):
    > def __init__(self, x=0, y=None):
    > print "in Base init"
    > self.x = x
    > self.y = y
    >
    > class Derived1(Base):
    > def __init__(self, **kwargs):
    > print "in Derived1 init"
    > self.z = Grab(kwargs, "z", None)
    > super(Derived1, self).__init__(**kwargs)
    >
    > class Derived2(Derived1):
    > def __init__(self, **kwargs):
    > print "in Derived2 init"
    > self.a = Grab(kwargs, "a", 0)
    > self.b = Grab(kwargs, "b", False)
    > super(Derived2, self).__init__(**kwargs)
    > print self.__dict__
    >
    > newthing = Derived2(x=234, y="blah", a=55555)



    The problem with this (apart from being somewhat more verbose and less
    explicit) is that you have to set existing attributes (like x and y)
    *after* the call to super __init__ while new attributes (like z, a and
    b) *before* the call. Mixing it up will either raise a runtime error
    for passing an unknown argument to the parent, or (worse) set the
    parent's default instead of the child's. So for the common attribute
    setting case it involves more error-prone boilerplate code.

    George
    George Sakkis, Jan 21, 2008
    #9
    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. Stefan Siegl
    Replies:
    1
    Views:
    495
    Kermit T Tensmeyer
    Jul 28, 2003
  2. Bob Foster
    Replies:
    0
    Views:
    418
    Bob Foster
    Jul 30, 2003
  3. Peekachu
    Replies:
    1
    Views:
    321
    Oliver Wong
    Jul 10, 2006
  4. Replies:
    3
    Views:
    1,522
    Brandon McCombs
    May 2, 2007
  5. dkmd_nielsen
    Replies:
    6
    Views:
    198
    Joel VanderWerf
    Nov 26, 2008
Loading...

Share This Page