[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

Default attribute values pattern

George Sakkis

1/19/2008 6:14:00 PM

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
8 Answers

davidtweet

1/19/2008 11:02:00 PM

0

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 <george.sakkis@gmail.com> 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/p...
>



--
-David

Arnaud Delobelle

1/20/2008 1:01:00 AM

0

On Jan 19, 11:02 pm, "David Tweet" <davidtw...@gmail.com> 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

davidtweet

1/20/2008 2:16:00 AM

0

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

On Jan 19, 2008 5:01 PM, Arnaud Delobelle <arnodel@googlemail.com> wrote:
> On Jan 19, 11:02pm, "David Tweet" <davidtw...@gmail.com> 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/p...
>



--
-David

Bruno Desthuilliers

1/21/2008 9:46:00 AM

0

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)

cokofreedom

1/21/2008 10:09:00 AM

0

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

Arnaud Delobelle

1/21/2008 11:33:00 AM

0

On Jan 21, 10:09 am, cokofree...@gmail.com 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

Bruno Desthuilliers

1/21/2008 6:20:00 PM

0

cokofreedom@gmail.com 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.

George Sakkis

1/21/2008 7:30:00 PM

0

On Jan 19, 6:02 pm, "David Tweet" <davidtw...@gmail.com> 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