Proposal: default __init__

Alex Martelli aleaxit at yahoo.com
Tue Nov 14 06:10:37 EST 2000


"Andrew Dalke" <dalke at acm.org> wrote in message
news:8uqoo1$rdq$3 at slb0.atl.mindspring.net...
    [snip]
Summarizing: Andrew wants to special-case __init__ in some future
not-needing-compatibility-with-today's Python (Py3k):

> I'm not suggesting that the getattr rules be changed, only that each
> base class always define an __init__.

for his own definition of 'base class', which is not "a class which
is derived from" but "a class which derives from no other":

> Suppose instead that base classes (true base classes, not derived
> ones like 'C' or 'D' in this example) always had a do nothing __init__.


Basically, the claimed advantage is to let any derived class
explicitly code its own __init__ as something like (assuming
no-arguments inits):

class X( <list of base classes of X> ):
    def __init__(self):
        # special stuff specific to class X, then:
        for base in X.__bases__:
            base.__init__(self)

rather than something like:

class X( <list of base classes of X> ):
    def __init__(self):
        # special stuff specific to class X, then:
        for base in X.__bases__:
            if hasattr(base,'__init__'):
                base.__init__(self)


But wouldn't it be even simpler to be able to code, instead:

class X( <list of base classes of X> ):
    def __init__(self):
        # special stuff specific to class X, then:
        initbases(self, X.__bases__)

...?


The point: you can do that *TODAY* -- without waiting for the
year 3000 -- if you like this idiom!  Just code ONCE something
like (e.g. in your sitecustomize.py, or wherever):

def initbases(obj, base_classes):
    for base in base_classes:
        if hasattr(base,'__init__'):
            base.__init__(obj)

stick it into your __builtins__, and use it to your heart's
contents.

Is it worth introducing an incompatible change, that treats
one class attribute differently from ANY other (and just in
"classes which don't inherit from any other class"), just to
be able to optimize this ONE function (by 1 LOC!) down to...:

def initbases(obj, base_classes):
    for base in base_classes:
        base.__init__(obj)

???


I claim the special-casing is unwarranted, and the simplicity
of having just one rule apply uniformly to all class attributes
is *by far* preferable.


On a slightly-related tack, I would even eschew any fancy
metaprogramming tricks here to try to let initbases determine
for itself which baseclasses it should be calling __init__ on.

By having the _caller_ of initbases fully control that, we
get maximum simplicity and power (the caller will only pass
baseclasses that it's not already initializing itself with
explicit arguments it knows about, for example).  Besides,
I don't know how (in initbases) to find out about the class
of the __init__ method that called it (as opposed to the
class of the *object* that called it) -- I can get to the
stackframe, and from it to the codeobject, but I don't know
how to 'navigate' from the codeobject to the method-object,
and hence to the class-object (to find out its bases, &c).

(Maybe _these_ slight occasional difficulties in Python's
rich and powerful reflection/introspection mechanisms might
be worth enhancement requests -- getting to the class of
the current method [as opposed to the class of 'self'] is,
it seems to me, not as easy as other dynamic determinations
and may prompt hardcoding of class-structure; OTOH, maybe
it's not a good idea to further encourage any such dirty
metaprogramming tricks!-).


That C++ treats constructors (and destructors, too) as
"deeply magickal" entities, with lots of compiler built-in
support and subtle, complex rules (particularly when
virtual bases are involved... and Python bases tend to
approximate the behavior of _virtual_ C++ bases, though
not in their construction-rules...!), doesn't seem to
me to be a powerful argument for doing anything like that
in Python.  Yes, you do get all sorts of constructs "built
and defined implicitly for you" by a C++ compiler --
including default AND copy costructors, destructor,
default assignment operator, with all sort of 'nice'
rules about what the default-constructed thingies do
regarding sub-objects (attributes, bases virtual and
non-virtual), in what cases they are _not_ generated,
what to do when you DON'T want one (sometimes you need
to _declare_ one of the magickal thingies as private
and then avoid _defining_ it...)...

...one key reason Python makes me (me, personally, but
I suspect this generalizes!) far more productive than C++
(or Java) is that it gets me out of this whole morass of
"convenient" (sic) automatisms and onto a clear, sunny
plateau of simplicity and power.  Metaprogramming (even
though I sometimes find a limitation with it -- or with
my knowledge of it) lets me build and use my own idioms
when I want to (and even more so, I'm sure, when using
metaclasses -- if & when I finally get my mind around
them for good!-) [I'll admit that templates, particularly
with Coplien's "pattern without a name", give me a little
bit of that in C++, too; but, what they can't do is _turn
off_ all the built-in automatisms that sometimes trip
me up...:-)].

The natural tendency of any programming environment is
towards 'richness' and away from simplicity.  Which is
why I tend to think it important to fly simplicity's
banner, trying (for what little I can do in the matter!)
to oppose the 'natural tide' towards "just a little
mint"...:-).

Even as 'small' a change "for convenience" as treating
the getattr/hasattr of '__init__' differently from that
of any other attribute is a step in the wrong direction,
IMHO.


A side issue: is it ever good, correct code to rely
on a certain base-class NOT having an __init__?  I
claim it's ok, in the context of a certain mix-in
style of programming, that Python supports quite well.

For example, say I'm using a framework which gives
me (for use as baseclasses) certain classes A, B, C,
D, ..., which all have in common (e.g, inherit it)
a certain implementation of method spam.  I need to
inherit my own versions A1, B1, C1, D1, ..., which
basically behave like A, B, ..., but also all need
to override method spam in the same, identical way.

Then, for a certain specific subsystem, I'll further
derive A2, B2, ..., from A1, B1, ..., with further
additions &tc &tc.

Should I then code, repetitively...:

class A1(A):
    def spam(self):
        print "no spam here, please!"
...
class D1(D):
    def spam(self):
        print "no spam here, please!"

and then:

class A2(A1):
    def __init__(self):
        self.clic='cloc'
        initbases(self, A2.__bases__)
    # other specific stuff
...
class D2(D1):
    def __init__(self):
        self.cluc='clec'
        initbases(self, D2.__bases__)
    # other specific stuff

?

This is one possibility, sure, but Python
offers other ways to build classes A1...D1,
avoiding the need to repetitiously override
method spam in each case.  Hey, this is one
of the things mix-in classes perform SO well
and handily!  Just do:

class NoSpam:
    def spam(self):
        print "no spam here, please!"

and then:

class A1(NoSpam,A):
    pass

etc, etc; the inheritance of A2 from A1,
etc, doesn't change a whit with this new,
handier implementation of A1, ..., D1.

It's just splendid, that classes A2, ...,
*don't care* about HOW their bases A1, ...,
manage to accomplish the task "override
method spam, and apart from that just
be exactly like A, B, C...".


Isn't this just wonderful?  By inheriting
from NoSpam *first*, class A1 automatically
"overrides" all the methods (and, _those
ONLY_) which the mixin class defines; all
_other_ methods come from the "substantial
base class" that comes later in the base
classes list.  Of course, I'm the author
of the mix-in class, so I'm in full control
of what gets overridden and how.  Bliss!

I know exactly what's going on, because
Python does *NOT* "implicitly build" ANY
method for the classes; so, A1 and friends,
in perfectly classical 'mix-in user' style,
need do NOTHING _except_ bring the mix-in
together (in the right order) with the
"substantial base".


Python *very specifically* enables this
mix-in style, by carefully specifying the
order in which multiple baseclasses are
searched for an attribute.  There is a
sharp difference with C++, which has a very
different "attitude" to methods being defined
in more than one baseclass, more prudential
(it doesn't make a difference _to method
resolution_ in what order baseclasses are
listed in C++ -- though it makes a *huge*
difference at construction/destruction times)
[and IMHO less productive, but I can see how
__in the context of other complex C++ rules__
Stroustrup just didn't want to make order-of-
baseclasses significant for THIS, too!].


Is this "bad" Python?  Puh-leeze.  Just
because one can't use a certain object-oriented
style in C++ (or, in Smalltalk: no multiple
inheritance means no mix-ins!) doesn't take
anything away from its value _in a language
that enables it, as Python does so well_!

Incidentally, in C++ I'd use the usual
Coplien pattern-without-a-name trick...:

template <class Base>
class NoSpam: public Base {
    public:
        void spam() {
            cout << "No spam please!\n";
        }
};

and then:

class A1: public NoSpam<A> {
};

etc -- getting my "mixin effect" back through
the _dominance_ rule.  Python doesn't normally
have 'parameterized classes' similar to C++'s
templates -- and mostly, it doesn't need them,
thanks to its simplicity; if we lose the
simplicity, I guess we'd better all get
familiar with the new.classobj function...:-).


I claim that mix-in is excellent Python style,
that it's absolutely no breach of good coding
rules to use it and rely on it, and that it will
be a real pity if Py3k [a] breaks existing code
relying on it, and [b] makes all use of mixins
substantially less convenient by forcing mixin-
using classes to code __init__ methods which
mimick today Python's intrinsic rules.

Maybe, if Python starts by default sticking a
do-nothing __init__ into all classes with no
bases, I should have a way to turn THAT off in
my classes that do NOT want an __init__ -- more
complexity, yes, but at least it would afford
a chance to repair mixin-using code by tweaking
the mixin-class only, without having to tamper
with the more-numerous classes that use (inherit
from) the mixin.

I'd *FAR* rather keep coding the 'if hasattr'
guard, in that one-and-only initbases function,
mind you, rather than have all of this complex
stuff for the sake of saving that 1 LOC in just
one place.  But if Python does have, by Fate's
command, to slide towards complexity, not to
say complication, consistent complexification
may be better than ad-hoc 'convenience quests'...


Alex






More information about the Python-list mailing list