Default attribute values pattern

G

George Sakkis

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
 
D

David Tweet

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)
 
A

Arnaud Delobelle

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)
 
D

David Tweet

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

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)
 
B

Bruno Desthuilliers

David Tweet a écrit :
( said:
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)
 
C

cokofreedom

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

Arnaud Delobelle

"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.
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)
 
B

Bruno Desthuilliers

(e-mail address removed) a écrit :
"pop() raises a KeyError when no default value is given and the key is
not found."

Then use it with a default value !-)
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.
 
G

George Sakkis

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
 

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,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top