84

So I'm trying to create a "dynamic" docstring which is something like this:

ANIMAL_TYPES = ["mammals", "reptiles", "other"]

def func(animalType):
""" This is a sample function.

    @param animalType: "It takes one of these animal types %s" % ANIMAL_TYPES
"""

to basically let the docstring for @param animalType show whatever ANIMAL_TYPES has; so that when this variable is updated, the docstring will be updated automatically.

Unfortunately, it doesn't seem to work. Does anyone know if there is a way of achieving this?

5 Answers 5

81

One way to do this would be to use a decorator. I'm not sure how I feel about this; I actually searched for commentary on this method and found this answer, which rightly notes that it could mask a design problem. But your use case seems sound to me at first glance.

In any case, here's a fairly elegant way to achieve the result you're looking for:

>>> def docstring_parameter(*sub):
...     def dec(obj):
...         obj.__doc__ = obj.__doc__.format(*sub)
...         return obj
...     return dec
... 
>>> @docstring_parameter('Ocean')
... def foo():
...     '''My Docstring Lies Over The {0}'''
...     pass
... 
>>> @docstring_parameter('Sea')
... def bar():
...     '''My Docstring Lies Over The {0}'''
...     pass
... 
>>> @docstring_parameter('Docstring', 'Me')
... def baz():
...     '''Oh Bring Back My {0} To {1}'''
...     pass
... 
>>> foo.__doc__
'My Docstring Lies Over The Ocean'
>>> bar.__doc__
'My Docstring Lies Over The Sea'
>>> foo.__doc__
'My Docstring Lies Over The Ocean'
>>> baz.__doc__
'Oh Bring Back My Docstring To Me'
Sign up to request clarification or add additional context in comments.

2 Comments

A slight amendment to this answer, if I may: def docstring_parameter(*args, **kwargs): and then obj.__doc__ = obj.__doc__.format(*args, **kwargs). Now, you can do this: @docstring_parameter('Docstring', 'Me', yeah='yeah') and '''Oh Bring Back My {} To {}. {yeah}!'''. Finally, print(baz.__doc__) prints 'Oh Bring Back My Docstring To Me. yeah!'. Thank you!
Nice addition @nik_m. I would like to further improve on your solution by noting the fact that a check for obj.__doc__ is not None would fix an issue where no docstring is set in the function!
23

Triple-quoted strings are one big string. Nothing is evaluated inside them. The % part is all part of the string. You'd need to have it operating on the actual string.

def func(animalType):
    """
    This is a sample function.

    @param animalType: "It takes one of these animal types %(ANIMAL_TYPES)s"
    """ % {'ANIMAL_TYPES': ANIMAL_TYPES}

I'm not certain this will work properly, though; docstrings are a bit magic. This will not work; the docstring is evaluated at compile time (as the first statement in the function, given it is a string literal—once it's got the % in it it's not just a string literal), string formatting takes place at runtime, so __doc__ will be empty:

>>> def a(): 'docstring works'
... 
>>> a.__doc__
'docstring works'
>>> def b(): "formatted docstring doesn't work %s" % ':-('
... 
>>> b.__doc__
>>> 

If you wanted to work this way, you'd need to do func.__doc__ %= {'ANIMAL_TYPES': ANIMAL_TYPES} after the function is defined. Be aware that this would then break on python -OO if you didn't check that __doc__ was defined, as -OO strips docstrings.

>>> def c(): "formatted docstring works %s"
... 
>>> c.__doc__
"formatted docstring works %s"
>>> c.__doc__ %= 'after'
>>> c.__doc__
"formatted docstring works after"

This is not the standard technique anyway; the standard technique is to reference the appropriate constant: "Takes one of the animal types in ANIMAL_TYPES", or similar.

3 Comments

Thanks, Chris. Can I ask why __doc__ will be empty though in the code you crossed out? Thanks!
The reason it's empty is because the expression 'foo %s' % 'bar' isn't a string literal, it's an expression that would evaluate to a string. The compiler expects a real string literal, as the first thing inside the block, to qualify as a docstring.
I eventually went with the suggestion in the last code snippet. I tried to do "Takes one of the animal types in ANIMAL_TYPES", but it didn't work well because I'm writing the docstring for an API which will be generated by SPHINX and it wouldn't print out the content of the constant in the docs, no matter what I tried... :( So now it's like (Please see the original post. Code doesn't format very well here... )
13

You can also define a docstring using .__doc__

For example:

>>> def f():
      pass
>>> x = 1
>>> y = "docstring"

>>> f.__doc__ = "%s string %s" % (x, y)
>>> print(f.__doc__)
1 string docstring

Comments

3

I'm using Python 3.8

Simple string formatting worked for me. """This is {}""".format("StackOverflow")

1 Comment

That is not working: In [9]: def who_is_this(): ...: """This is {}""".format("StackOverflow") ...: return ...: In [11]: who_is_this? Signature: who_is_this() Docstring: <no docstring>
2

You could simply use cross-references in your docstring to refer to the variable.

So:

:param animalType: It takes one of these :data:`animal types<ANIMAL_TYPES>`

And in the second:

:param choice: can be one of :attr:`MY_CONST`

2 Comments

Can someone provide a SSCE clarifying how to write this into a python script? See also: sphinx-doc.org/domains.html#cross-referencing-python-objects and sphinx-doc.org/domains.html#python-roles
Can you explain in more detail how to use this?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.