Что изменилось в системе типов Python 3.12
Основные нововведения в системе типов Python 3.12
1. Упрощенный синтаксис обобщений (PEP 695)
Как объявить обобщенную функцию без явного вызова TypeVar?
В Python 3.12 появился новый синтаксис для определения обобщенных функций и классов. Вместо создания экземпляров TypeVar можно указать параметры типа в квадратных скобках после имени функции или класса. Это уменьшает количество кода и улучшает читаемость.
def first[T](items: list[T]) -> T:
return items[0]
result = first([1, 2, 3])
print(result) # 1Python type self (тип self в python)
Пояснение: параметр T автоматически связывается с типом элементов списка. Тип возвращаемого значения совпадает с типом элемента.
Типичная ошибка: попытка использовать имя, уже занятое в глобальной области. Если T уже объявлен как обычная переменная, возникнет конфликт. Решение: использовать другое имя или импортировать TypeVar для избежания путаницы.
Когда нужно использовать явный TypeVar вместо нового синтаксиса?
Старый способ с TypeVar остается актуальным для обратной совместимости и для случаев, когда требуется ограничить тип (например, bound или covariant/contravariant). Новый синтаксис не поддерживает явные ограничения.
from typing import TypeVar
T = TypeVar('T', bound=float)
def scale(value: T, factor: float) -> T:
return value * factorPython typing function (аннотация типа функции в python)
Пояснение: здесь T может быть только числовым типом (int, float). Новый синтаксис такой возможности пока не предоставляет.
2. Декоратор @override (PEP 698)
Как отметить, что метод переопределяет родительский метод?
Декоратор @override из модуля typing позволяет явно указать намерение переопределить метод базового класса. Если метод с таким именем отсутствует в родительском классе, статический анализатор выдаст предупреждение.
from typing import override
class Base:
def greet(self) -> str:
return "Hello"
class Child(Base):
@override
def greet(self) -> str:
return "Hi"
obj = Child()
print(obj.greet()) # HiPython 3.12 type (новые возможности типов в python 3.12)
Пояснение: декоратор @override не изменяет поведение кода, но служит подсказкой для инструментов проверки типов (mypy, Pyright).
Типичная ошибка: случайное переопределение метода с опечаткой. Если написать @override над методом gret, mypy укажет, что такого метода нет в базовом классе.
Какие альтернативы декоратору @override существуют?
До появления @override разработчики могли добавлять комментарии или использовать абстрактные методы из abc для обязательного переопределения. Однако явный декоратор делает намерение более очевидным и автоматически проверяемым.
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def doit(self) -> None: ...
class Child(Base):
def doit(self) -> None:
passPython typing type checking (проверка типов с помощью typing в python)
Пояснение: абстрактный метод гарантирует переопределение, но декоратор @override подходит для неабстрактных методов.
3. Поддержка протокола буфера (PEP 688)
Как аннотировать аргумент, принимающий любой объект с буферным протоколом?
Новый тип Buffer в модуле typing позволяет типизировать параметры, которые могут принимать bytes, bytearray, memoryview и пользовательские объекты, реализующие буферный протокол.
from typing import Buffer
def process(data: Buffer) -> str:
view = memoryview(data)
return f"Buffer size: {view.nbytes}"
print(process(b"abc")) # Buffer size: 3
print(process(bytearray(10))) # Buffer size: 10Python return type (тип возврата функции в python)
Пояснение: Buffer объединяет все типы, поддерживающие буферный интерфейс Си, что упрощает написание полиморфного кода для работы с двоичными данными.
Типичная ошибка: передача неподдерживаемого типа, например, строки. Статический анализатор выдаст ошибку, так как str не реализует буферный протокол (в отличие от bytes).
Какие альтернативы существуют для типизации буфера?
Ранее можно было использовать объединение Union[bytes, bytearray, memoryview], но это не покрывало пользовательские классы с поддержкой буфера. Новый Buffer решает эту проблему.
from typing import Union
def process_old(data: Union[bytes, bytearray, memoryview]) -> int:
return len(data)
Пояснение: такое объединение не включает, например, объект array.array, который также поддерживает буфер.
Расширенные примеры использования новых типов в Python 3.12
Пример 1: Обобщенная функция для поиска максимального элемента
def find_max[T](items: list[T]) -> T | None:
if not items:
return None
max_item = items[0]
for item in items[1:]:
if item > max_item:
max_item = item
return max_item
result = find_max([3, 7, 2, 9])
print(result) # 9
Output: 9
Пояснение: параметр T автоматически выводится как int. Функция может работать с любым сравниваемым типом.
Пример 2: Класс с обобщенным параметром и методом
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
int_stack = Stack[int]()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop()) # 2
Output: 2
Пояснение: обобщенный класс Stack принимает параметр типа T и использует его для типизации внутреннего списка и методов.
Пример 3: Использование @override с наследованием нескольких уровней
from typing import override
class Shape:
def area(self) -> float:
return 0.0
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
@override
def area(self) -> float:
return 3.14159 * self.radius ** 2
class ColoredCircle(Circle):
def __init__(self, radius: float, color: str):
super().__init__(radius)
self.color = color
@override
def area(self) -> float:
return super().area() + 0.0 # просто демонстрация
c = ColoredCircle(3, "red")
print(c.area()) # 28.27431
Output: 28.27431
Пояснение: декоратор @override применяется на каждом уровне переопределения. Если метод area удалить из Shape, mypy сообщит об ошибке.
Пример 4: Работа с буфером: функция чтения данных из разных источников
from typing import Buffer
import array
def read_buffer(source: Buffer) -> bytes:
with memoryview(source) as view:
return view.tobytes()
# Разные типы
b1 = b"hello"
b2 = bytearray([1, 2, 3])
b3 = memoryview(array.array('i', [10, 20]))
print(read_buffer(b1)) # b'hello'
print(read_buffer(b2)) # b'\x01\x02\x03'
print(read_buffer(b3)) # b'\n\x00\x00\x00\x14\x00\x00\x00'
Output: b'hello' b'\x01\x02\x03' b'\n\x00\x00\x00\x14\x00\x00\x00'
Пояснение: функция принимает объекты, реализующие буферный протокол, и преобразует их в bytes. Это работает с array.array, который не входит в Union[bytes, bytearray, memoryview].