[Python-Dev] pydoc for named tuples is missing methods

Tim Lesher tlesher at gmail.com
Tue Mar 15 14:42:38 CET 2011


On Mon, Mar 14, 2011 at 08:28, Tim Lesher <tlesher at gmail.com> wrote:
> On Mon, Mar 14, 2011 at 05:45, Nick Coghlan <ncoghlan at gmail.com> wrote:
>> There are two relatively simple ways forward I can see:
>>
>> A. Add a __public__ attribute that pydoc (and import *) understand.
>> This would overrride the default "this is private" detection and add
>> affected names to the public list (without needing to respecify all
>> the "default public" names - this is important in order to support
>> subclassing correctly)
>
> I believe this was the direction the bug report was implying.
>
> I'll be sprinting for a few hours this morning; if there are no
> objections, I will try to turn this idea into a patch that makes
> pydoc.visiblename look for a __public__ function attribute as "step
> 0".
>
> Maybe there should also be a @public decorator to apply it, although
> that name may be an attractive nuisance, tempting C++ or Java
> programmers new to Python to ask for @protected and @private...

I implemented this on Monday, and it worked great... for instance
methods.  Unfortunately, it doesn't do a thing for classmethods or
data attributes, which are 2/4 of the original namedtuple use cases,
so it feels like a bad idea.

In the process of implementing it, though, I realized that the __all__
mechanism for modules could still be reused.  Two implementations came
to mind:

1. Allow an __all__ class attribute that affects pydoc for classes the
same way it does for modules.
Pro: it's familiar, easy to explain, and easy to implement (four lines of code)
Con: the original use case (adding a small number of
otherwise-excluded attributes to existing documentation) is
cumbersome. Because __all__ means "don't document anything but
__special_names__ and the contents of __all__, you'd have to manually
add names that pydoc would normally pick up.  Also, __all__ itself
will show up in the documentation, which seems to me like useless
clutter to a reader of help().

2. Add a new class attribute that uses the same mechanism, with a
different name (e.g., __api__).
Pro: fairly easy to explain; because it doesn't share a name with
__all__ its semantics can be tweaked without confusing people.
Con: slight additional cognitive burden of a new feature

I'm implementing both of these separately this week to see which works
better in practice.  So far I'm liking __api__ better, with these
semantics:

1. Classes without __api__ are treated just as they are today.
2. __api__ on classes works just like __all__ on modules (only special
names, plus its contents are documented)
3. Additionally, __api__ can take an Ellipsis.  That makes the __api__
list additive--pydoc documents everything it normally would, *plus*
the contents of __api__
4. __api__ is added to pydoc's "hidden names" list; since its only
purpose is to help generate docs, its value would be of little use in
the generated docs themselves.

Point 3 (Ellipsis) is unusual, but to me it makes the declaration read
well and solves the namedtuple case succinctly and compatibly, without
changing a published API.  It also could be used to address the issue
of stdlib classes that have non-underscored members that really
shouldn't be considered part of the "public" API, but can't be renamed
now for fear of breaking code.

Usage example:

class C1:
    __api__ = ['meth1', '_data2'] # read as "The API consists of meth1
and _data2."
    def __init__(self):
        """documented because it's a special name"""
    def meth1(self):
        """documented because it's in __api__"""
    def meth2(self):
        """Shouldn't really have been exposed, but it was released that way,
           and we don't want to break users' code.
           NOT documented because it's not in __api__
           """
    def _meth3(self):
        """NOT documented because it's not in __api__"""
    data1 = None # not documented--not in __api__
    _data2 = None # documented--it's in __api__

class C2:
    __api__ = ['_data2', ...] # read as "The API includes _data2."
    def __init__(self):
        """This is documented because it's a special name"""
    def meth1(self):
        """documented because it follows the normal rules"""
    def meth2(self):
        """documented because it follows the normal rules"""
    def _meth3(self):
        """NOT documented because it's not in __api__ and starts with
underscore"""
    data1 = # documented because it follows the normal rules
    _data2 = None # documented--it's in __api__

Comments and criticisms welcome.

-- 
Tim Lesher <tlesher at gmail.com>


More information about the Python-Dev mailing list