"inherited" keyword in Python?

Alex Martelli aleaxit at yahoo.com
Wed Dec 6 06:03:50 EST 2000


"Paul Foley" <see at below> wrote in message
news:m23dg2ibes.fsf at mycroft.actrix.gen.nz...
> On Tue, 5 Dec 2000 22:11:08 +0100, Alex Martelli wrote:
>
> > To clarify: Python's rule (as applied by, e.g., getattr) is anything
> > but broken, but it doesn't easily let you return (for a given class X
>
> Python's rule works for Python, sure -- but Python doesn't provide any
> way to call a superclass method without knowing exactly what you want
> to call (else all this would be unnecessary!)

It's not a built-in feature, but we have just proven it can be
built up (the main thing missing being a way to find out 'in
what class am I right now' -- that one needs to be made explicit).


> > that inherits from A, B, C, ...) a single class Z that will ensure that
> > getattr(Z,whatever) is the same as getattr(X,whatever) for any
> > whatever that is not a key in X.__dict__.
>
> I'm not sure that's all that's needed, though.

What else would be needed?


> >> that won't work.  I thought super() was just supposed to call the
> >> appropriate same-named method of a parent class.  Anything else can be
> >> done through the usual self.whatever, surely?
>
> > Not really.  To translate literally a Java class which has methods X
> > (which calls the base's Y) and Y (which calls the base's X method),
> > I'd need a Python's super() to be like Java's super (returning a class
>
> I've never bothered with Java.  How can you write a method X which
> calls the base's Y without explicitly calling the base's Y?  And if
> you have to do it explicitly, that'll work in Python, too.

    super.Y(anarg, another)

will call the Y method of the (single) superclass, just as surely as

    self.Y(anarg, another)

will call it on the (current) class.  Both of these calls work in Java
(and the different-syntax but same-semantic equivalents in Smalltalk,
I think) from ANY method X of a certain object 'self'.

This is easy in a single-inheritance language such as Smalltalk or
Java (or C#, or VB7, ...), of course.


> > object reference) and not like what I name 'supercall'.  self.X and
> > self.Y just won't do for this.
>
> It seems to me, what you said above about Java notwithstanding, that
> the only time it's OK to call X where X is a method in a base class,
> and where self.__class__ also has a method named X (so that self.X()
> will do the wrong thing), is from inside self.X.

No way!  The set of implementations of a given class's methods
makes up ONE tightly-woven net of mutual dependencies.  It's
perfectly all right _within such a set_ for a method X of class
K to know everything about how another method Y of K is implemented,
and depend in its implementation on K's Y's implementation details,
for example to bypass an override that K's Y implementation
performs on some inherited-Y.

For example, consider two methods, a somewhat typical getter/setter
pair -- in C++ notation:

    virtual AnObj& getObj() throws(MissingObj);
    virtual AnObj& setObj(AnObj&) throws(ObjSettingFailed);

Specifically: in the baseclass B, getObj() is defined to return
(a reference to) the AnObj that was last set, raising an exception
if no setting was ever performed; setObj() is defined to set a
new AnObj (raising an exception if that somehow fails) and return
a reference to the same to allow chaining, typical use:

    myContainer.setObj(LocateObj()).tweakDetails(anoption, another);

meant to have the same semantics as:

    myContainer.setObj(LocateObj());
    myContainer.getObj().tweakDetails(anoption, another);

but with tighter expression.  All right so far?


Now, in derived class K, we need to tweak the semantics a little
bit: if NO setting was ever performed, we want getObj, before it
fails, to make a last-ditch attempt to set a suitable AnObj by
other means; similar means to be used in setObj if the setting
as requested failed.  The natural way to express this, switching
to Python, would be something like:

    def setObj(self, anObj):
        try: return B.setObj(self, anObj)
        except ObjSettingFailed:
            return B.setObj(self, self.LastDitchAttempt())

    def getObj(self):
        try: return B.getObj(self)
        except MissingObj:
            try: return B.setObj(self, self.LastDitchAttempt())
            except ObjSettingFailed:
                raise MissingObj

If in this getObj we *HAD* to use the setObj *from this
very class*, rather than the superclass's, we would get
subtly wrong semantics: *TWO* retries (two calls to
LastDitchAttempt) if the first setting fails.  We need
to call the *superclass*'s setObj from within the
*derived class*'s getObj, to get maximum simplicity.

If each method was only allowed to call-on-superclass
*the same method as itself*, we'd have to complicate
things with TWO levels of inheritance -- one to override
just getObj, then another one to override just setObj --
and in slightly harder cases, where the need to call-
superclass-implementation-of-other-method is mutual,
we'd be slightly out of luck.

Actually, this first sketch at a solution could be
usefully refactored (IMHO) to:

    def __lastDitch(self, ExceptionToTranslate=None):
        try: return B.setObj(self, self.LastDitchAttempt())
        except ExceptionToTranslate: raise MissingObj

    def setObj(self, anObj):
        try: return B.setObj(self, anObj)
        except ObjSettingFailed:
            return self.__lastDitch()

    def getObj(self):
        try: return B.getObj(self)
        except MissingObj:
            return self.__lastDitch(ObjSettingFailed)

but, even here, it's crucial for __lastDitch to be able
to call the superclass's implementation of setObj; indeed,
here we might well get a _loop_ if method __lastDitch was
only allowed to call self.setObj!

Allowing such useful elementary refactorings is another
important motivation for having ANY method of an object
call the superclass's implementation of that method, by
the way.  Suppose that 'by nature' your only calls to
super.Foo where from inside self.Foo; still, some of
those calls can occur within a repeated fragment of code,
and of course you might well want to 'factor them out'
into a little method of their own.  I can hardly stand
a sequence of 3-4 statements repeated 3 or 4 times when
it clearly should be a function/method... having copies
of it strewn around can well make maintenance needlessly
more difficult (when you miss one of the repetitions and
thus fail to tweak it -- if you only have one 'repetition',
called from wherever it's needed, no risk of that then:-).


> >> > 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
> >>
> >> It isn't (at least, not to me), which is why I avoided doing it that
>
> > But you did do that -- you call just one of the several 'inherited'
> > implementations of the method, in the code below.
>
> Any one call to super() only calls one method, naturally.  But if
> everyone calls super() _all_ the methods eventually get called.  Under
> "Python rules", only the methods in D, B and A can ever run -- the one
> in C never does, unless called explicitly with C.foo(self) from D; and
> in that case, it's called out of order -- you either get D, B, A, C or
> D, C, B, A, or, more likely D, B, A, C, A or D, C, A, B, A, with A.foo
> getting called twice, since both B and C inherit A.  This sucks.  I
> get D, B, C, A, which is the right order (B.foo and C.foo are both
> called before their common superclass's A.foo, B.foo is called before
> C.foo since B is to the left of C in D.__bases__) and A.foo is only
> called once.

Sorry, I really don't get this.  B and C don't inherit from each
other either way, so why should a call to super(B) inside class B
ever invoke anything within C, or vice versa?  Remember we said
we'd provide the classobject as an explicit argument to super --
method foo in class B will thus say 'super(B)'.  How do you get
from this to C...?

D-B-A-C is not "out of order" -- it is THE order (depth-first,
left-first) that attributes are looked up in Python.  Yeah, I've
gathered that you dislike it, but, personally, I find it easy
to explain and understand, easily predictable, and simple.


> >> way.  Having figured out how to get the frame info, here's an
> >> implementation.  No need to pass in the object and method name:
>
> > I'd still want to pass them, at least optionally, to handle the
> > above case where I want to call a _different_ inherited method.
>
> I'm working on the assumption that if you want to do that, you're
> doing something wrong.

I've provided one example where this is not so.  I can provide
a zillion more, for example through the refactoring idea.  I
believe your assumption is just plain wrong.


> >> If you just call super(__thisclass) with no args other than the class,
> >> it passes on the args the calling method got; or you can specify other
> >> args explicitly [yes, I know this prevents you calling with no args if
> >> super's caller has args]
>
> > As, once again, convenience gets in the way of simplicity and
>
> Maybe.  This is an attempt to copy the CALL-NEXT-METHOD functionality
> of CLOS; in CLOS, all the methods will have similar argument lists,
> because they're all part of one generic function, not independent
> members of various classes.  Just chop out the 5 lines in the middle
> that copy the caller's arglist if you think it's better not to do
> this.

CLOS (or Dylan) generic-function/specific-methods setting has lots
of interest, but trying to shoehorn Python into that mold is, IMHO,
an unfruitful attempt.


> > generality.  Oh well:-).  "Tantum convenientia potuit suadere
> > malorum", as Lucretius might write if he lived today!-)
>
> [Am I supposed to be one of them?  And shouldn't "malorum" be dative?]

I'm definitely not accusing you to be 'an evil'!-)  I'm just
jokingly paraphrasing well-known verse 101 of Lucretius' "De Rerum
Naturae", cfr e.g.:
http://www.informalmusic.com/DRN/
(and, yes, I assure you it's correct in the genitive case here:-).
[I had no particular reason to pick Lucretius, but, on reflection,
I think one _could_ make a case for simplicity of programming in
the Epicurean mold.  "Catastematic Python", here we come...!-).


Alex






More information about the Python-list mailing list