Python's Self Type: How to Annotate Methods That Return self

Python's Self Type: How to Annotate Methods That Return self

by Harrison Hoffman Reading time estimate 20m intermediate best-practices python

Have you ever found yourself lost in a big repository of Python code, struggling to keep track of the intended types of variables? Without the proper use of type hints and annotations, uncovering variable types can become a tedious and time-consuming task. Perhaps you’re an avid user of type hints but aren’t sure how to annotate methods that return self or other instances of the class itself. That’s the issue that you’ll tackle in this tutorial.

First, though, you’ll need to understand what type hints are and how they work. Type hints allow you to explicitly indicate variable types, function arguments, and return values. This can make your code more readable and maintainable, especially as it grows in size and complexity.

You specify variable and function argument types with a colon (:) then the data type, while return value annotations use a dash–greater than symbol (->) then the return type. To see an example, you can write a function that accepts as input the number of pies that you bought and the price per pie then outputs a string summarizing your transaction:

Language: Python
>>> def buy_pies(num_pies: int, price_per_pie: float) -> str:
...     total_cost = num_pies * price_per_pie
...     return f"Yum! You spent ${total_cost} dollars on {num_pies} pies!"
...

In buy_pies(), you type the num_pies variable with int and price_per_pie with float. You annotate the return value with the str type because it returns a string.

Types and annotations in Python usually don’t affect the functionality of the code, but many static type checkers and IDEs recognize them. For instance, if you hover over buy_pies() in VS Code, then you can see the type of each argument or return value:

You can also use annotations when working with classes. This can help other developers understand which return type to expect from a method, which can be especially useful when working with complex class hierarchies. You can even annotate methods that return an instance of the class.

One use case for types and annotations is to annotate methods that return an instance of their class. This is particularly useful for class methods and can prevent the confusion that arises when working with inheritance and method chaining. Unfortunately, annotating these methods can be confusing and cause unexpected errors. A natural way to annotate such a method is to use the class name, but the following won’t work:

Language: Python
# incorrect_self_type.py

from typing import Any

class Queue:
    def __init__(self):
        self.items: list[Any] = []

    def enqueue(self, item: Any) -> Queue:
        self.items.append(item)
        return self

In the example above, .enqueue() from Queue appends an item to the queue and returns the class instance. While it might seem intuitive to annotate .enqueue() with the class name, this causes both static type checking and runtime errors. Most static type checkers realize that Queue isn’t defined before it’s used, and if you try to run the code, then you’ll get the following NameError:

Language: Python Traceback
Traceback (most recent call last):
  ...
NameError: name 'Queue' is not defined

There would also be issues when inheriting from Queue. In particular, a method like .enqueue() would return Queue even if you called it on a subclass of Queue. Python’s Self type can handle these situations, offering a compact and readable annotation that takes care of the subtleties of annotating methods returning an instance of the enclosing class.

In this tutorial, you’ll explore the Self type in detail and learn how to use it to write more readable and maintainable code. In particular, you’ll see how to annotate a method with the Self type and make sure your IDE will recognize this. You’ll also examine alternative strategies for annotating methods that return a class instance and explore why the Self type is preferred.

How to Annotate a Method With the Self Type in Python

Due to its intuitive and concise syntax, as defined by PEP 673, the