Inheriting methods' docstrings in Python

后端 未结 5 405
无人共我
无人共我 2020-12-02 16:28

I have an OO hierarchy with docstrings that take as much maintenance as the code itself. E.g.,

class Swallow(object):
    def airspeed(self):
        \"\"\"R         


        
相关标签:
5条回答
  • 2020-12-02 17:06

    The following adaptation also handles properties and mixin classes. I also came across a situation where I had to use func.__func__ (for "instancemethod"s), but I'm not completely sure why the other solutions didn't encouter that problem.

    def inherit_docs(cls):
        for name in dir(cls):
            func = getattr(cls, name)
            if func.__doc__: 
                continue
            for parent in cls.mro()[1:]:
                if not hasattr(parent, name):
                    continue
                doc = getattr(parent, name).__doc__
                if not doc: 
                    continue
                try:
                    # __doc__'s of properties are read-only.
                    # The work-around below wraps the property into a new property.
                    if isinstance(func, property):
                        # We don't want to introduce new properties, therefore check
                        # if cls owns it or search where it's coming from.
                        # With that approach (using dir(cls) instead of var(cls))
                        # we also handle the mix-in class case.
                        wrapped = property(func.fget, func.fset, func.fdel, doc)
                        clss = filter(lambda c: name in vars(c).keys() and not getattr(c, name).__doc__, cls.mro())
                        setattr(clss[0], name, wrapped)
                    else:
                        try:
                            func = func.__func__ # for instancemethod's
                        except:
                            pass
                        func.__doc__ = doc
                except: # some __doc__'s are not writable
                    pass
                break
        return cls
    
    0 讨论(0)
  • 2020-12-02 17:15

    F.Y.I for people just now stumbling on this topic: As of Python 3.5, inspect.getdoc automatically retrieves docstrings from the inheritance hierarchy.

    The responses above are thus useful for Python 2, or if you want to be more creative with merging the docstrings of parents and children.

    I've also created some lightweight tools for docstring inheritance. These support some nice default docstring styles (numpy, google, reST) out of the box. You can easily use your own docstring style as well

    0 讨论(0)
  • 2020-12-02 17:15
    def fix_docs(cls):
        """ copies docstrings of derived attributes (methods, properties, attrs) from parent classes."""
        public_undocumented_members = {name: func for name, func in vars(cls).items()
                                       if not name.startswith('_') and not func.__doc__}
    
        for name, func in public_undocumented_members.iteritems():
            for parent in cls.mro()[1:]:
                parfunc = getattr(parent, name, None)
                if parfunc and getattr(parfunc, '__doc__', None):
                    if isinstance(func, property):
                        # copy property, since its doc attribute is read-only
                        new_prop = property(fget=func.fget, fset=func.fset,
                                            fdel=func.fdel, doc=parfunc.__doc__)
                        cls.func = new_prop
                    else:
                        func.__doc__ = parfunc.__doc__
                    break
        return cls
    
    0 讨论(0)
  • 2020-12-02 17:17

    Write a function in a class-decorator style to do the copying for you. In Python2.5, you can apply it directly after the class is created. In later versions, you can apply with the @decorator notation.

    Here's a first cut at how to do it:

    import types
    
    def fix_docs(cls):
        for name, func in vars(cls).items():
            if isinstance(func, types.FunctionType) and not func.__doc__:
                print func, 'needs doc'
                for parent in cls.__bases__:
                    parfunc = getattr(parent, name, None)
                    if parfunc and getattr(parfunc, '__doc__', None):
                        func.__doc__ = parfunc.__doc__
                        break
        return cls
    
    
    class Animal(object):
        def walk(self):
            'Walk like a duck'
    
    class Dog(Animal):
        def walk(self):
            pass
    
    Dog = fix_docs(Dog)
    print Dog.walk.__doc__
    

    In newer Python versions, the last part is even more simple and beautiful:

    @fix_docs
    class Dog(Animal):
        def walk(self):
            pass
    

    This is a Pythonic technique that exactly matches the design of existing tools in the standard library. For example, the functools.total_ordering class decorator add missing rich comparison methods to classes. And for another example, the functools.wraps decorator copies metadata from one function to another.

    0 讨论(0)
  • 2020-12-02 17:30

    This is a variation on Paul McGuire's DocStringInheritor metaclass.

    1. It inherits a parent member's docstring if the child member's docstring is empty.
    2. It inherits a parent class docstring if the child class docstring is empty.
    3. It can inherit the docstring from any class in any of the base classes's MROs, just like regular attribute inheritance.
    4. Unlike with a class decorator, the metaclass is inherited, so you only need to set the metaclass once in some top-level base class, and docstring inheritance will occur throughout your OOP hierarchy.

    import unittest
    import sys
    
    class DocStringInheritor(type):
        """
        A variation on
        http://groups.google.com/group/comp.lang.python/msg/26f7b4fcb4d66c95
        by Paul McGuire
        """
        def __new__(meta, name, bases, clsdict):
            if not('__doc__' in clsdict and clsdict['__doc__']):
                for mro_cls in (mro_cls for base in bases for mro_cls in base.mro()):
                    doc=mro_cls.__doc__
                    if doc:
                        clsdict['__doc__']=doc
                        break
            for attr, attribute in clsdict.items():
                if not attribute.__doc__:
                    for mro_cls in (mro_cls for base in bases for mro_cls in base.mro()
                                    if hasattr(mro_cls, attr)):
                        doc=getattr(getattr(mro_cls,attr),'__doc__')
                        if doc:
                            if isinstance(attribute, property):
                                clsdict[attr] = property(attribute.fget, attribute.fset, 
                                                         attribute.fdel, doc)
                            else:
                                attribute.__doc__ = doc
                            break
            return type.__new__(meta, name, bases, clsdict)
    
    
    
    class Test(unittest.TestCase):
    
        def test_null(self):
            class Foo(object):
    
                def frobnicate(self): pass
    
            class Bar(Foo, metaclass=DocStringInheritor):
                pass
    
            self.assertEqual(Bar.__doc__, object.__doc__)
            self.assertEqual(Bar().__doc__, object.__doc__)
            self.assertEqual(Bar.frobnicate.__doc__, None)
    
        def test_inherit_from_parent(self):
            class Foo(object):
                'Foo'
    
                def frobnicate(self):
                    'Frobnicate this gonk.'
            class Bar(Foo, metaclass=DocStringInheritor):
                pass
            self.assertEqual(Foo.__doc__, 'Foo')
            self.assertEqual(Foo().__doc__, 'Foo')
            self.assertEqual(Bar.__doc__, 'Foo')
            self.assertEqual(Bar().__doc__, 'Foo')
            self.assertEqual(Bar.frobnicate.__doc__, 'Frobnicate this gonk.')
    
        def test_inherit_from_mro(self):
            class Foo(object):
                'Foo'
    
                def frobnicate(self):
                    'Frobnicate this gonk.'
            class Bar(Foo):
                pass
    
            class Baz(Bar, metaclass=DocStringInheritor):
                pass
    
            self.assertEqual(Baz.__doc__, 'Foo')
            self.assertEqual(Baz().__doc__, 'Foo')
            self.assertEqual(Baz.frobnicate.__doc__, 'Frobnicate this gonk.')
    
        def test_inherit_metaclass_(self):
            class Foo(object):
                'Foo'
    
                def frobnicate(self):
                    'Frobnicate this gonk.'
            class Bar(Foo, metaclass=DocStringInheritor):
                pass
    
            class Baz(Bar):
                pass
            self.assertEqual(Baz.__doc__, 'Foo')
            self.assertEqual(Baz().__doc__, 'Foo')
            self.assertEqual(Baz.frobnicate.__doc__, 'Frobnicate this gonk.')
    
        def test_property(self):
            class Foo(object):
                @property
                def frobnicate(self): 
                    'Frobnicate this gonk.'
            class Bar(Foo, metaclass=DocStringInheritor):
                @property
                def frobnicate(self): pass
    
            self.assertEqual(Bar.frobnicate.__doc__, 'Frobnicate this gonk.')
    
    
    if __name__ == '__main__':
        sys.argv.insert(1, '--verbose')
        unittest.main(argv=sys.argv)
    
    0 讨论(0)
提交回复
热议问题