Полиморфизм (от греч. “много форм”) — это способность объектов разных классов обрабатывать один и тот же вызов метода по-разному. Это позволяет писать более гибкий и обобщенный код, работающий с объектами на основе их общего интерфейса, а не конкретной реализации.

5.1. Концепция Полиморфизма

Представьте, что у вас есть разные типы животных (Dog, Cat), и все они умеют издавать звук (speak). Вы можете создать список животных и попросить каждое издать звук, не зная заранее, собака это или кошка. Каждое животное выполнит метод speak по-своему.

class Animal: # Родительский класс  
    def __init__(self, name): self.name = name  
    def speak(self): raise NotImplementedError  
  
class Dog(Animal): # Дочерний  
    def speak(self): return f"{self.name} говорит Гав!"  
  
class Cat(Animal): # Дочерний  
    def speak(self): return f"{self.name} говорит Мяу!"  
  
class Cow(Animal): # Дочерний  
    def speak(self): return f"{self.name} говорит Мууу!"  
  
# Функция, работающая с любым Animal (полиморфизм)  
def make_animal_speak(animal_obj):  
    print(animal_obj.speak())  
  
animals = [Dog("Рекс"), Cat("Барсик"), Cow("Буренка")]  
  
for animal in animals:  
    make_animal_speak(animal)  
# Вывод:  
# Рекс говорит Гав!  
# Барсик говорит Мяу!  
# Буренка говорит Мууу!  

Функция make_animal_speak не зависит от конкретного типа животного, ей важно лишь наличие метода speak.

5.2. Утиная Типизация (Duck Typing)

Python во многом полагается на концепцию “утиной типизации”. Название происходит от фразы: “Если нечто крякает как утка и плавает как утка, то это, вероятно, и есть утка”.

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

class Duck:  
    def quack(self): print("Кря!")  
    def swim(self): print("Плывет")  
  
class Person:  
    def quack(self): print("Я человек, крякающий как утка!")  
    def swim(self): print("Человек плывет")  
  
def make_it_quack_and_swim(thing):  
    # Не важно, какого класса thing, главное - есть методы  
    try:  
        thing.quack()  
        thing.swim()  
    except AttributeError:  
        print("Это не умеет крякать и плавать!")  
  
d = Duck()  
p = Person()  
  
make_it_quack_and_swim(d)  
# Вывод:  
# Кря!  
# Плывет  
  
make_it_quack_and_swim(p)  
# Вывод:  
# Я человек, крякающий как утка!  
# Человек плывет  

Функция make_it_quack_and_swim успешно работает с объектами обоих классов, потому что оба имеют методы quack и swim.

5.3. Перегрузка Операторов (Operator Overloading)

Это форма полиморфизма, при которой стандартные операторы Python (+, -, *, len() и др.) могут быть переопределены для работы с объектами пользовательских классов. Это достигается путем реализации специальных (магических) методов

class Vector:  
    def __init__(self, x, y):  
        self.x = x  
        self.y = y  
  
    # Перегрузка оператора +  
    def __add__(self, other):  
        if isinstance(other, Vector):  
            return Vector(self.x + other.x, self.y + other.y)  
        return NotImplemented # Важно для корректной работы  
  
    # Как объект будет представлен в виде строки  
    def __str__(self):  
        return f"Vector({self.x}, {self.y})"  
  
v1 = Vector(2, 4)  
v2 = Vector(1, 5)  
  
v3 = v1 + v2 # Используем оператор + благодаря __add__  
print(v3)   # Вывод: Vector(3, 9)  

5.4. Переопределение Методов (как форма полиморфизма)

Как мы видели в разделе 4. Наследование, переопределение методов — это когда дочерний класс предоставляет свою реализацию метода родителя. Это также является проявлением полиморфизма: объекты дочерних классов по-разному реагируют на вызов одного и того же метода, унаследованного от родителя.

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰