在 Python 中,多态指同一调用形式作用于不同对象时可产生不同行为。语言采用鸭子类型:不强制共享某一父类,只要对象提供所需方法,即可被同一段代码调用。若需一组类型遵守统一接口,亦可在父类中约定方法名并由子类实现。本节说明鸭子类型、继承场景下的多态及内置函数中的协议。

同名方法,不同行为

make_speak 仅要求参数具有 speak 方法,不区分具体类型。传入不同实例时,输出不同:

class Dog:
    def speak(self):
        print("汪汪!")

class Cat:
    def speak(self):
        print("喵喵!")

def make_speak(animal):
    animal.speak()

make_speak(Dog())
make_speak(Cat())

鸭子类型

能否使用某对象,取决于是否具备所需行为而非名义上的类型关系。Car、Bike、Bird 无共同父类,但均实现 move,即可传入同一 go 函数:

class Car:
    def move(self):
        print("汽车在跑")

class Bike:
    def move(self):
        print("自行车在骑")

class Bird:
    def move(self):
        print("鸟在飞")

def go(vehicle):
    vehicle.move()

go(Car())
go(Bike())
go(Bird())

继承下的多态

需要统一接口时,可在父类中声明方法,子类各自实现。父类中用 NotImplementedError 占位,可提示子类必须提供实现:

class Shape:
    def area(self):
        raise NotImplementedError("子类必须实现 area")

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14 * self.r * self.r

class Square(Shape):
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side * self.side

shapes = [Circle(2), Square(3)]
for s in shapes:
    print(s.area())

内置函数中的多态

len 可作用于字符串、列表、元组等,因其均实现 __len__。自定义类提供同名特殊方法后,亦可接入 len:

print(len("hello"))
print(len([1, 2, 3]))
print(len((1, 2)))

class Team:
    def __init__(self, members):
        self.members = members
    def __len__(self):
        return len(self.members)

t = Team(["a", "b", "c"])
print(len(t))

按类型分支:isinstance

多数情况下直接调用方法即可。若须按类型分别处理,可使用 isinstance。分支过多时,宜考虑是否可通过统一方法名消化差异:

def describe(x):
    if isinstance(x, int):
        return f"整数 {x}"
    elif isinstance(x, str):
        return f"字符串 '{x}'"
    else:
        return f"其他 {type(x).__name__}"

print(describe(5))
print(describe("hi"))

常见错误

  • 多态不依赖继承;鸭子类型下,具备同名方法即可被同一调用方使用。
  • 传入缺少所需方法的对象将引发 AttributeError;边界输入复杂时,可先 hasattr 检查或由调用方保证接口。
  • 父类提供空实现且未提示时,子类未重写可能静默出错;须强制子类实现时,宜使用 NotImplementedError。

本节说明了 Python 中多态与鸭子类型的含义。下一节介绍封装:命名约定、@property 与访问控制。