Каким образом можно получить полиморфное поведение в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Каким образом можно получить полиморфное поведение в 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:
- Классический — наследование + переопределение (ООП)
- Duck typing — без наследования, если есть метод — работает
- Протоколы — структурная типизация (лучшее из обоих миров)
- Магические методы — переопределение операторов
- Композиция — вместо наследования
- Генерики — полиморфизм по типам данных
- Функции высшего порядка — передаем функции как параметры
Когда использовать:
- Наследование — для иерархии с общим интерфейсом
- Duck typing — для скрипт и сценариев
- Протоколы — для типизированного кода (production)
- Композиция — когда наследование усложняет код
- Генерики — для контейнеров и коллекций
Python очень гибкий язык — выбирай подход, который лучше подходит для твоей задачи!