We've been trying to find an easy way to inherit docstrings when
overriding methods in subclasses, e.g. so that Sphinx does a nice job.

We used Shai's DocInherit code from this post:

http://groups.google.com/group/comp.lang.python/tree/browse_frm/thread/1e4075ba10dcbdd9/f63651cd9e76df63?rnum=1&_done=%2Fgroup%2Fcomp.lang.python%2Fbrowse_frm%2Fthread%2F1e4075ba10dcbdd9%2F%3F#doc_5f5963372b951d79

of this thread

http://groups.google.com/group/comp.lang.python/browse_frm/thread/1e4075ba10dcbdd9/

The DocInherit property works fine, except that all the logic for
adding the docstring to the method is evaluated every single time the
method is looked up.  I experimented with a simple caching scheme but
abandoned it.

I've tried a new approach -- it uses the same property mechanism as
Shai's code to delay doing anything until the class exists and the
method is used -- but it does its work only once, the first time the
method is looked up; it adds the docstring from the first mro parent,
and then the property *replaces itself* in the class's namespace with
the method that now has a docstring.  It appears to work in a large
project with extensive testing.

My question concerns whether it is risky to replace a class property
attribute to a method attribute after the class has been created, and
perhaps after the class has been used?  Does this kind of change
violate some subtle or internal expectations of the interpreter
runtime?

Thanks.
-Hugh

Here's the code and doctests:

class InheritDoc(object):
 
"""
    Docstring inheriting method
descriptor

    The class itself is used as a decorator that creates a class
property
for
    the method; the first time the property is used it installs the
method's
doc
    and then replaces itself as a class attribute with the
method!

 
Usage:

    >>> class
Foo(object):
    ...     def foo(self,
x):
    ...
'Frobber'
    ...         print 'Foo.foo()',
x

    Correct usage for overridden method foo(), incorrect usage for
method
bar()

    >>> class
Bar(Foo):
    ...
@inherit_doc
    ...     def foo(self,
x):
    ...         print 'Bar.foo()',
x
    ...
@inherit_doc
    ...     def
bar(self):
    ...         print
'Bar.bar()'

    >>> Bar.foo.__doc__ == Bar().foo.__doc__ == Foo.foo.__doc__ ==
'Frobber'
 
True

    >>>
Foo.foo
    <unbound method
Foo.foo>
    >>>
Foo().foo(10)
    Foo.foo()
10
    >>>
Bar.foo
    <unbound method
Bar.foo>
    >>>
Bar().foo(11)
    Bar.foo()
11

    >>>
Bar.bar
    Traceback (most recent call
last):
      ...
    NameError: inherit_doc cannot find method 'bar' in parents of
'__main__.Bar'

    >>>
Bar().bar
    Traceback (most recent call
last):
      ...
    NameError: inherit_doc cannot find method 'bar' in parents of
'__main__.Bar'
    """
    __slots__ = 'inherit_doc_unbound_method'
    def __init__(self, unbound_method):
        self.inherit_doc_unbound_method = unbound_method
    def __get__(self, obj, cls):
        # a self-destructing descriptor/
property:
        # the first and only time it's used, it fixes the method's doc
and
then
        # replaces itself with the
method

        # find the overridden method in mro sequence, skipping the
class
itself
        method_name = self.inherit_doc_unbound_method.__name__
        mro_iter = iter(cls.__mro__)
        mro_iter.next()
        for parent in mro_iter:
            overridden = getattr(parent, method_name, None)
            if overridden is not None: break
        if overridden is None:
            raise NameError('inherit_doc cannot find method %r in
parents of %r'
                            % (method_name, '%s.%s'%(cls.__module__,
cls.__name__)))

        # XXX next steps are not threadsafe, maybe not safe at
all!
        # set the
doc
        self.inherit_doc_unbound_method.__doc__ = overridden.__doc__
        # replace the property with the
function
        setattr(cls, method_name, self.inherit_doc_unbound_method)
        # use the
replacement
        return getattr(obj if obj is not None else cls, method_name)

inherit_doc = InheritDoc
-- 
http://mail.python.org/mailman/listinfo/python-list

Reply via email to