Дженерики в Python, простыми словами

Дженерики в Python, простыми словами

Если вы только начинаете изучать Python и слышите слово дженерики, скорее всего в голове сразу каша: «что это вообще такое?». На самом деле дженерики - это очень простая идея. Представьте, что у вас есть коробка. В коробку можно положить игрушки, яблоки, книжки - всё что угодно.

Но иногда вы хотите, чтобы в коробке лежали только яблоки. А иногда - только игрушки.И вот тут вам помогают generics.

Что такое generics?

Generics - это способ написать класс или функцию один раз, но при этом заранее указать, с каким типом объектов он будет работать. Это как шаблон: «эта коробка для яблок», «эта корзина для бананов», «этот калькулятор для чисел».

В Python для этого используется модуль typing и конструкция TypeVar.

Пример 1. Корзина для предметов

python from typing import Generic, TypeVar T = TypeVar('T') class Box(Generic[T]): def __init__(self, item: T) -> None: self.item = item def get_item(self) -> T: return self.item

Здесь Box может хранить что угодно: строки, числа, даже смешанные объекты. Это удобно, но небезопасно - легко ошибиться.

Теперь сделаем ту же коробку, но с дженериком:

python if __name__ == '__main__': apple_box = Box('apple') print(apple_box.get_item()) number_box = Box(123) print(number_box.get_item()) apple_box = Box[str](1) # mypy error print(apple_box.get_item()) print(some_box.get_item())

Теперь в коде, где мы в коробку для яблок пытаемся положить число, mypy и ide подсветит, что мы делаем что-то не то.

Пример 2. Коллекция с ограничением типов

Сделаем корзину (Basket), куда можно складывать предметы только одного типа:

python from typing import TypeVar, Generic, List T = TypeVar('T') class Basket(Generic[T]): def __init__(self): self.items: List[T] = [] def add(self, item: T) -> None: self.items.append(item) def get_all(self) -> List[T]: return self.items if __name__ == '__main__': fruit_basket = Basket[str]() fruit_basket.add("apple") fruit_basket.add("orange") print(fruit_basket.get_all()) number_basket = Basket[int]() number_basket.add(1) number_basket.add(2) print(number_basket.get_all()) fruit_basket.add(2) # mypy/ide warning

Если попытаться добавить число в Basket[str], ide и mypy сразу скажут, что это ошибка.

Пример 3. Ограниченные дженерики (bound)

Иногда нужно разрешить только числа, а не всё подряд.Тогда мы говорим: «T должен быть числом» (bound=float):

python from typing import Generic, TypeVar NumberT = TypeVar('NumberT', bound=float) class Calculator(Generic[NumberT]): def __init__(self, value: NumberT) -> None: self.value = value def add(self, other: NumberT) -> NumberT: return self.value + other if __name__ == '__main__': calc = Calculator(10.5) print(calc.add(2)) # сработает, но mypy будет ругаться print(calc.add(3.5)) print(calc.add('s')) # warning

Пример 4. Репозиторий

Представьте, что у нас есть база данных с разными сущностями: User, Product. Вместо того чтобы писать одинаковый код для каждой, мы можем сделать дженерик-репозиторий:

python from typing import Generic, TypeVar T = TypeVar('T') class Repository(Generic[T]): def __init__(self): self.items: list[T] = [] def add(self, item: T) -> None: self.items.append(item) def get_all(self) -> list[T]: return self.items class User: def __init__(self, name: str) -> None: self.name = name def __repr__(self): return f"User: {self.name}" class Product: def __init__(self, title: str) -> None: self.title = title def __repr__(self): return f"Production: {self.title}" if __name__ == '__main__': user_repo = Repository[User]() user_repo.add(User('Alice')) user_repo.add(User('Bob')) print(user_repo.get_all()) product_repo = Repository[Product]() product_repo.add(Product('Iphone')) product_repo.add(Product('Laptop')) print(product_repo.get_all()) user_repo.add(Product('Table')) # warning product_repo.add(User('Miki')) # warning

Пример 5. Дженерики + Protocol

А что если мы хотим складывать объекты (например, числа или строки)? Тогда можно сказать: «принимаю любой тип, у которого есть оператор +».

python from typing import Protocol, TypeVar, Generic class Addable(Protocol): def __add__(self, other: "Addable") -> "Addable": ... T = TypeVar("T", covariant=True) class Summer(Generic[T]): def __init__(self, items: list[T]) -> None: self.items = items def total(self) -> T: result = self.items[0] for item in self.items[1:]: result += item return result if __name__ == '__main__': print(Summer([1, 2, 3]).total()) print(Summer(['a', 'b', 'c']).total())

Буду рад быть полезным! Если есть вопросы, можете написать мне в телеграмм! А так же буду очень рад активности в моем телеграмм канале в том числе!

3
6 комментариев