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