__autoinit__ (Was: Proposal: reducing self.x=x; self.y=y; self.z=z boilerplate code)

Ralf W. Grosse-Kunstleve rwgk at yahoo.com
Sun Jul 10 02:15:48 CEST 2005

--- Kay Schluehr <kay.schluehr at gmx.net> wrote:
> Ralf, if you want to modify the class instantiation behaviour you

I don't. I simply want to give the user a choice:

    __init__(self, ...) # same as always (no modification)
    __autoinit__(self, ...) # self.x=x job done automatically and efficiently

If someone overrides both __init__ and __autoinit__, the latter will
have no effect (unless called from the user's __init__).

> should have a look on metaclasses. That's what they are for. It is not
> a particular good idea to integrate a new method into the object base
> class for each accidental idea and write a PEP for it.

The flurry of suggestions indicates that many people have tried to find
some way to reduce the boilerplate burden, even though everybody seems
keen to point out that it is not that big a deal. It is not that big a
deal indeed. But it is bigger than the problem solved by, e.g.,
enumerate, and I believe it can be solved with a comparable effort.

> I provide you an example


> which is actually your use case.

Close, but not quite.

> It doesn't
> change the class hierarchy : the metaclass semantics is not "is a" as
> for inheritance but "customizes" as one would expect also for
> decorators.
> class autoattr(type):
>     '''
>     The autoattr metaclass is used to extract auto_xxx parameters from
>     the argument-tuple or the keyword arguments of an object
> constructor __init__
>     and create object attributes mit name xxx and the value of auto_xxx
> passed
>     into __init__
>     '''
>     def __init__(cls,name, bases, dct):
>         super(autoattr,cls).__init__(name,bases,dct)
>         old_init = cls.__init__
>         defaults = cls.__init__.im_func.func_defaults
>         varnames = cls.__init__.im_func.func_code.co_varnames[1:]
>         def new_init(self,*args,**kwd):
>             for var,default in zip(varnames[-len(defaults):],defaults):

This doesn't work in general. You are missing "if (defaults is not None):"
as a condition for entering the loop.

>                 if var.startswith("auto_"):
>                     self.__dict__[var[5:]] = default
>             for var,arg in zip(varnames,args):
>                 if var.startswith("auto_"):
>                     self.__dict__[var[5:]] = arg
>             for (key,val) in kwd.items():
>                 if key.startswith("auto_"):
>                     self.__dict__[key[5:]] = val
>             old_init(self,*args,**kwd)
>         cls.__init__ = new_init

I couldn't easily time it because your approach changes the user
interface (what are implementation details becomes exposed as "auto_"),
but the code above is sure to introduce a serious runtime penalty.

I stripped your code down to the essence. See attachment.
For the user your approach then becomes:

  class grouping:
    __metaclass__ = autoattr
    def __init__(self, x, y, z):

My __autoinit__ suggestion would result in (assuming object supports
this by default):

  class grouping(object):
    def __autoinit__(self, x, y, z):

I think that's far more intuitive.

The timings are:

  overhead: 0.00
  plain_grouping: 0.28
  update_grouping: 0.45
  plain_adopt_grouping: 0.69
  autoinit_grouping: 1.15
  autoattr_grouping: 1.01

Your approach wins out time-wise, mainly since you don't have to repeat
the expensive '*.im_func.func_code.co_varnames[1:]" 3 or 4 * getattr +
slicing operation for each object instantiation. However, I am hopeful
that a built-in C implementation could eliminate most if not all
runtime penalties compared to the hand-coded "plain_grouping"


Sell on Yahoo! Auctions – no fees. Bid on great items.  

More information about the Python-list mailing list