ProgrammingPython Developer

What are function annotations in Python, how does the type hints mechanism work, do they affect code execution at runtime, and what mistakes do careless developers make?

Pass interviews with Hintsage AI assistant

Answer

Background

Function annotations were introduced in Python 3.0, and the type hints mechanism is described in PEP 484, added in 3.5. This tool is designed for static code analysis, autocompletion, and improving readability — the standard library typing allows explicitly specifying expected types of variables, arguments, and return values of functions.

Problem

Python is a dynamic language where variable types can change at runtime, which can lead to errors only at the execution stage of the program. Annotations do not affect code execution, but when misused, developers have a false sense of "strict typing."

Solution

Type annotations are used for documentation, automatic checking with tools like mypy, pylance, pyright, and similar, as well as integration with IDEs. They are implemented with a colon after the argument name and an arrow after the parameter list:**

def greet(name: str, times: int = 1) -> None: for _ in range(times): print(f"Hello, {name}!") # Correct annotation for a function processing a dictionary from typing import Dict, List def transform(data: Dict[str, List[int]]) -> float: return sum(sum(lst) for lst in data.values()) / 10

Key features:

  • Annotations are not checked by the interpreter; they remain during code execution but do not affect it in any way
  • Relevant for large projects where it's important to understand interfaces and component interactions
  • For complex constructs, typing.List, typing.Dict, typing.Optional, typing.Union, etc., are needed.

Tricky Questions.

Can Python "automatically" check type conformity declared in annotations?

No! Type checking occurs only through external static analysis tools, such as mypy. At runtime, Python completely ignores the contents of annotations.

def f(x: int): return x * 2 print(f('oops')) # Type str, there will be no error!

Where are annotations stored and how can they be accessed at runtime, and why might this be needed?

They are stored in a special attribute __annotations__:

def add(x: int, y: int) -> int: return x + y print(add.__annotations__) # {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

This is used by third-party libraries for data validation, autogeneration of documentation, serialization, etc.

Can any variable be annotated, just functions, and what happens in the global scope?

Both local and global variables can be annotated with a colon; this also does not affect execution:

index: int = 0 def func(x: 'User') -> None: ...

Common Errors and Anti-patterns

  • Assuming that type hints are part of strict typing and strict type control
  • Forgetting that default values must be compatible with the declared type (even though this is not checked at runtime)
  • Incorrectly using complex types from typing (e.g., List<int> instead of List[int])

Real-life Example

Negative Case

In a corporate project, all developers began actively implementing annotations, yet the actual types of function arguments often did not match those specified. Python allowed these errors, and unexpected bugs only appeared deep within the business logic. There was no mypy configuration set up.

Pros:

  • Improved autocompletion and documentation

Cons:

  • There were remaining implicit errors, leading the cause far from the point of annotation

Positive Case

Using type hints and mandatory running of mypy in CI, as well as autogeneration of documentation from __annotations__:

Pros:

  • Minimum errors due to type inconsistencies
  • Improved quality of collaboration on the API

Cons:

  • There is overhead in maintaining the relevance of the annotations during refactoring