[Doc-SIG] suggestions for a PEP

Edward Welbourne Edward Welbourne <eddy@chaos.org.uk>
Mon, 19 Mar 2001 20:30:31 +0000 (GMT)


Edward said:
> I tend to agree; but it seems from Guido's mail like this isn't 
> anything that's likely to happen soon.  So I propose the following:
a fix in the tools.  Perfectly sensible.

>  1. For now, do *not* recommend that people use::
>
>        f.__doc__ = parent.f.__doc__
s/For now, d/D/

Mayhap tell folk this is something they *can do* if they want to
duplicate a docstring, but folk should never need to do it this way ...

>  2. For now, recommend that *tools* inherit documentation for a
>     method if f.__doc__ == None, and don't inherit if 
>     f.__doc__ = '' or any other string.

I know I'm about to vary my tune but ... someone else has been talking
persuasively out-of-band.  Rather than borrowing the doc directly off
the parent ...

If f.__doc__ is None, it would make sense to provide a `default
docstring' comprising a `See also' section (with `also' omitted for
obvious reasons) referencing the corresponding methods in those of our
bases that provide the given method.

Optionally, if these methods are themselves using the default, shortcut
to the ancestors which *do* provide something useful, though this would
be extra work the user could save you by following a few links.
However, if you do it this way, the correct rules for when Z.f's default
doc refers to A.f's doc are:

  * A defines f and A.f.__doc__ is not None
  * there is a chain [A,...,Z] of classes in which each chain[i] is in
    chain[1+i].__bases__ (sorry about the fiddliness here, but Z may
    inherit from A via several chains)
  * no class in the chain (other than A)
    defines .f and provides a non-None .f.__doc__

But I'd argue for the simpler approach: just link to all bases which
provide the given method and leave *their* default pages to provide
chains of links to chase.  This would have the advantage that you'd
record the inheritance tree via which the given method could have been
provided if it hadn't been over-ridden.

Hmm.  Indeed, I'd argue that *every* method's (auto-generated)
documentation has a proper section which refers to *all* methods, on
bases, with the same name, regardless of whether those methods have
docstrings.  One is apt to need to know.

It is, however, a bit fiddly to determine which bases (here I mean
the classes in the tree obtained by chasing __bases__ repeatedly)
are providing the given method without it being overridden on the
way, i.e. given Z, f, those A for which:

  * A defines f
  * there is a chain [A,...,Z] of classes in which each chain[i] is in
    chain[1+i].__bases__, as before
  * no class in the chain (other than A) defines .f

The test for `C defines .f' is: 'f' in dir(C), by the way.
For each base B of a class C to be checked:
   * if #B.f# raises AttributeError, we can ignore B
   * else if #'f' in dir(B)# [and B.f.__doc__ is not None], B.f is interesting
   * else we need to check B

where the [and ... None] clause is only involved if you want to do the
short-cut.  Hmm.  Maybe not that fiddly.  Code follows (for both
alternatives, nearly identical).  It isn't noticably harder to do the
job right going straight to the ones with docs than going to the ones
with the method but no doc; however, I'm inclined to argue for going via
all defined intermediaries, even without docs.

	Eddy.

def baseswithmeth(meth, klaz):
    todo, result, seen = [ klaz ], [], []
    while todo:
        c, todo = todo[0], todo[1:]
        seen.append(c)
        try: getattr(c, meth).__doc__
        # the .__doc__ filters out classes with a non-method called meth
        except AttributeError: pass
        else:
            if meth in dir(c):
                result.append(c)
            else:
                for base in c.__bases__:
                    if base not in todo and base not in seen:
                        todo.append(base)
    return result

def baseswithmethdoc(meth, klaz):
    todo, result, seen = [ klaz ], [], []
    while todo:
        c, todo = todo[0], todo[1:]
        seen.append(c)
        try: doc = getattr(c, meth).__doc__
        except AttributeError: pass
        else:
            if doc is not None and meth in dir(c):
                result.append(c)
            else:
                for base in c.__bases__:
                    if base not in todo and base not in seen:
                        todo.append(base)
    return result

# Now do you see what Tibs meant about vertical space ?
# He maintains code I left behind when I changed jobs ...