Search code examples
pythondocstring

Conveying docstrings for certain python functions


I have a general format of docstrings that I try to follow when I make functions. I give a summary of what the function does followed by a brief explanation of what class it takes for input and what class it outputs.

def cuberoot4u(number):
    """Returns the cube root of number.

    cuberoot4u(float) -> float

    """
    return pow(number, 1/3)

So in this case, cuberoot4u takes a float for an input and returns a float for output.

How could I best convey to the user with docstrings what class of input a function requires if it takes a .txt file as an input and outputs the content in a string?

def getText(filename):
    """Reads the file filename and returns the content.

    getText(?????) -> str
    """
    fd = open(filename)
    text = fd.read()
    fd.close()
    return text

Would it be best to say getText(filename.txt) -> str or is there a specific class name for that much like string is 'str' and an integer is 'int'?

Also, what about for functions whose outputs are not clearly defined like this example:

def commandmenu():
    """Begin some random menu which does stuff.

    commandmenu() -> ??????
    """


    while 1:
        someuserinput = input("Enter a command: ")
        if someuserinput == 'this':
            pass
        elif someuserinput == 'that':
            pass
        else:
            print('u suck')
    return None

So in this case, there is no initial output from entering the function as it leads to an input from the user before it does something. What would best fit in ????? if such a function becomes more and more meticulous like this and may lead to multiple different outputs depending on prompts to the user etc.?


Solution

  • You hit what is usually referred to as a hole in the abstraction. In your case the abstraction being that for every function specifying argument types is enough to document it. Most languages (or, perhaps, all), and Python in particular do not have a type system strong enough to make it possible.

    Consider a function similar to your first one, but calculating square roots:

    def squareroot4u(number):
        """Returns the cube root of number.
    
        squareroot4u(float) -> float
    
        """
        return pow(number, 1/2)
    

    Obviously, the return type is float only if number is non-negative, otherwise it should be complex. Python (unlike some scientifically-oriented languages) does not have a separate type for non-negative numbers, so you have to additionally specify a contract that enforces it:

    def squareroot4u(number):
        """Returns the cube root of number.
    
        squareroot4u(number: float) -> float
    
        precondition: number >= 0
        raises ValueError otherwise
        """
        return pow(number, 1/2)
    

    I personally use Sphinx for documenting, and in its format it would look like

    def squareroot4u(number):
        """Returns the cube root of number.
    
        :param number: a non-negative number, raises ``ValueError`` otherwise
        :type number: ``float``
        :return: square root of the input
        :rtype: ``float``
        """
        return pow(number, 1/2)
    

    For your second function:

    def getText(filename):
        """Reads the file filename and returns the content.
    
        :param filename: a path to a text file 
                         (raises ``WhateverError``/blows ups the monitor otherwise)
        :type filename: ``str``
        :returns: file contents
        :rtype: ``str``
        """
    

    Note that types in Python docstrings are often omitted when they can be deduced from the context (e.g. what else other than str can filename be?)

    Now with the third function, in addition to types and contracts, you have side effects. These should be documented in the general description of the function (if they're the main point of running this function), or in a note/warning (if they're just something to be kept in mind). For instance (using Sphinx syntax again):

    def commandmenu():
        """Begin some random menu which does stuff.
        Shows an input prompt and awaits user input.
    
        .. warning::
    
            May or may not randomly format your hard drive during full moon.
        """
    

    In other languages (e.g., Haskell), some side effects can be represented as types (monads) and statically checked. For example, there is an analogous function to your getText in the standard library that has a type

    readFile :: FilePath -> IO String
    

    as opposed to just String, which would mean that it does some pure operation on its parameter (that is, always returns the same output for the same input and does not have side effects).