[Doc-SIG] suggestions for a PEP

Edward Welbourne Edward Welbourne <eddy@chaos.org.uk>
Sat, 17 Mar 2001 20:47:14 +0000 (GMT)

Thank you Guido.
That makes sense.
Not naughty after all ;^)

> The ExtensionClass module in Zope actually implements class-like
> objects that behave in such a way that at least the first example
> (D.f.foo = 2) changes the f.foo value for class D but not for class C.
> So this is not just theoretical!
see, I knew it'd be the sort of think Jim Fulton plays with.
(OK, `think' was a typo: but it deserves to survive ;^)
Which encourages hope.

First off, I'm going to disparage your second example:
>    class C:
>        def f(self): pass
>        f.foo = 1
>    x = C()
>    x.f.foo = 2      # Would also change value of C.f.foo!

This is strictly another matter: x.f isn't really the same thing as C.f,
it `should' be currie(C.f, x), if you see what I mean, hence a different
object from C.f, so setting its .foo shouldn't affect C.f, even with the
old semantics (even assuming one's allowed to setattr on a bound method,
which sounds *very* dodgy to me).  So I would be rude enough to call
this example a bug in pre-2.1b1 python and ask that it be left out of
the discussion ;^>

But the `D.f is C.f' problem is real.
So, on the one hand, when a method is inherited,

>    class C:
>        def f(self): pass
>        f.foo = 1
>    class D(C):
>        pass
>    D.f.foo = 2       # Would change value of C.f.foo!

but, at the same time, if the derived class *does* over-ride the method,

>>> class A:
... 	def f(self): """doc string of A.f"""
>>> class B(A):
... 	def f(self): return
>>> B.f.__doc__	   # B.f wishes it would `inherit' from A.f, but doesn't

Now any attempt at getting the latter desideratum, without the former
naughtiness, is going to be sophisticated: but can it be done ?  Clearly
there *is* a sophisticated munging phase when B's namespace, having been
built by execution of B's suite, gets transformed and packaged so that
B.f is no longer simply a function; is it possible, at that juncture, to
arrange that it will borrow __doc__ off A.f ?
(Only if B.f lacks __doc__, naturally.)

This would involve some added magic in the type of unbound methods but
that's a pretty magical type *anyway* and it *looks* like it should be
feasible by applying games similar to (though hopefully less complex
than) those used by ExtensionClass.

A derived class' re-implementation `should' behave like that of the
base, or it abrogates its ADT, so having the same doc as the method
being over-ridde should be `usual'.  The exceptions incur a tiny cost -
they have to supply a doc string, which can be empty if they want, but
really they *should* be explaining why they abrogate the ADT anyway,
given that the base class's other methods might exercise the replacement
- and, without this borrowing, the usual case implies gratuitous
duplication - either of the doc string or of the assignment from base.
The latter is really ugly - I should not have to type __doc__ in any
ordinary piece of code; only in introspectors.

Note (for anyone who missed it) that Tony discovered one needn't go via
im_func, as long as one assigns *before* B's namespace gets munged:

>>> class E(A):
... 	def f(self): pass
... 	f.__doc__ = A.f.__doc__
>>> E.f.__doc__
'doc string of A.f'

This is because f is still an ordinary function object (in particular,
it isn't yet E.f; E doesn't yet exist) when the assignment happened.

There is, of course, an obvious problem: multiple inheritance, when more
than one base supplies the over-ridden method.  The solution is, of
course, to borrow off the method on the earliest of the bases to provide
it and leave the implementor to over-ride that by assigning if they
must.  This will be rare enough not to be an issue; and it will simply
work, because either

  * you assign as above, in which case E.f had a __doc__ before munging,
    so borrowing off E's bases' .f didn't get invoked; or

  * you assign after the class body, via im_func, in which case you
    over-ride what the munging has done and it still works.

How's the time machine doing ?
Do methods yet `inherit' __doc__ when not over-ridden ?