"inherited" keyword in Python?

Alex Martelli aleaxit at yahoo.com
Tue Dec 5 05:11:13 EST 2000


"Paul Foley" <see at below> wrote in message
news:m2elznipbl.fsf at mycroft.actrix.gen.nz...
    [snip]
> > Metaprogramming magick was deemed inappropriate here (as I
> > read the group consensus).  One *might* have a global function
> > called super(), which tried to gauge the appropriate class
> > object to use (in single-inheritance cases, only, of course),
>
> Why "of course"?  If you can find out the name of the method which is
> calling super() (which can be done, but I don't remember how), and the

Name of calling-function is easy (raise & catch an exception,
it's in the stackframe you get this way).

> class from which it's being called (I can think of ways to avoid
> needing this, if it can't be done),

This one is hellish.  I can't figure out ANY halfway clean way
that doesn't require either mucking with the bytecode, or going
through all the dictionaries in an object's baseclasses until
an entry with the same codeobject as the one previously identified
is found -- slow as well as dirty.

> it shouldn't be very hard to make
> a super() function that works with multiple inheritance.  Use
> cpl(class) [see below] to get a properly ordered list of base classes,
> and search for the first one that implements the named method.  Call
> that.  [You need to know which class the current method belongs to in
> order to get the right cpl, otherwise it'll infloop...but that isn't
> obtainable, you could maybe have super() maintain a stack to tell it
> where it's at, or something...]

I think your cpl function is much more complicated than what's needed
to reproduce Python's very simple rule for walking the DAG of base
classes (it's just depth-first, left-first), even assuming it does
end up reproducing it perfectly.  But that's not the main point.

A more interesting theoretical question is how you propose to solve
the case in which you can't tell which class the current method
'belongs' to (also remember a given function-object may be set as
a method of N classes, for any N, with any set of names -0- but,
even forgetting that for the moment).  E.g., a very simple case:

class A:
    def foo(self):
        print super()

class B(A):
    def foo(self):
        print super()
        A.foo(self)

class C(A):
    def foo(self):
        print super()
        A.foo(self)

class D(B,C):
    def foo(self):
        print super()
        B.foo(self)
        C.foo(self)

Here, the various 'foo's are mercifully separate codeobjects so
IN THEORY this is solvable (e.g., by rooting in the dictionaries
of each ancestor-class of self looking for the codeobject as a
value) -- of course if, say, C just did

class C(A):
    foo = B.foo

or

def bar(something):
    print super()
    A.foo(something)
class C(A):
    foo = bar

we might have a harder time.  But unless the class 'owning'
(hoping there is some 'ownership'...) the calling-method IS
identified (be it by hook or by crook), how does 'super()'
does its job, even theoretically...?


On a *pragmatic* level, I think this kind of "deep and dark
metaprogramming tricks" are most often best eschewed for
practical use.  Great for writing debuggers, class-browsers,
and all sorts of infrastructure, sure, but simpler and cleaner
ways will generally prove more understandable and thus more
maintainable.  "Do the simplest thing that could possibly
work" rules... and the "simplest thing" _rarely_ (not 'never')
involves such reflection/introspection.  It's too easy to get
lost on a misguided quest to provide 'convenience', and
actually end up obfuscating and obscuring things for no
real good purpose.

'super()' in a multiple-inheritance context is a good example.
There is no single superclass that will do for all attributes
one might wish to access, so
    super().foo()
and
    super().bar()
just won't work.  'supercall' might work (without needing
any real complication -- just a loop of getattr over the
__bases__, given Python's quite-simple semantics here), IF
you tell it which class it must find the 'supers' of -- the
simplest and most effective way to do that is to have a
convention that each class starts out by setting some
specific variable to itself:

class Foo:
    __thisclass=Foo
    # etc

class Bar(Foo):
    __thisclass=Bar
    # etc

and 'supercall' is always (but always!-) called in the form

    supercall(self, __thisclass, methodname, *args, **kwds)

which reduces it to something decently-simple, like:

def supercall(_obj, _cls, _metnam, *args, **kwds):
    for _bas in _cls.__bases__:
        _metobj = getattr(_bas, _metnam, None)
        if not _metobj is None:
            return _metobj(_obj, *args, **kwds)
    else:
        raise NameError, "No superclass of %s has method %s" % (
            _cls, _metnam)


But, how often is it going to be satisfactory to call 'just
the one' (of several) inherited-method of a certain name, in
a multiple-inheritance context?  Just as often we may want
to call _all_ methods of a certain name (__init__ and __del__
are frequent candidates, and indeed the last time this was
discussed, a couple weeks ago I think, __init__ was the
focus of the discussion) -- and shall we want to pass each
of them the same arguments...?  Complications flood us
again pretty soon, and the harked-for "convenience" remains
elusive.

To quote an old local saying, I think that, in most cases,
going for sophisticated metaprogramming tricks in the
attempt to provide "convenience" is "quenching your thirst
with ham".  (P.S.: Bolognese ham tends to be pretty salty:-).


Alex






More information about the Python-list mailing list