SoFunction
Updated on 2024-11-17

Why write type annotations in python coding?

1. Background

Let's talk about why type annotations are highly recommended in Python coding ?

Python for beginners is very good to start, the reason is that the high degree of encapsulation of the underlying principles of the computer and the characteristics of the dynamic language makes Python very comfortable to use. But this "comfort" comes at a price. We may have heard a phrase describing dynamic languages, "Dynamic is fun for a while, but dynamic is fun all the time". Why is that? Dynamics does give us more flexibility and ability in coding, but dynamics brings more uncertainty and confusion, leading to later maintainers and even the author himself will produce a lot of maintenance pressure (you can imagine a complex system after several years of iteration, if most of the use of dynamic way to write the code looks like), as the saying goes, the greater the ability, the greater the responsibility, the need to exercise restraint;

Type annotations can help us in the maintenance and development, clarify the type of variables to reduce uncertainty and confusion, and comfortable use of variables. Here so much nonsense, mainly to enable readers to deeply realize the dynamics of the positive and negative direction of the "cool". Let's get to the point;

2. Usage

2.1. Python3's built-in type annotations

Built-in annotations can be exposed to everyone, but always feel very troublesome, resulting in the back of the easy to give up writing annotations, this is because of getting not positive feedback, see the following example:

a: str = "aa"
b: int = 1

# Parameters and returns are labeled with their type, so that the next call can be prompted
def example(a: str) -> str:
 return f"Hello {a}"

pirnt(example("world"))

# # Some simple labeling that doesn't seem to work, but what if it was named something meaningful?
User = str
Age = int
Answer = str

def say_hello(u: User) -> Answer:
 return f"Hello {u}"

print(say_hello("Shadow"))

The above briefly demonstrates how to use the built-in type annotations, but in fact such simple type annotations don't help us label variables very well; here's a typing module

2.2 Quick Start with the Typing Module

The typing module is the main protagonist of type annotations; Python runtime does not enforce function and variable type annotations, but these annotations are available to third-party tools such as type checkers, IDEs, static checkers, and so on. These third-party tools will prompt and correct errors as we code;

Below are some of the methods and use cases that are used on a daily basis for your reference:

import typing

# Custom type annotations
User = str
Age = int

# Define types with multiple type annotations
AnyStr = ('AnyStr', str, bytes)
a_str: AnyStr = "a"
a_bytes: AnyStr = b"a"



# Generic types, receive generic types and use them sparingly.
def example_1(a: ):
  print(a)


"""
The typing module is allowed to use subscripts to assist in labeling types
"""

# list, subscripts are attributes of the list
def example_2(a_list: [User]) -> [str]:
  pass


# Dictionary, first subscript is key, second is value
def example_3(a_dict: [User, Age]) -> [str, int]:
  pass


# metazoans, subscripted with the attributes of the metazoans
def example_4(a_tuple: [User] = None) -> [User]:
  pass


# Union, in some scenarios some of our parameters or return values are indeterminate, given at least one parameter type
def example_5(a_b: [str, int]) -> [str, int]:
  pass


# Optional, somewhat similar to Union, but with an extra None by default, given at least one parameter type
# e.g. Optional[str] is equivalent to Union[str, None].
def example_6(a: str) -> [str]:
  pass


# Tuple, when there is more than one return value, e.g. str, int, bool, float.
def example_7() -> [str, int, bool, float]:
  pass


# class, class itself is a type
class Action:

  up: str = "up"
  down: str = "down"

# Specify the parameters that require an action object
def example_8(action_obj: Action) -> Action:
  pass


# So that in some enumerated parameter scenarios, we can also use classes as a categorization of our enumerated parameters
def example_9(action_cls: Action) -> Action:
  pass

# If the above enumerated parameters don't work well for you, then you can still use custom type annotations to achieve this
Action = str
up: Action = "up"
down: Action = "down"

# Better support for enumerated parameter types in python 3.9
MODE = ['r', 'rb', 'w', 'wb']
def open_file(file: str, mode: MODE) -> str:
  pass

open_file('/some/path', 'r') # Normal
open_file('/other/path', 'typo') # It will indicate that the type is not legal


# Type, labeling different morphisms of the same type in some polymorphic class scenarios
class User: ...

class BasicUser(User): ...

class ProUser(User): ...

class TeamUser(User): ...

# Equivalent to [User, BasicUser, ProUser, TeamUser].
def make_new_user(user_class: [User]) -> User:
  return user_class()

The above dozen use case scenarios basically cover most of the daily coding, if there are some other needs can refer to the official documentation, which has a clear description of the above.

Docs: /zh-cn/3/lib…

3. Write at the end

I hope that the article can help you to understand and use of type annotations, and soon get out of the dynamic around the heart "cursing" and can not find the "mother" of the day.

The above is why should I write type annotations in python coding? For more information about python type annotations, please pay attention to my other related articles!