Part of a partial solution to SF bugs 463378, 463381, 463383, 463384.
This almost entirely replaces how pydoc pumps out class docs, but only in text mode (like help(whatever) from a Python shell), not in GUI mode. A class C's attrs are now grouped by the class in which they're defined, attrs defined by C first, then inherited attrs grouped by alphabetic order of the defining classes' names. Within each of those groups, the attrs are subgrouped according to whether they're plain methods, class methods, static methods, properties, or data. Note that pydoc never dumped class data attrs before. If a class data attr is implemented via a data descriptor, the data docstring (if any) is also displayed (e.g., file.softspace). Within a subgroup, the attrs are listed alphabetically. This is a friggin' mess, and there are bound to be glitches. Please beat on it and complain! Here are three glitches: 1. __new__ gets classifed as 'data', for some reason. This will have to get fixed in inspect.py, but since the latter is already looking for any clue that something is a method, pydoc will almost certainly not know what to do with it when its classification changes. 2. properties are special-cased to death. Unlike any other kind of function or method, they don't have a __name__ attr, so none of pydoc's usual code can deal with them. Worse, the getter and setter and del'er methods associated with a property don't appear to be discoverable from Python, so there's really nothing I can think of to do here beyond just listing their names. Note that a property can't be given a docstring, either (or at least I've been unable to sneak one in) -- perhaps the property() constructor could take an optional doc argument? 3. In a nested-scopes world, pydoc still doesn't know anything about nesting, so e.g. classes nested in functions are effectively invisible.
This commit is contained in:
parent
894258cebe
commit
28355496c1
93
Lib/pydoc.py
93
Lib/pydoc.py
|
@ -128,6 +128,16 @@ def allmethods(cl):
|
||||||
methods[key] = getattr(cl, key)
|
methods[key] = getattr(cl, key)
|
||||||
return methods
|
return methods
|
||||||
|
|
||||||
|
def _split_class_attrs(attrs, predicate):
|
||||||
|
yes = []
|
||||||
|
no = []
|
||||||
|
for tuple in attrs:
|
||||||
|
if predicate(tuple):
|
||||||
|
yes.append(tuple)
|
||||||
|
else:
|
||||||
|
no.append(tuple)
|
||||||
|
return yes, no
|
||||||
|
|
||||||
# ----------------------------------------------------- module manipulation
|
# ----------------------------------------------------- module manipulation
|
||||||
|
|
||||||
def ispackage(path):
|
def ispackage(path):
|
||||||
|
@ -876,13 +886,80 @@ class TextDoc(Doc):
|
||||||
title = title + '(%s)' % join(parents, ', ')
|
title = title + '(%s)' % join(parents, ', ')
|
||||||
|
|
||||||
doc = getdoc(object)
|
doc = getdoc(object)
|
||||||
contents = doc and doc + '\n'
|
contents = doc and [doc + '\n'] or []
|
||||||
methods = allmethods(object).items()
|
push = contents.append
|
||||||
methods.sort()
|
|
||||||
for key, value in methods:
|
|
||||||
contents = contents + '\n' + self.document(value, key, mod, object)
|
|
||||||
|
|
||||||
if not contents: return title + '\n'
|
def spill(msg, attrs, predicate):
|
||||||
|
ok, attrs = _split_class_attrs(attrs, predicate)
|
||||||
|
if ok:
|
||||||
|
push(msg)
|
||||||
|
for name, kind, homecls, value in ok:
|
||||||
|
push(self.document(getattr(object, name),
|
||||||
|
name, mod, object))
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
# pydoc can't make any reasonable sense of properties on its own,
|
||||||
|
# and it doesn't appear that the getter, setter and del'er methods
|
||||||
|
# are discoverable. For now, just pump out their names.
|
||||||
|
def spillproperties(msg, attrs):
|
||||||
|
ok, attrs = _split_class_attrs(attrs, lambda t: t[1] == 'property')
|
||||||
|
if ok:
|
||||||
|
push(msg)
|
||||||
|
for name, kind, homecls, value in ok:
|
||||||
|
push(name + '\n')
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def spilldata(msg, attrs):
|
||||||
|
ok, attrs = _split_class_attrs(attrs, lambda t: t[1] == 'data')
|
||||||
|
if ok:
|
||||||
|
push(msg)
|
||||||
|
for name, kind, homecls, value in ok:
|
||||||
|
doc = getattr(value, "__doc__", None)
|
||||||
|
push(self.docother(getattr(object, name),
|
||||||
|
name, mod, 70, doc) + '\n')
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
attrs = inspect.classify_class_attrs(object)
|
||||||
|
|
||||||
|
# All attrs defined in this class come first.
|
||||||
|
attrs, inherited = _split_class_attrs(attrs,
|
||||||
|
lambda t: t[2] is object)
|
||||||
|
# Sort inherited attrs by name of defining class.
|
||||||
|
inherited.sort(lambda t1, t2: cmp(t1[2].__name__, t2[2].__name__))
|
||||||
|
|
||||||
|
thisclass = object
|
||||||
|
while attrs or inherited:
|
||||||
|
if thisclass is object:
|
||||||
|
tag = "defined here"
|
||||||
|
else:
|
||||||
|
tag = "inherited from class %s" % classname(thisclass,
|
||||||
|
object.__module__)
|
||||||
|
|
||||||
|
# Sort attrs by name.
|
||||||
|
attrs.sort(lambda t1, t2: cmp(t1[0], t2[0]))
|
||||||
|
|
||||||
|
# Pump out the attrs, segregated by kind.
|
||||||
|
attrs = spill("Methods %s:\n" % tag, attrs,
|
||||||
|
lambda t: t[1] == 'method')
|
||||||
|
attrs = spill("Class methods %s:\n" % tag, attrs,
|
||||||
|
lambda t: t[1] == 'class method')
|
||||||
|
attrs = spill("Static methods %s:\n" % tag, attrs,
|
||||||
|
lambda t: t[1] == 'static method')
|
||||||
|
attrs = spillproperties("Properties %s:\n" % tag, attrs)
|
||||||
|
attrs = spilldata("Data %s:\n" % tag, attrs)
|
||||||
|
assert attrs == []
|
||||||
|
|
||||||
|
# Split off the attributes inherited from the next class (note
|
||||||
|
# that inherited remains sorted by class name).
|
||||||
|
if inherited:
|
||||||
|
attrs = inherited
|
||||||
|
thisclass = attrs[0][2]
|
||||||
|
attrs, inherited = _split_class_attrs(attrs,
|
||||||
|
lambda t: t[2] is thisclass)
|
||||||
|
|
||||||
|
contents = '\n'.join(contents)
|
||||||
|
if not contents:
|
||||||
|
return title + '\n'
|
||||||
return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
|
return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
|
||||||
|
|
||||||
def formatvalue(self, object):
|
def formatvalue(self, object):
|
||||||
|
@ -933,7 +1010,7 @@ class TextDoc(Doc):
|
||||||
doc = getdoc(object) or ''
|
doc = getdoc(object) or ''
|
||||||
return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')
|
return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')
|
||||||
|
|
||||||
def docother(self, object, name=None, mod=None, maxlen=None):
|
def docother(self, object, name=None, mod=None, maxlen=None, doc=None):
|
||||||
"""Produce text documentation for a data object."""
|
"""Produce text documentation for a data object."""
|
||||||
repr = self.repr(object)
|
repr = self.repr(object)
|
||||||
if maxlen:
|
if maxlen:
|
||||||
|
@ -941,6 +1018,8 @@ class TextDoc(Doc):
|
||||||
chop = maxlen - len(line)
|
chop = maxlen - len(line)
|
||||||
if chop < 0: repr = repr[:chop] + '...'
|
if chop < 0: repr = repr[:chop] + '...'
|
||||||
line = (name and self.bold(name) + ' = ' or '') + repr
|
line = (name and self.bold(name) + ' = ' or '') + repr
|
||||||
|
if doc is not None:
|
||||||
|
line += '\n' + self.indent(str(doc))
|
||||||
return line
|
return line
|
||||||
|
|
||||||
# --------------------------------------------------------- user interfaces
|
# --------------------------------------------------------- user interfaces
|
||||||
|
|
Loading…
Reference in New Issue