I've created a series of nodes that each have a set of attribute objects associated with them. The attribute objects are initialized with descriptions and names for each attribute. I'd like these attributes, and their descriptions, to show up in my sphinx documentation without having to maintain the name/description in two places, once in the doc string of the class and once in the initialization of the attribute.
To illustrate the problem, consider the following code:
class Foo(object):
"""My doc string
"""
@classmethod
def default_attributes(cls):
return {'foo':'description of foo attribute',
'bar':'description of bar attribute'}
@classmethod
def attributes_string(cls):
attributes = cls.default_attributes()
result = '\nDefault Attributes:\n'
for key, value in attributes.iteritems():
result += '%s: %s\n' % (key, value)
return result
print Foo.__doc__
I'd like the result of Foo.attributes_string to show up in the doc string of Foo so that I get this:
My doc string
Default Attributes:
foo: description of foo attribute
bar: description of bar attribute
First I thought "Hey that's easy! I'll just setup a class decorator!":
def my_decorator(cls):
doc = getattr(cls, '__doc__', '')
doc += cls.attributes_string()
cls.__doc__ = doc
return cls
@my_decorator
class Foo(object):
"""My doc string
"""
This failed miserably with the following error:
AttributeError: attribute '__doc__' of 'type' objects is not writable
So then I thought "Well then I'll just use a metaclass to set __doc__ before the class is created!". When I went to implement this I immediately ran into a problem: How do you call a classmethod for a class that hasn't been created yet?
I circumvented that problem with a very hacky workaround that makes me cringe: I create the class twice, once without modifying it so I can call it's classmethod, and then again to create the class with the proper __doc__:
class Meta(type):
def __new__(meta_cls, name, bases, cls_dict):
tmpcls = super(Meta, meta_cls).__new__(meta_cls, name, bases, cls_dict)
doc = cls_dict.get('__doc__', '')
doc += tmpcls.attributes_string()
cls_dict['__doc__'] = doc
return super(Meta, meta_cls).__new__(meta_cls, name, bases, cls_dict)
class Foo(object):
"""My doc string
"""
__metaclass__ = Meta
This totally works and gives me the result I was looking for:
My doc string
Default Attributes:
foo: description of foo attribute
bar: description of bar attribute
However, isn't it terribly inefficient to create the class twice? Does it matter? Is there a better way? Is what I'm trying to do really dumb?
Docstrings are accessible from the doc attribute (__doc__) for any of the Python objects and also with the built-in help() function. An object's docstring is defined by including a string constant as the first statement in the object's definition.
All modules should normally have docstrings, and all functions and classes exported by a module should also have docstrings. Public methods (including the __init__ constructor) should also have docstrings. A package may be documented in the module docstring of the __init__.py file in the package directory.
Module docstrings are placed at the top of the file even before any imports. Module docstrings should include the following: A brief description of the module and its purpose. A list of any classes, exception, functions, and any other objects exported by the module.
Declaring Docstrings: The docstrings are declared using ”'triple single quotes”' or “””triple double quotes””” just below the class, method or function declaration. All functions should have a docstring.
Did a bit of looking around for you, no dice because it's something fundamentally broken and wasn't fixed until Python 3.3. So if you plan to release your program for >3.3, __doc__
attributes will be mutable.
That however doesn't seem to help you, but there are ways to more cleverly beat this problem into submission by simply provide a __doc__
property in the metaclass.
class Meta(type):
@property
def __doc__(self):
return self.attributes_string()
class Foo(object):
"""My doc string
"""
__metaclass__ = Meta
@classmethod
def default_attributes(cls):
return {'foo':'description of foo attribute',
'bar':'description of bar attribute'}
@classmethod
def attributes_string(cls):
attributes = cls.default_attributes()
result = '\nDefault Attributes:\n'
for key, value in attributes.items():
result += '%s: %s\n' % (key, value)
return result
No need to butcher that __new__
method, since attributes and properties in metaclasses will be available to subclasses and vice versa. Do a help(Foo)
and it now gives this:
CLASSES
__builtin__.object
Foo
__builtin__.type(__builtin__.object)
Meta
class Foo(__builtin__.object)
| Default Attributes:
| foo: description of foo attribute
| bar: description of bar attribute
Tested under Python 2.7.
Caveat: It will override the standard way of declaring docstrings, so you probably have to put the entire thing in there unless you also happen to override the __new__
method to put the original __doc__
out of harms way. Maybe this is what you want:
class Meta(type):
def __new__(cls, name, bases, attrs):
attrs['_doc'] = attrs.get('__doc__', '')
return super(Meta, cls).__new__(cls, name, bases, attrs)
@property
def __doc__(self):
return self._doc + self.attributes_string()
You result:
class Foo(__builtin__.object)
| My doc string
|
| Default Attributes:
| foo: description of foo attribute
| bar: description of bar attribute
If you want to generate docstrings for uncommented functions, you can also use Pyment.
It won't provide your specific expected format but it currently generate patches adding (or converting) docstrings in formats Sphinxs, Numpydoc, or Google doc style.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With