python-course.eu

3. Type Annotations Decorators

By Bernd Klein. Last modified: 13 Jul 2023.

This chapter of our Python course is about decorators but only about how to annotate decorators with type annotations or type hints. If you want to learn all about decorators, we highly recommend our tutorials on Python decorators:

You will learn in this chapter how to annotate decorator functions, allowing you to enhance code readability and maintainability. We provide explanations of the syntax and demonstrate the benefits of using type annotations in your decorators.

Two real decorators at work

Our first example will be a decorator for calling how often a function has been called.

You have to know a few things about the Jupyter-Notebooks cells (ipython shell) to understand how we save programs and start mypy: With the shell magic %%writefile example1.py we can write the content of a cell into a file with the name example1.py. In IPython syntax, the exclamation mark (!) allows users to run shell commands (from your operating system) from inside a Jupyter Notebook code cell. Simply start a line of code with ! and it will run the command in the shell. We use this to call mypy on the Python file.

%%writefile example.py

from math import sin, cos

from typing import Callable

def call_counter(func: Callable) -> Callable:
    def wrapper(*args, **kwargs):
        wrapper.counter += 1
        return func(*args, **kwargs)  
    setattr(wrapper, 'counter', 0)
    # not possible:
    setattr(wrapper, 'counter', 0)
    return wrapper

sin = call_counter(sin)
cos = call_counter(cos)

@call_counter
def add(a: int, b: int) -> int:
    return a + b

result = add(5, 3)
print(result)

print(sin(3.5))
print(sin(36.5))
print(getattr(sin, 'counter'))

OUTPUT:

Overwriting example.py

We can see that this Python code above works, if we let it run with Python:

!python example.py

OUTPUT:

8
-0.35078322768961984
-0.9317168878547055
2

We can also see that it works with mypy as well:

!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

The Future: PEP-612 with ParamSpec and Concatenate

%%writefile example.py
from typing import Callable, Any, Dict

def memoize(f: Callable) -> Callable:
    memo: Dict[Any, Any] = {}
    def helper(x: Any) -> Any:
        if x not in memo:            
            memo[x] = f(x)
        return memo[x]
    return helper
    

def fib(n: int) -> int:
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

fib = memoize(fib)

fib(10)

OUTPUT:

Overwriting example.py
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here