Enhancing Readability of Python Code via Annotations

Author:Murphy  |  View: 26982  |  Time: 2025-03-22 22:06:08

PYTHON PROGRAMMING

Code annotations work like highlighting with markers. Photo by Mitchell Luo on Unsplash

Code clarity is both a virtue and a necessity. If you write clear and readable code, other developers will be able to understand it, users will understand how to use it, and even the future you will appreciate it, as with time most of us forget details of the code we implement. Code clarity gets more and more important with the increasing size of the project and the codebase.

Among programming languages, Python offers very readable code. Or rather, it can offer very readable code – but you need to know how to make it readable. I'd even say that Python gained such popularity for several reasons, an important of them being code readability. Hence, it's our responsibility to write good Python code. And for this, we need tools.

Code clarity is both a virtue and a necessity.

There are plenty of tools to improve Python code quality. First of all, we need to write code that meets the standards of good Python code, and these are provided by PEP 8:

PEP 8 – Style Guide for Python Code | peps.python.org

It's a lot of reading and a lot of rules, but don't worry: learn them one by one, step by step, and sooner or later you'll find yourself following the rules, at least most of them. After a while you won't need to return to PEP 8 to use the standards it presents – you'll simply know how to write good, idiomatic Python code.

Styling guide isn't the only tool to enhance code readability. Today, I'd like to discuss another one, so-called code Annotations. Be aware that it's a tool to improve the readability of code under development rather than production code. Annotations are phrases, written in uppercase, that inform the code developer, maintainer or, less frequently, user that there's something they should pay close attention to.

Among the most common examples are TODO, NOTE, FIXME, BUG, and REVIEW. There are more annotations, and you can create custom ones if you feel a need.

Put shortly, code annotations are more than mere comments: they are a standardized way to highlight areas of code that require attention or improvement.

When you're working in an advanced Integrated Development Environment (IDE) like Visual Studio Code (VSC), annotations can significantly improve your coding. They facilitate a smoother development process, not by changing how code runs, but by altering how we interact with it.

I'm a VSC user. If you are, too, you can install a TODO Highlight extension (currently, TODO Highlight v2 is also available), and it will highlight your annotations so that it will be difficult to miss them. We'll discuss how to use this extension later in this article. If you don't use VSC, check whether your IDE or editor offers annotation highlighting. Even if it doesn't, however, you can use annotations: they will only be a little less visible in a raw version than with highlighting, and so they'll remain a powerful tool for improving code quality and team communication.

This article explores the usefulness of code annotations in Python development. I'll also show you how annotation highlighting works in Visual Studio Code.

Annotations

In Python, the words "annotation" and "annotate" can have two different meanings. First, to annotate a Python function primarily refers to adding type hints to it. Consequently, a function's annotation consists of its signature enhanced with type hints.

In this article, we're discussing the other meaning, that is, annotating code via adding words in uppercase pointing out significant aspects of the code.

I know this can be confusing. For instance, a sentence like "Please annotate the foo() function" can have two meanings:

  • Add type hints to the foo() function. This is a typical meaning of this sentence.
  • Add annotations to the foo() function. This can be confusing, so it's better to use a different wording, like "Please add this-or-that annotation to the foo() function's docstring" (or to its code).

Thus, always remember to use the words "annotation" and "annotate" in a context that will clarify the meaning.

Types of annotations

I use two forms of annotations:

  • Annotations typically used in programming; I use them much more often.
  • Custom annotations, reflecting individual needs to annotate a particular code fragment; I use them only sparingly.

These are typical code annotations:

  • TODO: Indicates tasks, improvements, or features that need to be implemented. It's one of the most common annotations, probably the most frequently used.
  • NOTE: It's used to highlight an important piece of information about a module, class, method or function (if located in a docstring) or about the code fragment before which it's placed (if used as an inline comment). It can be something related to the implementation, usage, or context that developers should be aware of.
  • BUG: Marks a bug within the code. It should be accompanied by a description of the bug and other significant information. This can be a specific bug that you know of, or an indication of a code fragment that has an unknown bug.
  • FIXME: Marks an issue in the code that needs to be fixed. It's different from BUG, however. BUG indicates an actual mistake in the code while FIXME rather indicates problems that aren't mere bugs but could be related to, e.g., lousy or inefficient implementation, incorrect implementation of the business logic, unclear code.
  • REVIEW: Signals that a reviewer should pay attention to a particular piece of code. Hence, the context should be clearly explained.

In addition to these, from time to time I like to use two custom code annotations:

  • THINK ABOUT THIS: Marks a code fragment or an idea (if located in a docstring) that requires in-depth thinking, for whatever reasons. Most often, this won't have anything to do with the technical side of the code but with the business logic.
  • RECONSIDER: Signals a code fragment to be reconsidered. You must provide reasons and/or ideas, since without explanation, this annotation could be more confusing than helpful.

As u rule, explain annotations in a clear way. As explained for RECONSIDER, unexplained annotations can be confusing. Remember that these explanations should be short and to the point. Annotations definitely do not constitute a tool for lengthy discussions.

We often say that, for example, TODO is an annotation, but in fact, an annotation is an annotation tag, like TODO, with its explanation.

Whom are annotations directed to?

We can also group annotations by intended recipients. They can be directed to:

  • Developers: The most common use of annotations is during development, to inform the development team about important aspects of the code.
  • Code users: It's a less frequent use case. Such annotations inform the users about something essential, like deprecation, change in behavior, a critical piece of information about the logic a function implements, and so on. Note that such annotations convey information of different sort than those for developers. These annotations are located in the documentation, not directly in the code.
  • Maintainers: It's the least frequent type of annotations, and one you should use with great care. When a code base is in production and the code is used by users, they may find it confusing to see such annotations in code documentation. Hence, if you have to use an annotation for code maintainers, don't do this in documentation that's directed to users, like docstrings. Do it as inline comments or in maintaining documentation, if there is such.

We can't directly correlate these three types of annotations with those discussed above. This is because you can use NOTE for code developers, maintainers and users, but you won't direct a FIXME annotation to code users.

The point is to always keep in mind for whom you're annotating the code. Annotation tags themselves aren't enough to achieve this: when you see a NOTE annotation tag, you cannot know from the tag itself whom it's directed to. The annotation's explanation explains the context.

I know this can be confusing. Sure, we could try to differentiate between these three types of annotations, for instance by differentiating the annotation tags themselves. Nevertheless, this would make annotations longer and less readable. I'm not aware of any such attempts, and I myself haven't tried to do anything even remotely similar to this.

Therefore, whenever you add a code annotation, try to make it as clear as possible – and this also means making it clear whom you're annotating the code for.

What are annotations used for?

I consider the purpose of annotations threefold:

  1. They enhance team communication. Adding an annotation to code helps inform other developers that something's happening. For example, we can inform that we're going to do something (TODO) in the next task or release. That way, code reviewers will not have to wonder why you haven't done this since they'll learn this directly from the code (another approach is to write this in the pull request – but, interestingly, many reviewers don't read them…).
  2. Using annotations is a mnemonic technique for developers, where annotations work as mnemonic tags. They go beyond merely highlighting areas of the code; they help developers quickly identify and remember issues, but also navigate to the corresponding areas for future consideration. Not only are annotations easy to spot, but they are also easy to find using the text-finding tool of your IDE or editor.
  3. They offer a place for discussion. Code is seldom a good place to discuss its intricacies, but when you annotate code accordingly and discuss a matter in question, this becomes a helpful tool. Remember, however, to keep such discussions as short as possible.

While the first of these purposes represents teamwork, the second one shows that annotations can also be used when working alone. I do use them that way, and I consider them a great tool to remind me what to do (TODO) and to discuss important aspects of the code (e.g., NOTE, FIXME, RECONSIDER, but also any other annotation, depending on the issue).

When I'm part of a team, annotations constitute perhaps the most efficient way of pointing out something in the code, but also in the documentation. Of course, you can use other tools, like direct communication with developers, comments in a repository, and the like. More often than not, however, they won't be as effective as annotations, which are precisely where they should be and thus are difficult to overlook.

How long should an annotation be?

Annotations should be as short as possible to convey the message. Try not to break this rule, although as usually, there can be exceptions.

When an annotation becomes too long, it stops looking like an annotation. Readers can get confused or even lose track of whether they're still reading the annotation itself.

So, the shorter an annotation, the better. Sentence fragments are acceptable, although complete sentences generally improve understanding.

What does the presence of annotations mean?

Whenever you see annotations in code – especially when they are abundant – more often than not this means the code is under development. This is because annotations are mainly a development tool.

There are, of course, exceptions. Sometimes you may find annotations to be used by code maintainers, so their presence doesn't have to mean the code is still under development. Also, you may find annotations directed to code users, such as warnings about future releases or changes in code behavior.

It isn't, however, a good practice to overuse annotations in production code. A few that convey significant information are fine, but production code in which you see annotations in every other function or class method doesn't look well. When I see production code full of annotations, I feel the application was deployed immaturely, whether or not it was a thoughtful decision.

Where to use annotations?

Code annotations are difficult to overlook because they are added directly to the code. In addition, when you use an IDE highlighter, they are even more visible, sometimes almost too visible. Some annotations, like BUG, should be like that; you want BUG to scream that there is a bug in the code, not to whisper. I'll show you an example soon.

I use annotations in four main locations:

  • Module docstrings: I use mainly TODO in module docstrings, but also NOTE. This is because a module docstring is not where you want to annotate a bug or to explain why something is wrong with a particular function. Hence, I annotate module docstrings relatively seldom. When I do, I usually put annotations at the end of module docstrings, but it's not a rule.
  • Class/method/function docstrings: This is one of the two most common locations of annotations, along with inline comments (described below). It's a typical location for any annotation that's on a class/method/function level. It's the best place to annotate that the function may implement the business logic incorrectly; or that it could be more performant; or that it's a bottleneck; or that it has a bug somewhere, but it's still unclear where. When an annotation refers to the method's/function's code, I typically put it at the end of the corresponding docstrings. Again, it's not a rule: when an annotation refers to what the docstring conveys, it can sometimes fit better inside the docstring, not at its end.
  • Inline comments: The first sentence above, for class/method/function docstrings, applies here, too. Inline comments, however, are used at a code level. Annotations are typically put right before the code fragments they refer to.
  • Documentation files: While this is a much less frequent use case, from time to time I use annotations in documentation files, mainly README. I do this, for instance, to mark that a new section should be added, or that a particular section is incomplete or too detailed, or to stress that a particular functionality is not yet working as planned. In documentation files, I put annotations where they belong – so where the corresponding aspect is (or should be) discussed.

I decided to include the last item although many would claim that a documentation file is not truly part of the code. While this can be true, README and similar files usually constitute an integral part of code repositories. Annotating such a file is a great way of attracting attention, so why shouldn't we avoid using annotations in documentation files?

As for docstrings, if you need to add to a docstring an important piece of information for the user, you don't have to need an annotation. Docstrings constitute an important element of documentation, so whatever they contain should be important. Thus, limit using annotations for users to situations in which the information is of utmost importance and must stand out.

Example

Let's consider the following script as an example. You'll see the code is short and doesn't do anything useful – but it will perfectly serve our needs of presenting how highlighting annotations work. I'll use many more annotations than I've used in actual code, but I can imagine doing so, for instance, when designing a module.

This is the raw version of the code, as seen in a text file:

"""This is an example annotation module.

TODO: Install the extension.
RECONSIDER: Should the different annotations be formatted
    the same way?
"""
def foo(x, y, z):
    """Create a tuple of three elements.

    Examples:
    >>> foo(1, 2, 3)
    (1, 2, 3)
    >>> foo('1', '2', 3)
    ('1', '2', 3)

    TODO: Use *args to allow for more arguments.
    RECONSIDER: Is this function really important?
    """
    return x, y, z

def bar(x: int) -> tuple[int, float, str]:
    """Create a three-type int tuple.

    TODO: Explain what "a three-type int tuple" is.
    TODO: Add doctests.
    """
    # FIXME: Remove the direct call to tuple:
    return tuple(x, float(x), str(x))

def baz(x: float) -> tuple[int, float, str]:
    """Create a three-type int tuple from float.

    TODO: Explain what "a three-type int tuple" is.
    TODO: Add doctests.
    """
    # BUG: x is float, so it should be converted to
    #    a float (see the first position of the tuple)
    # FIXME: Remove the direct call to tuple:
    return tuple(x, x, str(x))

I used here four types of annotations, three typical (TODO, NOTE, FIXME and BUG) and one custom (RECONSIDER).

Note that even here, without code highlighting, annotations stand out. Sure, the code is full of them, which makes the annotations stand out more than with only a few annotations in a script. Still, In think you'll agree that annotations are easy to spot and difficult to overlook even in a text file without any highlighting.

Let's see the script using typical Python code highlighting:

"""This is an example annotation module.

TODO: Install the extension.
RECONSIDER: Should the different annotations be formatted
    the same way?
"""
def foo(x, y, z):
    """Create a tuple of three elements.

    Examples:
    >>> foo(1, 2, 3)
    (1, 2, 3)
    >>> foo('1', '2', 3)
    ('1', '2', 3)

    TODO: Use *args to allow for more arguments.
    RECONSIDER: Is this function really important?
    """
    return x, y, z

def bar(x: int) -> tuple[int, float, str]:
    """Create a three-type int tuple.

    TODO: Explain what "a three-type int tuple" is.
    TODO: Add doctests.
    """
    # FIXME: Remove the direct call to tuple:
    return tuple(x, float(x), str(x))

def baz(x: float) -> tuple[int, float, str]:
    """Create a three-type int tuple from float.

    TODO: Explain what "a three-type int tuple" is.
    TODO: Add doctests.
    """
    # BUG: x is float, so it should be converted to
    #    a float (see the first position of the tuple)
    # FIXME: Remove the direct call to tuple:
    return tuple(x, x, str(x))

I used the Medium Story Editor and its code highlighter. As you can see, it highlights some annotations by formatting them using the bold font – but only for inline comments, not for docstrings.

Check whether your IDE or editor offers annotation highlighting. If it does, check how it works and try to configure it. This is an important task, as sometimes annotation highlighting can be too inconspicuous. If you overdo with highlighting, you may have problems with focusing on the code itself, as annotations can get too visible, too vivid for you to ignore them.

This is an interesting thing about code annotations: they aim to attract attention, but at the same time you need to be able to forget them after writing or reading them. If you can't ignore their presence, they will distract you. Thus, you need to find the right balance between their noticeability and invisibility during code development.

As for invisibility during code development, the idea is to be able to ignore the annotation after writing it, but also after reading it. So, if a developer reads the annotation and grasps what's it about, they should be able to ignore it while working on the code – so that it doesn't distract them.

When you use a professional IDE or a text/code editor with advanced features, you may check out whether it offers highlighting annotations – and when it does, try to find your annotation highlighting preferences by configuring the tool.

Hence, remember to find configuration of annotation highlighting that will work for you – but also for your teammates, as you're seldom the only person to use annotations you write. Below, I'll show how to do this in Visual Studio Code. We'll focus on the technical part of the task, but later, in conclusions, I'm going to show you the configuration of annotation highlighting I like to use in my projects.

Annotations in Visual Studio Code

This section deals with Visual Studio Code:

Visual Studio Code – Code Editing. Redefined

Most likely, it's one of two the most frequently used Python IDEs, along with PyCharm. In the examples below, I'm using version 1.87.2 of Visual Studio Code.

VSC has a simple built-in annotation highlighter. This is how it works for our example script:

Screenshot of a Python script that shows built-in annotation highlighting in Visual Studio Code. Image by author

As you can see, VSC highlights standard annotations, irrespective of whether they're located in inline comments or in docstrings; here, these are TODO, NOTE, FIXME and BUG. The custom one, RECONSIDER, isn't highlighted.

This highlighting is quite subtle, as annotations are printed in a different font color. This often works, but we may want more:

  • Some annotations could be more visible than others, especially those critical ones, like BUG and FIXME.
  • We'd like to be able to highlight custom annotations, RECONSIDER in our example.

VSC has a tool for highlighting annotations, available as a TODO Highlight extension:

TODO Highlight – Visual Studio Marketplace

The tool a little outdated, as its last commit and release were in October 2021 – but it still works like a charm. Here's its repository.

So, let's install the extension:

Screenshot of the extension tab in VSC. Image by author

After clicking the "Install" button, we will be read to use version 1.0.5 of the extension. After moving to the tab with the script, we'll see this:

Screenshot of a Python script that shows how TODO Highlight works in its default configuration. Image by author

This time, the main annotations are much more visible – but not all. Clearly, TODO Highlight doesn't annotate all annotations; in our example, it doesn't annotate even BUG and NOTE, not to mention the custom one, RECONSIDER. Note that BUG and NOTE are still highlighted, but by the built-in VSC annotation highlighter.

Oftentimes, this can be enough. If you work in a team, all team members can install the extension and they'll see the same highlighting; this simplicity is an advantage of this approach. Other times, you may wish to extend the highlighter, and we'll do this in the next section.

Extending TODO Highlight with custom settings

If the default configuration is not enough for you or your team, you can extend it with custom settings. To do so, you need to edit the settings.json file, containing all user settings of your VSC installation.

If you want to check out the default settings, hit F1 and type "default settings"; you'll see this:

Looking for the file with the default settings of VSC. Image by author

You can read this file to learn the default settings, but don't edit it. User settings are located in a different file, so again hit F1, but this time type "user settings":

Looking for the file with user settings of VSC. Image by author

Choose the second option, that is, the JSON file with user settings. This is where we can add our custom settings for TODO Highlighter. To learn how to do it, you can read the DETAILS tab after opening the extension:

Screenshot of a fragment of the DETAILS tab of TODO Highlighter in VSC. Image by author

Let's start by adding the annotations that the extension doesn't highlight in our script: BUG, NOTE and RECONSIDER. This requires adding only a single line of code, which adds a new field to the JSON file, todohighlight.keywords:

Add custom annotations to TODO Highlighter in VSC. Image by author

Note that if you want the colon to be highlighted, you need to treat it as part of the annotation. If you don't do this, these annotations would be highlighted without the colon, which is a different behavior than that TODO Highlighter uses for the built-in annotations (see how TODO is highlighted in the next image).

Adding custom configuration to settings.JSON updates the extension immediately – you'll see that the annotations you've just added will be highlighted like in the screenshot above in the settings file and in the other open files.

Our script will now look as follows:

Example script with custom annotations highlighted by TODO Highlighter in VSC. Image by author

Better! And more often than not, this will be enough.

However, if you'd like to make one more step and configure a different color – actually, you can do more than that – you can do so in this very file. Let's make the BUG annotation stand out even more than it does now:

Customize the BUG annotation for TODO Highlighter in VSC. Image by author

We can see how it works: it's definitely very eye-catching, which was the whole point. Let's see how it works (only the baz() function):

Customized BUG annotation in action. The whole line is highlighted in the editor, from right to left. Image by author

When an annotation spreads into additional lines, as does BUG in the baz() function, they aren't highlighted at all. It's not a problem when only an annotation tag is highlighted, but here we want to highlight whole lines. That's why in baz(), both lines describing the bug are tagged.

At a first glance, the readers might think they're seeing two annotations (and two bugs). Hence, you can indent the second line to suggest it's the continuation of the previous line (not shown).

I showed you how to do this, but don't overdo with highlighting whole lines – or even simply don't do it. I consider this too confusing and distracting, and so I prefer highlighting annotation tags only, not whole lines.

You can vary the amount of settings for different annotations:

Different configurations in one settings file. Image by author

Here, we provide detailed settings for BUG, less detailed ones for FIXME, and define two annotations, NOTE and RECONSIDER, to be used with default settings.

The settings file shows how the annotations you added to it will be highlighted. Sometimes, however, you may think something's wrong as you don't see the changes you've just made. If this happens, it's enough to refresh the settings.JSON file (close it and reopen) or simply move to another tab after making a change, and the highlighter will work as expected.

Finally, this is how you can change the default style of TODO Highlight:

Setting the default configuration of a TODO Highlighter annotation in VSC. Image by author

Let's see what our example script looks like with the following configuration:

Custom configuration of TODO Highlight, saved in settings.JSON

This is the script:

Annotations after configuring them as shown above. Image by author

Conclusion and suggestions

Annotations can largely increase the readability of your code. You shouldn't overuse them, however, as you can achieve the opposite effect: the code can become even less readable, especially when you decide to use garish highlighting or write overly long explanations.

Annotations can largely increase the readability of your code. You shouldn't overuse them, however, as you can achieve the opposite effect.

Here are my suggestions on how to use annotations in Python code:

Use but don't overuse

First of all, use annotations but don't overuse them. Hence, use an annotation when you have something particularly significant to say, something that you cannot risk that can be forgotten or overlooked in the code.

At the same time, don't be afraid of using them: if only you don't use exaggerated highlighting, annotations can stand out just the way they should. Hence, use subtle highlighting – if any at all. To be honest, non-highlighted annotations work better than those with overdone highlighting.

Use annotations only for critical information

An annotation isn't just a mere comment. It's something you really have to say. It can be critical for the code, the project, code review or something else – but it must be critical.

Thus, don't treat annotations as a fancy way of saying something. Treat them as an efficient (in terms of visibility) way of conveying critical information.

Don't (over)use custom annotations

Try to avoid custom annotations, as your teammates may have problems with understanding the importance of such annotations. This doesn't mean you should avoid them by all means, but rather that you shouldn't use them when a regular annotation will do the job equally well. If it does, it actually does it better – as it'll be easier to notice and understand by others, and they won't have to stop for a couple of seconds to process the annotation and to think what's going on.

This doesn't mean you should avoid custom annotations by all means. I try not to use them in team projects, but when I work solo or in a small team of two or three developers and a need arises, sometimes I use a custom annotation.

When a situation seems to suggest that a custom annotation could do the job, try to find out a different way of achieving the aim. Think twice, then reconsider. If you still this is the best approach, think once more. Maybe talk to your teammates?

Or simply don't use custom annotations. You will save time and possible confusion. It's your call.

Never forget for whom you're creating each annotation

Annotations can be directed to the code' developers, maintainers and users. Sometimes an annotation is directed to only one of those group, other times to two or even three of them.

Therefore, you need to make it as clear as possible whom you're writing the annotation for. If you fail to do so, annotations may become confusing, making the code less rather than more readable.

Configure annotations with care

Don't overdo with fancy configuration of annotations. Code with many annotations formatted using visibly different formats will be visually cluttered.

In fact, some of the configurations we used for our example script were overly exaggerated, like the one that highlighted the whole line for BUG. Often, it can be best just to add new annotations and use the default settings for them, without configuring them individually.

If you do want to differentiate some annotations from the others – e.g., BUG and FIXME – it's enough to make minor changes, like here:

Example subtle user settings for TODO Highlight in VSC. Image by author

For VSC's extension TODO Highlight, this will mean annotating the most popular annotations: TODO, NOTE, REVIEW, BUG and FIXME. As the built-in TODO Highlight's annotation, TODO will have it's own color. BUG and FIXME will use the same style, different from that of the other annotations. We had to add "color": "white" to BUG, as otherwise a default color would be used for it, that is, black:

Example subtle user settings for TODO Highlight in VSC; BUG uses the default color (black). Image by author

This will give the following effect:

Example script with annotations configured using the settings from the previous image. Image by author

I think annotations can improve the readability of Python code and the efficiency of code development. One must be careful, however, not to overdo with them. Fancy configuration will likely make annotations too visible, adding unnecessary visual clutter to the code.

Therefore, don't be afraid of using annotations, but do so wisely. If they aren't too tempting for you, you should know how they work anyway, as chances are that sooner or later you'll work in a team that will use annotations, or you'll use an external package that will contain them.

Hence, you need to understand how annotations work. I hope this article helped you with this. Nevertheless, there is some level of subjectivity in annotations. You can see this subjectivity in this article, as I described how I see annotations and what I think about them. Others may have a different opinion about them.

Tags: Annotations Data Science Hands On Tutorials Python Readable Code

Comment