Generic constructors and duplication of internal Python logic
Peter Otten
__peter__ at web.de
Thu Apr 15 04:02:14 EDT 2004
John J. Lee wrote:
> This is one of those things that I can't quite believe I've never
> needed to do before.
>
> I've got a set classes, each of which has a set of attributes that all
> behave very similarly. So, I have a class attribute (Blah.attr_spec
> below), which is used by a mixin class to implement various methods
> that would otherwise be highly repetitious across these classes. I'd
> like to do the same for the constructor, to avoid this kind of
> nonsense:
>
> class Blah(NamesMixin):
> attr_spec = ["foo", "bar", "baz",
> ("optional1", None), ("optional2", None)]
> def __init__(self, foo, bar, baz,
> optional1=None, optional2=None):
> self.foo, self.bar, self.baz = \
> foo, bar, baz
> self.optional1, self.optional2 = \
> optional1, optional2
>
> So, I wrote a mixin class whose __init__ looks at the attr_spec
> attribute, and uses args and kwds (below) to assign attributes in the
> same sort of way as the special-case code above:
>
> class ArgsMixin:
> def __init__(self, *args, **kwds):
> # set attributes based on arguments passed in, as done
> # manually in Blah.__init__, above
> ... lots of logic already present in Python goes here...
>
> That immediately leads to duplication of Python's internal logic: I
> have to check things like:
>
> -are there too many positional arguments?
> -any unexpected keyword arguments?
> -multiple keyword arguments?
> -any duplication between positional and keyword arguments?
>
> etc.
>
> Surely there's some easy way of making use of Python's internal logic
> here? For some reason, I can't see how. Can anybody see a way?
You could use a noop method check_attrs() to define the argspec.
check_attrs() is then called from the mixin's __init__() just to check that
the parameters comply.
import inspect
def make_attrspec(f):
a = inspect.getargspec(f)
names = a[0]
if names[0] in ["self", "cls"]:
# XXX for now relies on naming convention
del names[0]
defaults = a[3]
for i in range(-1, -len(defaults)-1, -1):
names[i] = names[i], defaults[i]
return names
class ArgsMixin:
def __init__(self, *args, **kwds):
self.check_attrs(*args, **kwds)
class Blah(ArgsMixin):
def check_attrs(self, foo, bar, baz, optional1="first",
optional2="second"):
pass
attr_spec = make_attrspec(check_attrs)
print Blah.attr_spec
Blah(1, 2, 3)
Blah(1, 2, optional1="o1", baz=99)
Blah(1, 2)
I'm not sure whether check_attrs() should be a class or static method, so I
made it a standard method for now.
Todo: automagically "normalize" the argument list, e. g. convert
Blah(1, 2, optional1="o1", baz=99) to Blah(1, 2, 99, optional1="o1").
A workaround would be to make them all keyword arguments
kwds.update(dict(zip(Blah.attr_spec, args)))
after the self.check_attrs() call.
Peter
More information about the Python-list
mailing list