typing
Adnotacje typów: opis, jakich wartości oczekuje funkcja i co zwraca. Sprawdzają je edytory i narzędzia takie jak mypy.
Przykład
#def average(scores: list[int]) -> float:
return sum(scores) / len(scores)
def find_points(ranking: dict[str, int], name: str) -> int | None:
return ranking.get(name)
ranking: dict[str, int] = {"Ania": 250, "Kuba": 80}
print(average([80, 90, 100]))
print(find_points(ranking, "Ania"))
print(find_points(ranking, "Ola"))
print(average([1.5, 2.5]))Ostatnie wywołanie przekazuje liczby zmiennoprzecinkowe, choć adnotacja mówi list[int]. Python wykona je bez błędu, a mypy zgłosiłby niezgodność typów.
Definicja i zastosowanie
#Adnotacje typów to opis dopisany do kodu: def award(name: str, exp: int = 10) -> str: mówi, że funkcja przyjmuje napis i liczbę, a zwraca napis. Python nie sprawdza ich podczas działania programu. Korzystają z nich edytory, które podpowiadają kod i podkreślają błędy, oraz narzędzia takie jak mypy czy pyright, wykrywające pomyłki, zanim uruchomisz program.
Typy kolekcji zapisuje się wbudowanymi nazwami z nawiasami kwadratowymi: list[int], dict[str, int], tuple[int, int]. Wartość jednego z kilku typów opisuje operator |, np. int | str, a str | None oznacza napis albo None. To nowsze formy zapisów Union[int, str] i Optional[str] z modułu typing, które nadal spotkasz w starszym kodzie.
Moduł typing dostarcza też narzędzi do bardziej złożonych przypadków. TypedDict opisuje słownik o ustalonych kluczach, np. rekord odczytany z JSON-a. Protocol określa, jakie metody musi mieć obiekt, bez wymogu dziedziczenia, dzięki czemu narzędzia sprawdzą duck typing jeszcze przed uruchomieniem programu. Literal zawęża wartości do podanych, a Any wyłącza sprawdzanie.
Adnotacje są dostępne także w czasie działania programu, z czego korzystają @dataclass, pydantic czy FastAPI. Nie musisz typować wszystkiego od razu: zacznij od parametrów i wyników funkcji, bo typy zmiennych lokalnych narzędzia zwykle wywnioskują same.
Składnia
#from typing import Literal, Protocol, TypedDict
def funkcja(parametr: typ, opcjonalny: typ | None = None) -> typ_wyniku:
...
zmienna: dict[str, int] = {}Najczęstsze zapisy
#list[int]
Python 3.9
Lista liczb całkowitych. Tak samo zapisuje sięset[str],tuple[int, str]idict[str, int](typ kluczy i wartości).int | str
Python 3.10
Jeden z kilku typów. Starszy zapis:Union[int, str].str | None
Python 3.10
Napis alboNone. Starszy zapis:Optional[str].Literal[...]
Python 3.8
Tylko wymienione wartości, np.Literal["easy", "hard"].TypedDict
Python 3.8
Słownik o ustalonych kluczach i typach wartości.Protocol
Python 3.8
Zestaw metod, które obiekt musi mieć, bez wymogu dziedziczenia.Callable[[int], str]
Python 3.5
Funkcja, która przyjmujeinti zwracastr. Importowana zcollections.abcalbotyping.Any
Python 3.5
Dowolny typ. Wyłącza sprawdzanie dla danej wartości.type Alias = ...
Python 3.12
Nazwany alias typu, np.type Scores = list[int].
Więcej przykładów
#from typing import NotRequired, TypedDict
class StudentData(TypedDict):
name: str
world: int
badges: list[str]
mentor: NotRequired[str]
def describe(student: StudentData) -> str:
mentor = student.get("mentor", "brak")
return f"{student['name']} (świat {student['world']}), mentor: {mentor}"
ania: StudentData = {"name": "Ania", "world": 7, "badges": ["Start"]}
print(describe(ania))
print(type(ania).__name__)W czasie działania programu to zwykły słownik. NotRequired oznacza klucz, którego może brakować.
from typing import Protocol
class Scorable(Protocol):
def score(self) -> int: ...
class Quiz:
def __init__(self, correct: int) -> None:
self.correct = correct
def score(self) -> int:
return self.correct * 10
class Project:
def score(self) -> int:
return 50
def total(items: list[Scorable]) -> int:
return sum(item.score() for item in items)
print(total([Quiz(3), Project(), Quiz(1)]))Quiz i Project nie dziedziczą po Scorable, ale mają metodę score(), więc narzędzia do sprawdzania typów je zaakceptują.
from typing import Literal, Optional, Union
Difficulty = Literal["easy", "medium", "hard"]
def exp_for(difficulty: Difficulty) -> int:
return {"easy": 5, "medium": 10, "hard": 20}[difficulty]
def parse_points(value: Union[int, str]) -> Optional[int]:
if isinstance(value, int):
return value
return int(value) if value.isdigit() else None
print(exp_for("hard"))
print(parse_points(40), parse_points("75"), parse_points("dużo"))W nowym kodzie Union[int, str] zapisuje się jako int | str, a Optional[int] jako int | None.
Dobre praktyki
#- Adnotacje nie zatrzymają błędnych danych w czasie działania. Uruchamiaj mypy albo pyright, np. w edytorze, a dane z zewnątrz waliduj osobno.
- W nowym kodzie pisz
list[int]iint | NonezamiastList[int]iOptional[int]z modułutyping. Starsze zapisy nadal działają. - Złożony typ, który powtarza się w wielu miejscach, nazwij aliasem:
type Ranking = dict[str, int]od Pythona 3.12, a w starszych wersjach zwykłym przypisaniemRanking = dict[str, int].
Powiązane hasła
#- defDefiniuje funkcję: nazwany blok kodu z parametrami, który można wywoływać wielokrotnie.
- @dataclassDekorator, który na podstawie pól z adnotacjami typów generuje __init__, __repr__ i __eq__ klasy przechowującej dane.
- isinstance()Sprawdza, czy obiekt jest danego typu lub jego podtypu.
- collections.namedtupleTworzy krotki z nazwanymi polami: dane odczytasz przez student.points zamiast student[2], a obiekt pozostaje niezmienny.
- enumWyliczenia: zestaw nazwanych stałych, np. poziomów trudności albo statusów zadania, zamiast luźnych napisów i liczb.
Widzisz błąd albo brakuje przykładu? Napisz do nas.