5

I have a class MyClass:

class MyClass(object):
    def __init__(self):
        pass

    def my_function(self, x):
        # MyClass.my_function.__doc__ is not writable!
        # Otherwise, I could just set it here.
        Origin.func(self, x)

The class borrows from Origin:

class Origin(object):    
    def func(obj, x):
        """This is a function
        """
        # do stuff
        pass

How can I copy the docstring from Origin.func to MyClass.my_function automatically so that Sphinx Autodoc recognises it? And how can I extend the original docstring by a couple of words?

Edit:

Afaik, I cannot just change __doc__ after the definition of the function since Sphinx would not find it then. Or if it did, where would the "docfix" go?

Xiphias
  • 4,468
  • 4
  • 28
  • 51
  • 2
    Could you give a less abstract example of what you're trying to achieve? `my_function.__doc__ = Origin.func.__doc__ + 'also this'` should work fine just after the definition of `my_function` ends. – jonrsharpe Jun 30 '16 at 13:58
  • I'm not sure how to make it clearer — I edited my question, though. Does it help? – Xiphias Jun 30 '16 at 14:03
  • Oh, I see! I can just write it after the definition of the class. That indeed works! – Xiphias Jun 30 '16 at 14:07
  • You should be able to do it inside the class, just after the method definition. It seems odd to have a method inheriting documentation in a class that doesn't inherit, though! – jonrsharpe Jun 30 '16 at 14:08
  • The idea behind it is that I want to compose a class (instead of inheritance), but some methods are just copied from other classes with minor corrections. Thus, the parameters etc. all stay the same. Is there another approach to what I'm doing? For example, I have a `to_latex` method. In my composed class, there is some postprocessing done (as a wrapper), but the call is identical to the original class. – Xiphias Jun 30 '16 at 14:16
  • *"I want to compose a class (instead of inheritance)"* - why? – jonrsharpe Jun 30 '16 at 14:17
  • I have an n to m relationship between methods and classes, i.e. it's not clearly hierarchical. Thus, inheritance would get messy quite quickly, I believe. – Xiphias Jun 30 '16 at 16:41
  • Could you give some less abstract examples? Alternatively, once it's working, head to [codereview.se]. – jonrsharpe Jun 30 '16 at 18:26
  • @jonrsharpe I am happy with the solution provided. Thank you for your help! – Xiphias Jul 01 '16 at 08:52
  • @Xiphias I've added a bit more detail to the answer. – Aya Jul 01 '16 at 11:07
  • @jonrsharpe Wow, nice work! – Xiphias Jul 01 '16 at 14:05

1 Answers1

6

I'm not clear on exactly how Sphinx works, but assuming it reads from __doc__ rather than parsing the source, there are a number of options.


Consider the simpler example...

def add(x, y):
    return x + y

...which is virtually identical to...

add = lambda x, y: x + y

In either case, you cannot refer to the symbol add inside its definition, since the symbol is not defined at that point. Nor can you refer to the function object which the symbol add will ultimately refer to, since it hasn't been created yet.

Therefore, you can only modify add.__doc__ after the symbol has been defined...

def add(x, y):
    return x + y
add.__doc__ = 'This is my docstring'

...but this may be a little more verbose than we'd like.


Another option is to exploit the fact that the Python decorator syntax...

@my_decorator
def add(x, y):
    return x + y

...is equivalent to...

def add(x, y):
    return x + y
add = my_decorator(add)

...that is, although it's placed before the function definition, it's executed after the function is defined, so you can reference the function object inside the body of the decorator function.


A decorator function is required to return a callable object, but given that we have no need to change the behavior of the add function, we can just return the argument which is passed in to the decorator, so given the decorator function...

def set_fixed_docstring(func):
    func.__doc___ = 'This is my docstring'
    return func

...used like...

@set_fixed_docstring
def add(x, y):
    return x + y

...is equivalent to...

def add(x, y):
    return x + y
add = set_fixed_docstring(add)

...or...

def add(x, y):
    return x + y
add.__doc__ = 'This is my docstring'
add = add

Obviously, a fixed docstring isn't much use here, so we need to parameterize the decorator, which is a little more complex.

In this instance, we need our decorator function to be callable with a string parameter, and to return a callable object which takes the target function as a parameter.

The most common way to do this is to define another function within the decorator function, such that the inner function can refer to symbols defined in the outer function. So the function...

def set_docstring_to(docstring):
    def wrapper(func):
        func.__doc___ = docstring
        return func
    return wrapper

...used like...

@set_docstring_to('This is my docstring')
def add(x, y):
    return x + y

...is equivalent to...

def add(x, y):
    return x + y
add = set_docstring_to('This is my docstring')(add)

...which boils down to the same code as before...

def add(x, y):
    return x + y
add.__doc__ = 'This is my docstring'
add = add

Putting all this together, if you were to use a decorator like...

def copy_docstring_from(source):
    def wrapper(func):
        func.__doc__ = source.__doc__
        return func
    return wrapper

...then you can just do...

class Origin(object):
    def func(obj, x):
        """This is a function
        """
        # do stuff
        pass

class MyClass(object):
    def __init__(self):
        pass

    @copy_docstring_from(Origin.func)
    def my_function(self, x):
        # MyClass.my_function.__doc__ is not writable!
        # Otherwise, I could just set it here.
        Origin.func(self, x)

...which should achieve the desired result with the minimum amount of code.

Aya
  • 39,884
  • 6
  • 55
  • 55
  • 3
    *"assuming it reads from `__doc__` rather than parsing the source"* - yep, [`autodoc`](http://www.sphinx-doc.org/en/stable/tutorial.html#autodoc) just imports the source code and iterates through the objects for `__doc__` attributes. – jonrsharpe Jul 01 '16 at 07:37