Docstrings for Python descriptors

3 weeks ago 14
ARTICLE AD BOX

I've hit a strange problem trying to document a Python descriptor. The following demonstrates the problem. We define a simple noop descriptor class, and a test class with a single attribute implemented by the noop descriptor. The descriptor class has a docstring. We then check to see what gets displayed when help() is run on various things. Only help(noop) displays the docstring, nothing else does even though a lookup of test.attr.__doc__ shows the expected string. This is unexpected.

class noop(object): """ XXX documentation. """ def __get__(self, obj, owner = None): return self if obj is None else None def __set__(self, obj, value): pass def __delete__(self, obj): pass class test(object): attr = noop() help(test) # attr has no documentation help(test.attr) # no documentation print(test.attr.__doc__) # prints "XXX documentation" help(noop) # shows "XXX documentation" help(test()) # attr has no documentation

In the second attempt we add an .__init__() method to the descriptor and have that set a .__doc__ instance attribute. Now we see that documentation when retrieving the .attr attribute from either the test class or an instance of the test class, but we are shown the noop class' docstring when we ask for help about it. That there is a distinction is expected.

# # try again # class noop(object): """ XXX documentation. """ def __init__(self): self.__doc__ = "YYY documentation." def __get__(self, obj, owner = None): return self if obj is None else None def __set__(self, obj, value): pass def __delete__(self, obj): pass class test(object): attr = noop() help(test) # shows "YYY documentation" help(test.attr) # shows "YYY documentation" help(noop) # shows "XXX documentation" help(test()) # shows "YYY documentation"

Here's where things get strange. For reasons, I want to be shown the same documentation in all four cases above. That's what I was expecting would happen if only the noop class defined a .__doc__ attribute, if instances of the class did not override it, but that's not what happens. So, in the 3rd attempt I use .__init__() to copy the class' docstring to the instance's .__doc__ attribute.

# # OK, we get it, so let's do: # class noop(object): """ XXX documentation. """ def __init__(self): print(noop.__doc__) # prints "XXX documentation." self.__doc__ = noop.__doc__ def __get__(self, obj, owner = None): return self if obj is None else None def __set__(self, obj, value): pass def __delete__(self, obj): pass class test(object): attr = noop() help(test) # attr has NO DOCUMENTATION help(test.attr) # NO DOCUMENTATION print(test.attr.__doc__) # prints "XXX documentation" help(noop) # shows "XXX documentation" help(test()) # attr has NO DOCUMENTATION

This does not work.

To confirm that noop.__doc__ is not blank at the time of code execution I added a print() statement to the .__init__() method and it prints the expected string. Retrieving and printing the .__doc__ attribute of the test.attr instance of the noop class, ourselves, also shows the expected string. That confirms that the .__doc__ attribute has been set correctly by the .__init__() method, and yet we're shown no documentation. Changing the string works: self.__doc__ = noop.__doc__ + " ".

Questions:

What is going on!? Why is Python refusing to display the docstring that we know is there?

How, then, do I set the instance's .__doc__ equal to the class' .__doc__? Is the hack of adding a single whitespace character seriously the solution!?

Why do we need to this at all? When one retrieves an attribute from an instance of a class, the look-up falls through to a class attribute if the instance does not define one by that name. In the first of the three examples above, the one print() statement confirms that that is what happens when .__doc__ is retrieved from test.attr. The .__doc__ attribute is not exempt from this rule, so why do we not get shown the class' docstring when the instance does not define its own .__doc__?

Read Entire Article