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

Каким образом можно получить полиморфное поведение в Python?

2.2 Middle🔥 161 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Каким образом можно получить полиморфное поведение в Python?

Полиморфизм (polymorphism) — способность объектов разных типов использоваться одинаково. В Python это реализуется несколькими способами благодаря его динамической природе и системе типов.

1. Наследование и переопределение методов (классический полиморфизм)

Возможность разных классов определить одинаковый метод по-своему.

Пример: Разные животные издают разные звуки

from abc import ABC, abstractmethod

class Animal(ABC):
    """Базовый класс (абстрактный)"""
    
    @abstractmethod
    def make_sound(self) -> str:
        """Все животные должны издавать звук"""
        pass

class Dog(Animal):
    def make_sound(self) -> str:
        return "Woof! Woof!"

class Cat(Animal):
    def make_sound(self) -> str:
        return "Meow"

class Duck(Animal):
    def make_sound(self) -> str:
        return "Quack!"

# Полиморфное использование
animals: list[Animal] = [Dog(), Cat(), Duck()]

for animal in animals:
    print(animal.make_sound())
    # Woof! Woof!
    # Meow
    # Quack!

Каждый объект имеет свою реализацию make_sound(), но мы вызываем его одинаково для всех.

2. Duck Typing (утиная типизация)

"Если ходит как утка и крякает как утка — это утка". Python не требует явного наследования!

class Singer:
    def perform(self):
        return "Singing a song"

class Dancer:
    def perform(self):
        return "Dancing"

class Guitarist:
    def perform(self):
        return "Playing guitar"

# Эти классы НЕ наследуют друг друга
# Но у них есть метод perform()

def entertain(performer):
    """Принимает ЛЮБОЙ объект с методом perform()"""
    print(performer.perform())

entertain(Singer())      # Singing a song
entertain(Dancer())      # Dancing
entertain(Guitarist())   # Playing guitar

# Это работает без явного наследования!
# Python не проверяет тип, а проверяет наличие метода

Правило:

  • Не нужно наследовать базовый класс
  • Достаточно реализовать нужные методы
  • "Если у объекта есть метод — можно его вызвать"

3. Протоколы (Protocols) — структурная типизация

Определяет интерфейс БЕЗ явного наследования (Python 3.8+).

from typing import Protocol

class Drawable(Protocol):
    """Протокол: все что может быть нарисовано"""
    
    def draw(self) -> str:
        ...

class Circle:
    def draw(self) -> str:
        return "Drawing circle"

class Square:
    def draw(self) -> str:
        return "Drawing square"

class Triangle:
    def draw(self) -> str:
        return "Drawing triangle"

def render(drawable: Drawable) -> None:
    """Работает с ЛЮБЫМ объектом, имеющим draw()"""
    print(drawable.draw())

render(Circle())    # ✓ OK
render(Square())    # ✓ OK
render(Triangle())  # ✓ OK

# MyPy понимает, что Circle/Square/Triangle удовлетворяют Protocol Drawable
# без явного наследования

4. Магические методы (dunder)

Полиморфизм через переопределение специальных методов.

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """Переопределяем оператор +"""
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"
    
    def __repr__(self):
        return f"Vector(x={self.x}, y={self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2)      # Vector(4, 6)
print(v1 - v2)      # Vector(-2, -2)
print(v1 * 2)       # Vector(2, 4)

# Один оператор + работает полиморфно для разных типов
print(Vector(1, 1) + Vector(1, 1))  # Vector(2, 2)
print(Vector(5, 5) + Vector(3, 3))  # Vector(8, 8)

5. Множественное наследование

Класс может наследовать от нескольких базовых классов.

class Flyer:
    def fly(self):
        return "I can fly"

class Swimmer:
    def swim(self):
        return "I can swim"

class Walker:
    def walk(self):
        return "I can walk"

class Duck(Flyer, Swimmer, Walker):
    """Утка может летать, плавать и ходить"""
    pass

duck = Duck()
print(duck.fly())   # I can fly
print(duck.swim())  # I can swim
print(duck.walk())  # I can walk

Правило MRO (Method Resolution Order):

print(Duck.__mro__)
# (<class 'Duck'>, <class 'Flyer'>, <class 'Swimmer'>, <class 'Walker'>, <class 'object'>)

# Python ищет метод в таком порядке

6. Функции с типом-объектом (Callable Polymorphism)

from typing import Callable

def process_data(processor: Callable[[int], int]) -> int:
    """Принимает ЛЮБУЮ функцию, которая берет int и возвращает int"""
    return processor(10)

def double(x: int) -> int:
    return x * 2

def square(x: int) -> int:
    return x ** 2

def add_five(x: int) -> int:
    return x + 5

print(process_data(double))     # 20
print(process_data(square))     # 100
print(process_data(add_five))   # 15

# Одна функция работает с разными "processorsами"

7. Композиция (Composition) instead of Inheritance

Вместо наследования используем состав объектов.

class Engine:
    def start(self):
        return "Engine started"

class Wheel:
    def rotate(self):
        return "Wheel rotating"

class Car:
    def __init__(self):
        self.engine = Engine()
        self.wheels = [Wheel() for _ in range(4)]
    
    def start(self):
        return self.engine.start()
    
    def move(self):
        return [wheel.rotate() for wheel in self.wheels]

car = Car()
print(car.start())  # Engine started
print(car.move())   # ['Wheel rotating'] * 4

# Car не наследует Engine и Wheel
# Но использует их методы полиморфно

8. Генерики (Generics)

Полиморфизм параметрами типа.

from typing import TypeVar, Generic, List

T = TypeVar('T')

class Stack(Generic[T]):
    """Стек, работающий с ЛЮ БЫМ типом T"""
    
    def __init__(self):
        self.items: List[T] = []
    
    def push(self, item: T) -> None:
        self.items.append(item)
    
    def pop(self) -> T:
        return self.items.pop()

# Stack[int] — стек целых чисел
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop())  # 2

# Stack[str] — стек строк (ТОЖЕ ЖЕ реализация!)
str_stack: Stack[str] = Stack()
str_stack.push("hello")
str_stack.push("world")
print(str_stack.pop())  # world

# Один класс работает с разными типами данных

9. Функции высшего порядка

from typing import List, Callable

def apply_operation(numbers: List[int], operation: Callable[[int], int]) -> List[int]:
    """Применяет ЛЮБУЮ операцию к списку"""
    return [operation(n) for n in numbers]

numbers = [1, 2, 3, 4, 5]

# Одна функция, разные операции
print(apply_operation(numbers, lambda x: x * 2))      # [2, 4, 6, 8, 10]
print(apply_operation(numbers, lambda x: x ** 2))     # [1, 4, 9, 16, 25]
print(apply_operation(numbers, lambda x: x + 10))     # [11, 12, 13, 14, 15]

10. Пример: полиморфный обработчик платежей

from abc import ABC, abstractmethod
from typing import Protocol

class PaymentProcessor(ABC):
    """Абстрактный класс для обработки платежей"""
    
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f"Charging credit card for ${amount}")
        return True

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f"Processing PayPal payment of ${amount}")
        return True

class CryptoProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f"Processing crypto payment of ${amount}")
        return True

# Полиморфное использование
def checkout(processor: PaymentProcessor, amount: float) -> None:
    if processor.process_payment(amount):
        print("Payment successful!")
    else:
        print("Payment failed!")

# Один код работает с разными процессорами
checkout(CreditCardProcessor(), 100)
checkout(PayPalProcessor(), 50)
checkout(CryptoProcessor(), 75)

Сравнение подходов

Подход              | Требует наследования | Проверка типов | Гибкость
--------------------|----------------------|----------------|----------
Наследование        | Да (явное)           | MyPy/IDE       | Средняя
Duck typing         | Нет                  | Во время выпол. | Высокая
Protocol            | Нет (структурное)    | MyPy           | Высокая
Генерики            | Нет                  | MyPy/IDE       | Высокая
Композиция          | Нет                  | MyPy           | Высокая

Заключение

Способы получить полиморфизм в Python:

  1. Классический — наследование + переопределение (ООП)
  2. Duck typing — без наследования, если есть метод — работает
  3. Протоколы — структурная типизация (лучшее из обоих миров)
  4. Магические методы — переопределение операторов
  5. Композиция — вместо наследования
  6. Генерики — полиморфизм по типам данных
  7. Функции высшего порядка — передаем функции как параметры

Когда использовать:

  • Наследование — для иерархии с общим интерфейсом
  • Duck typing — для скрипт и сценариев
  • Протоколы — для типизированного кода (production)
  • Композиция — когда наследование усложняет код
  • Генерики — для контейнеров и коллекций

Python очень гибкий язык — выбирай подход, который лучше подходит для твоей задачи!