Override a method but inherit the docstring

Paul McGuire ptmcg at austin.rr.com
Thu Jul 16 21:35:05 EDT 2009


On Jul 16, 8:01 pm, Ben Finney <ben+pyt... at benfinney.id.au> wrote:
> Howdy all,
>
> The following is a common idiom::
>
>     class FooGonk(object):
>         def frobnicate(self):
>             """ Frobnicate this gonk. """
>             basic_implementation(self.wobble)
>
>     class BarGonk(FooGonk):
>         def frobnicate(self):
>             special_implementation(self.warble)
>
> The docstring for ‘FooGonk.frobnicate’ is, intentionally, perfectly
> applicable to the ‘BarGonk.frobnicate’ method also. Yet in overriding
> the method, the original docstring is not associated with it.
>
> What is the most Pythonic, DRY-adherent, and preferably least-ugly
> approach to override a method, but have the same docstring on both
> methods?
>

Two ideas come to mind, the decorator way and the metaclass way.  I am
not a guru at either, but these two examples work:

# the decorator way
def inherit_docstring_from(cls):
    def docstring_inheriting_decorator(fn):
        fn.__doc__ = getattr(cls,fn.__name__).__doc__
        return fn
    return docstring_inheriting_decorator


class FooGonk(object):
    def frobnicate(self):
        """ Frobnicate this gonk. """
        basic_implementation(self.wobble)


class BarGonk(FooGonk):
    @inherit_docstring_from(FooGonk)
    def frobnicate(self):
        special_implementation(self.warble)

bg = BarGonk()
help(bg.frobnicate)

Prints:
Help on method frobnicate in module __main__:

frobnicate(self) method of __main__.BarGonk instance
    Frobnicate this gonk.


Using a decorator in this manner requires repeating the super class
name.  Perhaps there is a way to get the bases of BarGonk, but I don't
think so, because at the time that the decorator is called, BarGonk is
not yet fully defined.



# The metaclass way

from types import FunctionType

class DocStringInheritor(type):
    def __new__(meta, classname, bases, classDict):
        newClassDict = {}
        for attributeName, attribute in classDict.items():
            if type(attribute) == FunctionType:
                # look through bases for matching function by name
                for baseclass in bases:
                    if hasattr(baseclass, attributeName):
                        basefn = getattr(baseclass,attributeName)
                        if basefn.__doc__:
                            attribute.__doc__ = basefn.__doc__
                            break

            newClassDict[attributeName] = attribute

        return type.__new__(meta, classname, bases, newClassDict)

class FooGonk2(object):
    def frobnicate(self):
        """ Frobnicate this gonk. """
        basic_implementation(self.wobble)


class BarGonk2(FooGonk2):
    __metaclass__ = DocStringInheritor
    def frobnicate(self):
        special_implementation(self.warble)

bg = BarGonk2()
help(bg.frobnicate)

Prints:

Help on method frobnicate in module __main__:

frobnicate(self) method of __main__.BarGonk2 instance
    Frobnicate this gonk.


This metaclass will walk the list of bases until the desired
superclass method is found AND if that method has a docstring and only
THEN does it attach the superdocstring to the derived class method.

Please use carefully, I just did the metaclass thing by following
Michael Foord's Metaclass tutorial (http://www.voidspace.org.uk/python/
articles/metaclasses.shtml), I may have missed a step or two.

-- Paul



More information about the Python-list mailing list