How do I programmatically set the docstring?

PythonDocstring

Python Problem Overview


I have a wrapper function that returns a function. Is there a way to programmatically set the docstring of the returned function? If I could write to __doc__ I'd do the following:

def wrapper(a):
    def add_something(b):
       return a + b
    add_something.__doc__ = 'Adds ' + str(a) + ' to `b`'
    return add_something

Then I could do

>>> add_three = wrapper(3)
>>> add_three.__doc__
'Adds 3 to `b`

However, since __doc__ is read-only, I can't do that. What's the correct way?


Edit: Ok, I wanted to keep this simple, but of course this is not what I'm actually trying to do. Even though in general __doc__ is writeable in my case it isn't.

I am trying to create testcases for unittest automatically. I have a wrapper function that creates a class object that is a subclass of unittest.TestCase:

import unittest
def makeTestCase(filename, my_func):
    class ATest(unittest.TestCase):
        def testSomething(self):
            # Running test in here with data in filename and function my_func
            data  = loadmat(filename)
            result = my_func(data)
            self.assertTrue(result > 0)
                            
    return ATest

If I create this class and try to set the docstring of testSomething I get an error:

>>> def my_func(): pass
>>> MyTest = makeTestCase('some_filename', my_func)
>>> MyTest.testSomething.__doc__ = 'This should be my docstring'
AttributeError: attribute '__doc__' of 'instancemethod' objects is not writable

Python Solutions


Solution 1 - Python

An instancemethod gets its docstring from its __func__. Change the docstring of __func__ instead. (The __doc__ attribute of functions are writeable.)

>>> class Foo(object):
...     def bar(self):
...         pass
...
>>> Foo.bar.__func__.__doc__ = "A super docstring"
>>> help(Foo.bar)
Help on method bar in module __main__:

bar(self) unbound __main__.Foo method
    A super docstring

>>> foo = Foo()
>>> help(foo.bar)
Help on method bar in module __main__:

bar(self) method of __main__.Foo instance
    A super docstring

From the 2.7 docs:

> User-defined methods > > A user-defined method object combines a class, a class instance (or None) and any callable > object (normally a user-defined function). > > Special read-only attributes: im_self is the class instance object, im_func is the function > object; im_class is the class of im_self for bound methods or the class that asked for the > method for unbound methods; __doc__ is the method’s documentation (same as > im_func.__doc__); __name__ is the method name (same as im_func.__name__); > __module__ is the name of the module the method was defined in, or None if unavailable. > > Changed in version 2.2: im_self used to refer to the class that defined the method. > > Changed in version 2.6: For 3.0 forward-compatibility, im_func is also available as > __func__, and im_self as __self__.

Solution 2 - Python

I would pass the docstring into the factory function and use type to manually construct the class.

def make_testcase(filename, myfunc, docstring):
    def test_something(self):
        data = loadmat(filename)
        result = myfunc(data)
        self.assertTrue(result > 0)
    
    clsdict = {'test_something': test_something,
               '__doc__': docstring}
    return type('ATest', (unittest.TestCase,), clsdict)

MyTest = makeTestCase('some_filename', my_func, 'This is a docstring')

Solution 3 - Python

This is an addition to the fact that the __doc__ attribute of classes of type type cannot be changed. The interesting point is that this is only true as long as the class is created using type. As soon as you use a metaclass you can actually just change __doc__.

The example uses the abc (AbstractBaseClass) module. It works using a special ABCMeta metaclass

import abc

class MyNewClass(object):
    __metaclass__ = abc.ABCMeta

MyClass.__doc__ = "Changing the docstring works !"

help(MyNewClass)

will result in

"""
Help on class MyNewClass in module __main__:

class MyNewClass(__builtin__.object)
 |  Changing the docstring works !
"""

Solution 4 - Python

Just use decorators. Here's your case:

def add_doc(value):
    def _doc(func):
        func.__doc__ = value
        return func
    return _doc

import unittest
def makeTestCase(filename, my_func):
    class ATest(unittest.TestCase):
        @add_doc('This should be my docstring')
        def testSomething(self):
            # Running test in here with data in filename and function my_func
            data  = loadmat(filename)
            result = my_func(data)
            self.assertTrue(result > 0)
    
    return ATest

def my_func(): pass

MyTest = makeTestCase('some_filename', my_func)
print MyTest.testSomething.__doc__
> 'This should be my docstring'

Here's a similar use case: https://stackoverflow.com/questions/13603088/python-dynamic-help-and-autocomplete-generation

Solution 5 - Python

__doc__ is not writable only when your object is of type 'type'.

In your case, add_three is a function and you can just set __doc__ to any string.

Solution 6 - Python

In the case where you're trying to automatically generate unittest.TestCase subclasses, you may have more mileage overriding their shortDescription method.

This is the method that strips the underlying docstring down to the first line, as seen in normal unittest output; overriding it was enough to give us control over what showed up in reporting tools like TeamCity, which was what we needed.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionoceanhugView Question on Stackoverflow
Solution 1 - PythonhwiechersView Answer on Stackoverflow
Solution 2 - PythonaaronasterlingView Answer on Stackoverflow
Solution 3 - PythonjhpView Answer on Stackoverflow
Solution 4 - PythonestaniView Answer on Stackoverflow
Solution 5 - PythonsatoruView Answer on Stackoverflow
Solution 6 - PythonPaul FenneyView Answer on Stackoverflow