Navigation
Recherche
|
Get started with Python type hints
mercredi 18 juin 2025, 11:00 , par InfoWorld
Python is best thought of as a dynamic but strongly typed language. Types aren’t associated with the names of things, but with the things themselves.
This makes Python flexible and convenient for developers because you don’t have to rigorously define and track variable types if you’re just throwing together a quick-and-dirty script. But for bigger projects, especially libraries used by third parties, it helps to know which object types are associated with which variables. For some time now, Python developers have been able to “annotate” names with type information. With Python 3.5, type hints officially became part of the language (see PEP 484). Using a linter or code-checking tool, developers can check the consistency of variables and their types across a codebase. We can perform static analysis on code that would previously have been difficult or impossible. All this is done ahead of time, before the code runs. In this article, we’ll explore some basic examples of Python type hinting, including the lazy evaluation of annotations, now the default in Python 3.14. But first, we’ll clear up a common misunderstanding about what type hinting is and isn’t used for. How Python uses type hints (it doesn’t) A key misconception about Python type hints is how they’re used. Python type hints are not used at runtime, at least not ordinarily. In fact, when your program runs, all the type information you’ve provided is ignored (although it is preserved in some form; more on this later). Python type hints are used ahead of time, by the type-checking system; for instance, in your editor or IDE. In other words, Python’s type hints are for the developer, not the runtime. This may sound counterintuitive, especially if you’ve worked with languages where type declarations were not optional. But Python’s development team has gone out of its way to make clear that type hints aren’t a prelude to the core Python language becoming statically typed. They’re a way for developers to add metadata to a codebase, making it easier to perform static analysis during development. Some have speculated that Python type hinting could in time give rise to a fork of the language that is statically typed, perhaps as a way to make Python faster. In some ways, this fork has already arrived: Cython uses type hints (although mostly its own peculiar breed of them) to generate C code from Python, and the mypyc project uses Python’s native type hinting to do the same. Additionally, projects like Codon and Mojo use type declarations to create speedier, alternative dialects of Python. But these projects are more properly thought of as complements to the core Python language, rather than signs of where Python is headed. The main purpose of type hinting in Python is to give developers a way to make their code as self-describing as possible, both for their own benefit and that of other developers. The syntax of Python type hints Type hints in Python involve a colon and a type declaration after the first invocation of a name in a namespace. Here’s an example: name: str age: int name = input('Your name?') age = int(input('Your age?')) # alternatively, and more simply: name: str = input('Your name?') age: int = int(input('Your age?')) The first declarations of name and age with type hints ensure that any future use of those names in that namespace will be checked against those types. For instance, this code would be invalid: name: int age: int name = input('Your name?') age = int(input('Your age?')) Because we declared name as an int already, and input by default returns a string, the type checker would complain. The code would still run, however, since type hints are not used at runtime. Outside of the mis-hinted types, the code doesn’t do anything wrong. Python type-checking systems will, whenever possible, try to infer types. For instance, let’s say we used the following code without the previous type declarations: name = input('Your name?') age = int(input('Your age?')) In that case, the type checker would be able to infer that name is a string (since input() doesn’t return anything else), and that age is an int (since int() doesn’t return anything else). But the best results come from hinting each variable explicitly, since it isn’t always possible to make these inferences. Type hinting Python functions Python functions can also be type hinted, so that the values they accept and return are documented ahead of time. Consider the following code: greeting = 'Hello, {}, you're {} years old' def greet(user, age): return greeting.format(user, age) name = input('Your name?') age = int(input('How old are you?')) print(greet(name, age)) One ambiguity with this code is that greet() could in theory accept any types for user and age, and could return any type. We could disambiguate that confusion using type hints: greeting = 'Hello, {}, you're {} years old' def greet(user: str, age: int) -> str: return greeting.format(user, age) name = input('Your name?') age = int(input('How old are you?')) print(greet(name, age)) Given these type hints for greet(), your editor could tell you ahead of time which types greet() will accept when you insert a call to it into your code. Again, sometimes Python can automatically infer what types are returned from a function. But if you plan on using type hinting with a function, it’s best to hint everything about it—what types it takes in as well as what types it returns. Type hinting container objects Because objects like lists, dictionaries, and tuples contain other objects, we sometimes want to type hint them to indicate what kinds of objects they contain. Previously, Python required using the typing module to provide tools for describing such types. As of Python 3.9, they can be done natively in Python: dict_of_users: dict[int,str] = { 1: 'Jerome', 2: 'Lewis' } list_of_users: list[str] = [ 'Jerome', 'Lewis' ] Dictionaries are made of keys and values, which can be of different types. You can describe the types for a dictionary by hinting with dict[, ]. And you can describe the object type for a list with a hint in the format of list[]. Optional and Union types Some objects contain one of a couple of different types of objects. In these cases, you can use Union or Optional. Use Union to indicate that an object can be one of several types. Use Optional to indicate that an object is either one given type or None. For example: from typing import Dict, Optional, Union dict_of_users: Dict[int, Union[int,str]] = { 1: 'Jerome', 2: 'Lewis', 3: 32 } user_id: Optional[int] user_id = None # valid user_id = 3 # also vald user_id = 'Hello' # not valid! In this case, we have a dictionary that takes ints as keys, but either ints or strs as values. The user_id variable (which we could use to compare against the dictionary’s keys) can be an int or None (“no valid user”), but not a str. In Python 3.10 and higher, Union types can be hinted using the pipe character as follows, with no imports needed: dict_of_users: dict[int, int|str] = { 1: 'Jerome', 2: 'Lewis', 3: 32 } Also note that Optional can be hinted as a Union with None; for example, int|None instead of Optional[int]. This saves you from needing to import Optional if you’d rather keep things simple. Type hinting and classes To provide type hints for classes, just reference their names the same as you would any other type: class User: def __init__(self, name: str): self.name = name users: dict[int, User] = { 1: User('Serdar'), 2: User('Davis') } def inspect_user(user: User) -> None: print (user.name) user1 = users[1] inspect_user(user1) Note that inspect_user() has a return type of None because it only prints output and does not return anything. (We’d normally make such a thing into a method for the class, but it’s broken out separately here for illustration.) Also note we have the __init__ method type-hinted; name is hinted as str. We do not need to hint self; the default behavior with Python linting is to assume self is the class. If you need an explicit self hint for the class that refers to whatever class is in context, and you’re using Python 3.11 or later, you can import Self from the typing module and use that as a hint. Deferred evaluation of annotations When using type hints for custom objects, we sometimes need to provide a type hint for an object that hasn’t yet been defined. For some time, a common way to do this was to provide the object name as a string hint. This is a deferred annotation; it allows the object reference to be resolved lazily: class User: def __init__(self, name: str, address: 'Address'): self.name = name self.address = address # ^ because let's say for some reason we must have # an address for each user class Address: def __init__(self, owner: User, address_line: str): self.owner = owner self.address_line = address_line This approach is useful if you have objects with interdependencies, as in the above example. There is probably a more elegant way to untangle it, but at least you can provide ahead-of-time hints in the same namespace simply by providing the name of the object. However, a better way to do this is to use a feature called deferred evaluation of annotations. You can use a special import to change the way annotations are resolved at the module level: from __future__ import annotations class User: def __init__(self, name: str, address: Address): self.name = name self.address = address # ^ because let's say for some reason we must have # an address for each user class Address: def __init__(self, owner: User, address_line: str): self.owner = owner self.address_line = address_line When you add from __future__ import annotations at the top of a module, annotations are now resolved lazily for that module—just as if they were string hints, but the actual object is referred to instead of just its name. This allows linters to resolve things far more powerfully and completely, which is why it’s the recommended solution for this problem. You can still use string hints where they’re expected, but they should be phased out. As of Python 3.14, the lazy evaluation of annotations is the default behavior. You still can use the above code without the __future__ import but in the future, it may generate deprecation warnings.
https://www.infoworld.com/article/2268917/get-started-with-python-type-hints.html
Voir aussi |
56 sources (32 en français)
Date Actuelle
mer. 18 juin - 15:59 CEST
|