## What Type hints are annotations added to Python code that indicate the expected [[data type]] of a [[variable]], [[parameter]], or return value. Type hints are not enforced by any features of the language—you could write a type hint saying a parameter should take an `int` and give it a `str`, and Python would do nothing to stop you. Type hints are just hints that make intentions explicit; it is up to you to heed them. > [!info] Python is dynamically typed > As a dynamically typed language, Python doesn't have any mechanisms to enforce the data types of your type hints. In statically typed languages like C, you see similar type annotations except these are enforced by the compiler—they are requirements, not hints. ## Syntax A type hint follows a colon `:` after the [[identifier]] whose type is being hinted, e.g., `age: int = 16`. For return values, they appear with a `->` after the parameters, e.g., `def add(a: int, b: int) -> int:`. ## Why As type hints are not enforced by the interpreter, much of their usefulness comes from tools outside the language. Their main benefit is improving [[Readability|readability]] by making your code self-documenting. With proper type hints, there is no question about what data types your code works with and the burden on your [[Documentation|documentation]] is lessened. Editors and IDEs with autocomplete and error detection can use type hints to autocomplete smarter and detect type errors without running your code. You can also use tools like `mypy` to explicitly check for typing errors. While type hints increase the verbosity of your code, which can feel suffocating in smaller projects, the value of type hints grows with the scale of, and number of people working on, the project. ## Examples ### Variables Variables can be annotated with type hints. ```python age: int = 16 name: str = "Alyn" is_revising: bool = False ``` ### Parameters and return values: Subprogram signatures can be annotated to show parameter and return data types. ```python def calculate_grade(p_score: int, p_total: int) -> str: # returns a str percentage = (p_score / p_total) * 100 return "Pass" if percentage >= 40 else "Fail" ``` If a subprogram doesn't return anything, the hint should say it returns `None`[^1]: ```python def greet(name: str) -> None: print(f"hello {name}") ``` ### Collections Collections use square brackets to hint their data types: ```python scores: list[int] = [56, 78, 91] student_grades: dict[str, str] = {"Aisha": "Pass", "Tom": "Fail"} ``` ### Multiple possible types Values that could have multiple types list the possible types separated by pipes `|`. The pipe means the same thing as logical `OR`[^2]. According to the type hints, this function will either return a string or `None`. ```python def find_student(p_id: int) -> str | None: ... ``` ### Custom classes Custom classes can be used as hints, just like built-in types. By convention, we don't hint the `__init__()` return type[^3]. ```python class Student: def __init__(self, p_name: str): self.__name: str = p_name def enrol(p_student: Student) -> None: ... ``` ### Class and subprogram Putting it all together: ```python class Student: def __init__(self, p_name: str, p_grades: list[int]): self.__name: str = p_name self.__grades: list[int] = p_grades def average(self) -> float: return sum(self.__grades) / len(self.__grades) def get_name(self) -> str: return self.__name def top_student(p_students: list[Student]) -> Student | None: if not p_students: return None return max(p_students, key=lambda student: student.average()) students: list[Student] = [ Student("Aisha", [78, 82, 91]), Student("Tom", [55, 60, 58]), ] best: Student | None = top_student(students) if best is not None: print(f"{best.get_name()} has the highest average.") ``` Judge for yourself where type hints are most useful. I personally like them in subprogram signatures but find them redundant in variable declarations. ## Notes [^1]: This is because, in Python, every subprogram returns _something_. If no explicit return value is given in the code, it returns the value `None`. [^2]: In lots of languages (e.g., C, JavaScript), a double pipe `||` is how logical `OR` is written. [^3]: See [[Python quirks#Constructors return `None`, not the object]].