What is Gradual Typing in Python?

Fluent Python Notes About Gradual Typing and Variable Annotations

Introduction

Typing in Python works differently! Yes, it is very different as right now Python supports Four Type Systems.

  • First, we have the Duck Typing approach, this is the oldest and the most known approach for Python.

  • Then the Goose Typing which was introduced in Python 2.6 and with it we started using the isinstance checks.

  • The Static Typing, most commonly used in Java and C# languages.

  • Finally, the Static Duck Typing introduced in Python 3.8 with typing.Protocol type hints.

The Typing Map. Image from the Book Fluent Python

Since Python supported Duck Typing only before, it started with implementing the Gradual Typing System.

Gradual Typing in Python

A Gradual Type System is a system where:

  • Type Hint checking is Optional, i.e. Static Type Checkers like PyCharm’s Type Checker or MyPy Type Checker won’t emit warnings or errors when checking the code.

  • Errors are not caught at runtime, i.e. Type hints are used by type checkers, linters and IDEs only, in runtime values can be passed even if they are inconsistent with the intended type.

  • The Type Hints do not enhance the Performance of our apps.

  • Type Hints or what Python calls Annotations are optional.

How to Gradually Type a Function?

We will start with a simple example of a function that prints the number of occurrences of a word. This function will take 2 arguments: the number of occurrences and a word.

def number_occ(nb, word):
    if nb == 1:
       return f'1 {word}'
    occur_str = str(nb) if nb else 'no'
    return f'{occur_str} {word}s'

If we check this with a Linter or a Type Checker they will return no issues or warnings.

Now let’s gradually Annotate our function:

def number_occ(nb: int, word: str) -> str:
    if nb == 1:
       return f'1 {word}'
    occur_str = str(nb) if nb else 'no'
    return f'{occur_str} {word}s'

Now imagine we want to support plural irregular nouns, like for example mouse becomes mice, a Default Parameter “plural” might solve this:

from typing import Optional

def number_occ(nb: int, word: str, plural: Optional[str] = None) -> str:
    if nb == 1:
       return f'1 {word}'
    occur_str = str(nb) if nb else 'no'
    if not plural:
       plural = singular + 's'
    return f'{occur_str} {plural}'

print(number_occ(1, “mouse”)) 
# 1 mouse
print(number_occ(10, “mouse”, “mice”))
# 10 mice

So How are Types Decided at Runtime?

Types in Python are defined by the supported operations! At runtime, the set of operations conducted with an object is what determines the type of the object itself.

For example:

def sum(a, b):
    return a + b

Multiple types could support this “+” operation from the primitive types like int and float to the more elaborate ones such as str, list, array and any sequence really.

The Python runtime will accept any objects as the arguments of the sum function, the computation or the operation itself could raise a TypeErrorbut that’s not the point here. Python will determine the type of the object as long as it accepts operations.

In general, in a gradual type system we have an interplay of two typing systems:

  • The Duck Typing: This is the view adopted by Python originally, objects have types but variables, when declared, are untyped, i.e. It doesn’t matter what the type of the object is as long as it supports the operations we want.

  • Nominal Typing: This is the view adopted by statically typed languages such as Java, C# and C++. Objects and Variables are Typed, and the Static Type Checker will enforce certain rules.

Here’s an example that shows both duck typing and nominal typing in action:

class Bird:
    pass

class Duck(Bird):
    def quack(self):
        print('Quack!')

def alert(birdie):
    birdie.quack()

def alert_duck(birdie: Duck) -> None:
    birdie.quack()

def alert_bird(birdie: Bird) -> None:
    birdie.quack()

daffy = Duck()

alert(daffy)
# Quack!
alert_duck(daffy)
# Quack!
alert_bird(daffy)
# Quack!

#They all quack! Python Executes all

Conclusion

The Gradual Typing System is one of the systems adopted by Python, and these systems are what allow Python to have immense flexibility and stability at the same time. You can read more about Typing Systems and Type Hinting in Python here:

Did you find this article valuable?

Support Nadim Jendoubi by becoming a sponsor. Any amount is appreciated!