Saturday, August 16, 2014

Python docstrings

Yesterday my colleague had some problems with updating Python documentation strings. Is __doc__ immutable for classes and methods? I decided to look into the matter.
So let's check this strange Python docstrings immutability.
Let's take the next Python code:
class A(object): 
    """Class A"""
    def foo(self):
        """Function foo"""
        pass

A.__doc__ = 'A class'
We will catch an exception:
AttributeError: attribute '__doc__' of 'type' objects is not writable.

The reason could be found in Python sources.
Here is part of code from Python 2.7 sources "Objects/typeobject.c" file:
static PyGetSetDef type_getsets[] = {
    ...
    {"__doc__", (getter)type_get_doc, NULL, NULL},
    ...
}
As you can see a setter function is NULL. So the problem is here.

Let's check the same for an instance of the class A.
a = A()
a.__doc__ = 'A'
print a.__doc__
A
print A.__doc__
Class A
So it is possible to change docstring for instance objects of a class, but the docstring of class remains unchanged.

To update docstring of classes it is possible to use metaclasses. Here is simple trick, that creates a copy of class with updated docstring.
class A(object):
    """Class A"""

    def foo(self):
        """Function foo"""
        print 'A.foo()'

def update_class_doc(cls, new_doc):
    new_dict = dict(cls.__dict__)
    new_dict['__doc__'] = new_doc
    new_cls = type(cls.__name__, A.__bases__, new_dict)
    return new_cls

A = update_class_doc(A, 'Updated docstring of class A')
print A.__doc__
Updated docstring of class A

Now let's try to change docstring for methods.
A.foo.__doc__ = 'Updated docstring of foo'
But exception will be caught again:
AttributeError: attribute '__doc__' of 'instancemethod' objects is not writable.

The reason could be found in Python "Objects/classobject.c" sources:
static PyGetSetDef instancemethod_getset[] = {
    {"__doc__", (getter)instancemethod_get_doc, NULL, NULL},
    {0}
};
As you can see a setter function is NULL again.

But it's possible to bypass this limitation, knowing that instancemethod gets docstring from its __func__ method. The __doc__ attribute of function is writeable.
print A.foo.__doc__
Function foo

A.foo.__func__.__doc__ = 'Updated docstring of foo'
print A.foo.__doc__
Updated docstring of foo
what is the same as:
A.foo.im_func.__doc__ = 'Updated docstring of foo 2'
print A.foo.__doc__
Updated docstring of foo 2
From Python 2.7 documentation:
"Changed in version 2.6: For Python 3 forward-compatibility, im_func is also available as __func__, and im_self as __self__."

In Python 3.3 issue 12773 was fixed, and now it's possible to set the __doc__ attribute of classes.
static PyGetSetDef type_getsets[] = {
    ...
    {"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL},
    ...
}
So now, using Python version >3.3, you can just write the next for a class A:

A.__doc__ = 'A class'
And no exceptions will be caught.