[Doc-SIG] suggestions for a PEP

Guido van Rossum guido@digicool.com
Sun, 18 Mar 2001 12:12:15 -0500


> 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 ;^>

Sure!

> 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.)

Hm, this is entering a whole realm of stuff where Python isn't very
helpful.  Folks who know Eiffel have suggested inheriting pre- and
post-conditions.  Others, coming from C++, have suggested automatic
calling of base class constructors in derived constructors.  Now you
suggest inheriting docstrings.

Maybe there's something there, but it's definitely Python 3000
material...

> 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,

Yes, but Python doesn't really try to enforce that (or even help you).

> so having the same doc as the method
> being over-ridde should be `usual'.

Unclear.  It depends a lot on what's in the docstring.  I have written
lots of docstrings that would be really misleading if they were
inherited!

> 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.

f is still an ordinary docstring even after the class definition is
complete -- but if you access it in the conventional way (as E.f) it
is munged on the way out.  E.__dict__['f'] also gives the function.
(Not that I encourage using this!)

> 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 ?
> 
> 	Eddy.

For this one, I prefer to use the time machine in the opposite
direction.  Let's move this set of ideas to a new design for Py3K.

(PS, I regret that this is off-topic for doc-sig -- it should really
be moved to python-dev.)

--Guido van Rossum (home page: http://www.python.org/~guido/)