← Назад к вопросам

Что такое дженерики?

1.7 Middle🔥 71 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Дженерики (Generics) в Python

Дженерики (Generics) — это механизм типизации, позволяющий писать функции, классы и структуры данных, которые работают с любыми типами данных, при этом сохраняя информацию о типе для проверки во время разработки. Дженерики обеспечивают повторное использование кода без потери типобезопасности.

Концепция дженериков

Дженерик позволяет определить класс или функцию один раз, которая будет работать с разными типами:

from typing import Generic, TypeVar

# TypeVar — переменная типа
T = TypeVar('T')

# Функция работает с любым типом
def get_first_element(items: list[T]) -> T:
    return items[0]

# Использование
first_int: int = get_first_element([1, 2, 3])  # T = int
first_str: str = get_first_element(["a", "b"])  # T = str
first_float: float = get_first_element([1.5, 2.5])  # T = float

TypeVar — переменные типа

Базовое использование:

from typing import TypeVar

# Без ограничений
T = TypeVar('T')

# С ограничениями (только конкретные типы)
Numeric = TypeVar('Numeric', int, float, complex)

def process_number(value: Numeric) -> Numeric:
    return value * 2

process_number(5)      # OK: int
process_number(3.14)   # OK: float
process_number("text") # Error: str not allowed

Ограничение с условием (Bound):

from typing import TypeVar

# T может быть любым типом, но наследует методы int
T = TypeVar('T', bound=int)

def add_one(value: T) -> T:
    return value + 1  # OK: int имеет метод __add__

class MyInt(int):
    pass

result = add_one(MyInt(5))  # OK: MyInt наследует int
result = add_one("text")    # Error: str не наследует int

Дженерик классы

Stack (стек) как дженерик:

from typing import Generic, TypeVar

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self):
        self.items: list[T] = []
    
    def push(self, item: T) -> None:
        self.items.append(item)
    
    def pop(self) -> T:
        if not self.items:
            raise IndexError("Stack is empty")
        return self.items.pop()
    
    def is_empty(self) -> bool:
        return len(self.items) == 0

# Использование
int_stack: Stack[int] = Stack()
int_stack.push(10)
int_stack.push(20)
value: int = int_stack.pop()  # Type checker знает, что это int

str_stack: Stack[str] = Stack()
str_stack.push("hello")
str_stack.push("world")
word: str = str_stack.pop()  # Type checker знает, что это str

Словарь (Cache) как дженерик:

from typing import Generic, TypeVar, Optional

K = TypeVar('K')  # Key type
V = TypeVar('V')  # Value type

class Cache(Generic[K, V]):
    def __init__(self):
        self.data: dict[K, V] = {}
    
    def set(self, key: K, value: V) -> None:
        self.data[key] = value
    
    def get(self, key: K) -> Optional[V]:
        return self.data.get(key)
    
    def clear(self) -> None:
        self.data.clear()

# Использование
user_cache: Cache[int, str] = Cache()
user_cache.set(1, "Alice")
user_cache.set(2, "Bob")

name: Optional[str] = user_cache.get(1)  # Type: Optional[str]

Дженерик функции

from typing import TypeVar, Callable

T = TypeVar('T')
U = TypeVar('U')

# Функция трансформации
def map_function(items: list[T], func: Callable[[T], U]) -> list[U]:
    return [func(item) for item in items]

# Использование
numbers = [1, 2, 3, 4, 5]
squares = map_function(numbers, lambda x: x ** 2)
print(squares)  # [1, 4, 9, 16, 25]

words = ["hello", "world"]
lengths = map_function(words, len)
print(lengths)  # [5, 5]

Дженерик с несколькими типами

from typing import Generic, TypeVar, Tuple

K = TypeVar('K')
V = TypeVar('V')

class Pair(Generic[K, V]):
    def __init__(self, key: K, value: V):
        self.key = key
        self.value = value
    
    def get_key(self) -> K:
        return self.key
    
    def get_value(self) -> V:
        return self.value
    
    def as_tuple(self) -> Tuple[K, V]:
        return (self.key, self.value)

# Использование
pair: Pair[int, str] = Pair(1, "Alice")
key: int = pair.get_key()
value: str = pair.get_value()
print(pair.as_tuple())  # (1, 'Alice')

pair2: Pair[str, float] = Pair("score", 95.5)
key2: str = pair2.get_key()
value2: float = pair2.get_value()

Covariance и Contravariance

Covariant (ковариантный):

from typing import TypeVar

# Ковариантный TypeVar — может быть подтипом T
T_co = TypeVar('T_co', covariant=True)

class Producer(Generic[T_co]):
    def produce(self) -> T_co:
        pass

class Animal:
    pass

class Dog(Animal):
    pass

# Dog Producer может быть использован где ожидается Animal Producer
dog_producer: Producer[Dog] = DogProducer()
animal_producer: Producer[Animal] = dog_producer  # OK

Contravariant (контравариантный):

# Контравариантный TypeVar — может быть супертипом T
T_contra = TypeVar('T_contra', contravariant=True)

class Consumer(Generic[T_contra]):
    def consume(self, item: T_contra) -> None:
        pass

# Animal Consumer может быть использован где ожидается Dog Consumer
animal_consumer: Consumer[Animal] = AnimalConsumer()
dog_consumer: Consumer[Dog] = animal_consumer  # OK

Практические примеры

Generic Repository Pattern:

from typing import Generic, TypeVar, List, Optional

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 find_all(self) -> List[T]:
        return self.items
    
    def find_by_index(self, index: int) -> Optional[T]:
        try:
            return self.items[index]
        except IndexError:
            return None

class User:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name

class Product:
    def __init__(self, id: int, price: float):
        self.id = id
        self.price = price

# Использование
user_repo: Repository[User] = Repository()
user_repo.add(User(1, "Alice"))
user_repo.add(User(2, "Bob"))

product_repo: Repository[Product] = Repository()
product_repo.add(Product(1, 99.99))

users = user_repo.find_all()  # List[User]
products = product_repo.find_all()  # List[Product]

Дженерик Decorator:

from typing import TypeVar, Callable
from functools import wraps

T = TypeVar('T')

def timing_decorator(func: Callable[..., T]) -> Callable[..., T]:
    @wraps(func)
    def wrapper(*args, **kwargs) -> T:
        import time
        start = time.time()
        result: T = func(*args, **kwargs)
        print(f"Execution time: {time.time() - start:.3f}s")
        return result
    return wrapper

@timing_decorator
def slow_function(n: int) -> int:
    return sum(range(n))

result: int = slow_function(1000000)

Преимущества дженериков

  • Переиспользование кода — одна реализация для разных типов
  • Типобезопасность — type checker ловит ошибки типа
  • Лучшая документация — ясно, с какими типами работает код
  • IDE поддержка — автодополнение и навигация работают правильно
  • Рефакторинг — безопасное изменение типов

Дженерики — это мощный инструмент для написания абстрактного, переиспользуемого и типобезопасного кода на Python, особенно в больших проектах с высокими требованиями к надёжности.

Что такое дженерики? | PrepBro