Code Documentation And Static Typing in Python (vs JavaScript)

Another episode of Python For JavaScript Developers, a series of side-by-side between Python and JavaScript. In this installment: code documentation and static typing in Python.

Code Documentation And Static Typing in Python (vs JavaScript)

You’re a JavaScript developer, you want to know how to document and add static types in Python before jumping in. Are you? If so, this article is spot on. Enjoy!

In this episode:

  • Code Documentation with reStructuredText
  • Gradual typing with reStructuredText
  • Static typing in Python with mypy

Requirements

To follow along you need at least a basic understanding of JavaScript and a bit of “types” theory. If you don’t know what a type is check out Python data types and then come back!

Later in the post we’ll create a Python virtual environment for installing mypy so make sure to have familiarity with the command line.

The complete series

Missed an episode of Python For JavaScript Developers? Here we go:

Code documentation in Python with reStructuredText

In JavaScript we use JSDoc to add documentation for classes, functions parameters, function return values, and so on. Here’s an example of a JSDoc annotated function:

/**
 * Raises a number to exponent
 * @param {number} value - The base to raise
 * @param {number} exponent - The exponent
 * @return {number} - The exponent power
 */
function poooow(value, exponent) {
  return value ** exponent;
} 

Notice the type “number” on parameters and return. By adding these type hints you help the IDE to help you (no pun intended) when you use the function. The same concept applies to Python.

How to add code documentation in Python? Actually there are many ways, but reStructuredText is one of the recommended approaches. reStructuredText is plaintext, and can be embedded into Python Docstrings.

A Docstring is just a string that happens to live inside Python functions or modules, and acts as documentation for your code. To give you a bit of context here’s an example of Docstring inside a Python function:

def string_repeat(string, count):
    """
    Repeats a string N times
    """
    return string * count

reStructuredText comes into play when you decide to add documentation for parameters and return values. Here’s our example, enriched with reStructuredText:

def string_repeat(string, count):
    """
    Repeats a string N times
    :param string: The string to repeat
    :param count: The number of repeats
    :return: The string repeated N times
    """
    return string * count

I know, documentation may look redundant, but reStructuredText shines if you decide to dip your toes into static typing. First things first, a bit of type theory.

A bit of type theory: dynamic typing vs static typing

Python and JavaScript are dynamic languages. That is, they give almost absolute freedom to programmers. In JavaScript for example a variable might contain a string at first, and later be changed to contain a boolean. The JavaScript engine won’t complain. (However, you can prevent re-assignment with const).

Python does the same. Consider the following example:

>>> name = "Caty"
>>> name = True
>>> print(name)
True

The variable name has a string type at first, but then gets changed to a boolean type. Most of the times with self-discipline you can tame dynamic typing and sleep at night. But there are situations where dynamic typing might not be allowed, or too risky (think of financial systems).

Opposed to dynamic typing, static typing means assigning a fixed type to every variable, so that becomes more difficult to change a type by accident.

However, static typing is a different beast, and learning it might be a painful process, especially for beginners.

Luckily, both in JavaScript and Python we can use documentation for “gradual typing”.

Let’s see in the next section how to add types with reStructuredText in Python.

Gradual typing in Python with reStructuredText

With reStructuredText you can get your feet wet with static typing in Python without adding actual types in the code.

In addition to common tags like “:param” and “:return”, reStructuredText has also :type and :rtype, used respectively for adding types to parameters and return values.

If you want to follow along with the code it is a good time to create and activate a Python virtual environment (in the next section we’ll need to install mypy too):

mkdir docs_and_typings && cd $_

python3 -m venv venv

source venv/bin/activate

To illustrate the example create a new file named my_str_utils.py in your project folder with the following function:

def string_repeat(string, count):
    """
    Repeats a string N times
    :param string: The string to repeat
    :param count: The number of repeats
    :return: The string repeated N times
    """
    return string * count

Now let’s add types in reStructuredText with :type and :rtype:

def string_repeat(string, count):
    """
    Repeats a string N times
    :param string: The string to repeat
    :type string: str
    :param count: The number of repeats
    :type count: int
    :return: The string repeated N times
    :rtype: str
    """
    return string * count

This is gradual typing: you have now a typed function, without the cognitive load of a static type system. Most IDE are able to read reStructuredText types, in PyCharm for example you’ll be warned when a parameter’s type don’t match the actual argument’s type:

Gradual typing in Python

Also in Pycharm, by clicking on a function’s name and pressing Ctrl+Q (or F1 on MacOs) you can get actual type hints:

Gradual type hints can be converted by real type hints in Pycharm

Those hints are the same as the “real type hints” that we’re going to see in the next section (by the way, PyCharm is able to infer the return type even without :rtype, just by inspecting the return statement).

In JavaScript you can get the same effect with JSDoc types.

Static Typing in Python and JavaScript

The definition of gradual typing I gave earlier is a bit blurry. It is true that you can get into static typing through code documentation in docstrings.

But a stricter interpretation of gradual typing is: the process of adding real types gradually in your code. And by types now I mean type annotations that can be checked with a tool.

JavaScript always had TypeScript: a layer on top of JavaScript that you can plug into. TypeScript is an actual language on its own, but can fit into any JavaScript codebase, gradually.

The same holds true for Python: in recent years it gained an optional type system based on “type hints”. Here we’ll see Python type hints from 30,000 feet. Once grasped the basics hopefully you’ll be able to apply types to your Python code. Let’s take string_repeat from the previous section:

def string_repeat(string, count):
    """
    Repeats a string N times
    :param string: The string to repeat
    :type string: str
    :param count: The number of repeats
    :type count: int
    :return: The string repeated N times
    :rtype: str
    """
    return string * count

This time we’re going to strip out reStructuredText types:

def string_repeat(string, count):
    """
    Repeats a string N times
    :param string: The string to repeat
    :param count: The number of repeats
    :return: The string repeated N times
    """
    return string * count

At this point the IDE will loose all the hints. But if you remember from previous section “real” type hints are like “parameter: type”, so it should be easy to add them in the function signature:

def string_repeat(string: str, count: int):
    # omit

The style is similar to that used by TypeScript.

You can also type the return value with -> type:

def string_repeat(string: str, count: int) -> str:
    """
    Repeats a string N times
    :param string: The string to repeat
    :param count: The number of repeats
    :return: The string repeated N times
    """
    return string * count

Once the code is type annotated you can check its correctness with a tool called mypy, our topic for the next section.

Checking types with mypy

mypy is a type checker for Python. To install mypy in your test project make sure to activate the virtual environment and the run:

pip install mypy

Open up my_str_utils.py and try to use string_repeat with a float instead of an int:

def string_repeat(string: str, count: int) -> str:
    """
    Repeats a string N times
    :param string: The string to repeat
    :param count: The number of repeats
    :return: The string repeated N times
    """
    return string * count


string_repeat('hello', 15.6)

The IDE will warn you, but you can also use mypy against the Python script:

mypy my_str_utils.py

mypy will warn you as well:

my_str_utils.py:11: error: Argument 2 to "string_repeat" has incompatible type "float"; expected "int"
Found 1 error in 1 file (checked 1 source file

Opposed to TypeScript, which compiles to “vanilla” JavaScript, mypy does not produce any code, it just checks its types.

Wrapping up

JSDoc is the standard for documenting JavaScript, while Python uses more styles which seem to depend on the developer’s personal preference. reStructuredText is one of the most used. Google and other styles for documenting Python are also popular and work well with most IDEs.

Code documentation can also be used for gradual typing in the form of type documentation that is read by IDEs and editors.

Both JavaScript and Python can be augmented with an optional type layer. JavaScript has TypeScript, a typed language which compiles down to “vanilla” JavaScript.

Python has optional type hints, which can be checked with tools like mypy. mypy does not compile anything, types stay there alongside with the code.

However, type hints are not a replacement for documentation. If it’s not possible to infer the logic of a function just by reading the listing then code documentation could help.

Thanks for reading and stay tuned for more!

Resources

To learn more about Python type hints read these PEPs:

If you need a refresher on JSDoc and TypeScript check out:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.