def process(obj):
obj.method_x()
obj.method_y()
import abc
class AbstractClass(abc.ABC):
@abc.abstractmethod
def method_x(self):
pass
@abc.abstractmethod
def method_y(self):
pass
class ConcreteClass(AbstractClass):
def method_x(self):
print("ConcreteClass: method_x")
def method_y(self):
print("ConcreteClass: method_y")
from functools import singledispatch
@singledispatch
def my_func(arg):
print(f"Generic implementation for {type(arg)}")
@my_func.register
def _(arg: int):
print(f"Implementation for int: {arg}")
@my_func.register
def _(arg: str):
print(f"Implementation for str: {arg}")
Реализовать методы, которые принимают объекты разных типов и работают с ними полиморфно, можно несколькими способами в Python. Основная идея полиморфизма заключается в том, что один и тот же метод может работать с объектами разных классов, если эти классы предоставляют ожидаемый интерфейс (то есть, имеют методы с ожидаемыми именами и сигнатурами). Вот несколько распространенных подходов:
Python использует утиную типизацию, что означает, что тип объекта не важен, пока у него есть методы и атрибуты, которые требуются в конкретном контексте. Если объект "выглядит как утка, плавает как утка и крякает как утка", то он и есть утка.
def my_function(obj):
try:
return obj.calculate_area() # Предполагаем, что у объекта есть метод calculate_area
except AttributeError:
return "Object doesn't have calculate_area method"
class Circle:
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
class Square:
def __init__(self, side):
self.side = side
def calculate_area(self):
return self.side * self.side
class Text:
def __init__(self, text):
self.text = text
# Пример использования
circle = Circle(5)
square = Square(4)
text_obj = Text("Hello")
print(my_function(circle)) # Выведет площадь круга
print(my_function(square)) # Выведет площадь квадрата
print(my_function(text_obj)) # Выведет "Object doesn't have calculate_area method"
В этом примере, `my_function` не проверяет тип объекта. Она просто пытается вызвать метод `calculate_area`. Если метод существует, он вызывается, иначе генерируется исключение `AttributeError`, которое обрабатывается.
Модуль `abc` (Abstract Base Classes) позволяет определить абстрактные классы, которые не могут быть инстанцированы, но могут использоваться для определения интерфейса. Классы, которые наследуются от абстрактного класса, должны реализовать все абстрактные методы.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
class Square(Shape):
def __init__(self, side):
self.side = side
def calculate_area(self):
return self.side * self.side
# my_function остается прежней
circle = Circle(5)
square = Square(4)
print(my_function(circle))
print(my_function(square))
# shape = Shape() # Raises TypeError: Can't instantiate abstract class Shape with abstract methods calculate_area
Теперь `Circle` и `Square` должны реализовать метод `calculate_area`, иначе при попытке создать экземпляр этих классов будет выброшено исключение. Это позволяет явно определить интерфейс, который должны поддерживать классы.
Можно явно проверять тип объекта с помощью `isinstance()` и выполнять разные действия в зависимости от типа.
def my_function(obj):
if isinstance(obj, Circle):
return obj.calculate_area()
elif isinstance(obj, Square):
return obj.calculate_area()
else:
return "Unknown object type"
class Circle:
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
class Square:
def __init__(self, side):
self.side = side
def calculate_area(self):
return self.side * self.side
circle = Circle(5)
square = Square(4)
print(my_function(circle))
print(my_function(square))
Этот подход менее гибкий, чем утиная типизация или ABC, потому что требует явного знания о типах объектов, с которыми работает функция. Однако он может быть полезен, если нужно выполнить очень специфические действия в зависимости от типа объекта.
Python не поддерживает явную перегрузку функций, как в C++ или Java, но можно добиться похожего эффекта, используя декораторы или `functools.singledispatch`. `singledispatch` позволяет определить несколько реализаций функции в зависимости от типа первого аргумента.
from functools import singledispatch
@singledispatch
def my_function(arg):
return "Unknown type"
@my_function.register
def _(arg: Circle):
return arg.calculate_area()
@my_function.register
def _(arg: Square):
return arg.calculate_area()
class Circle:
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
class Square:
def __init__(self, side):
self.side = side
def calculate_area(self):
return self.side * self.side
circle = Circle(5)
square = Square(4)
text_obj = "Hello"
print(my_function(circle))
print(my_function(square))
print(my_function(text_obj)) # Prints "Unknown Type"
`singledispatch` особенно полезен, когда нужно расширить поведение функции для новых типов без изменения исходного кода функции.
Выбор между этими подходами зависит от конкретной задачи и требований к гибкости и расширяемости кода. Утиная типизация часто является наиболее идиоматичным способом реализации полиморфизма в Python, но ABC и `singledispatch` могут быть полезны в более сложных сценариях, когда требуется более строгий контроль над типами.