Python: 多态与魔术方法
前两课学了类和继承的"形",这一课讲"神"。多态让不同类的对象能用相同的方式调用——不需要知道它具体是什么类,只要它会那个方法就行。魔术方法让自定义类表现得像内置类型一样自然。
1. 什么是多态
多态(Polymorphism)意思是"多种形态"——同一个接口,不同的实现:
PYTHON
class Dog:
def speak(self):
return "汪汪!"
class Cat:
def speak(self):
return "喵喵~"
class Duck:
def speak(self):
return "嘎嘎!"
# 多态:不管是什么类,只要它有 speak() 方法
animals = [Dog(), Cat(), Duck()]
for animal in animals:
print(animal.speak())
运行结果:
TEXT
📖 仅展示
汪汪!
喵喵~
嘎嘎!
💡 Duck Typing(鸭子类型): "如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。" Python 不关心对象的类型,只关心它有没有你要的方法。这是 Python 最灵活的设计之一。
▶ 示例:计算器类(难度⭐⭐)
PYTHON
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# 多态——不管什么形状,计算面积就行
shapes = [Rectangle(3, 4), Circle(5), Triangle(6, 8)]
for shape in shapes:
print(f"面积:{shape.area():.2f}")
运行结果:
TEXT
📖 仅展示
面积:12.00
面积:78.54
面积:24.00
2. 魔术方法:让类更"Python 化"
魔术方法(Magic Methods)是名字以双下划线开头和结尾的特殊方法——__init__、__str__、__len__ 等。它们让自定义类的行为像内置类型。
(1) __str__ 和 __repr__:让打印更友好
PYTHON
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
"""面向用户的字符串表示"""
return f"{self.name}({self.age}岁)"
def __repr__(self):
"""面向开发者的字符串表示"""
return f"Person('{self.name}', {self.age})"
p = Person("Alice", 25)
print(p) # Alice(25岁)——调用 __str__
print(repr(p)) # Person('Alice', 25)——调用 __repr__
(2) __len__:让对象支持 len()
PYTHON
class Team:
def __init__(self, members=None):
self.members = members or []
def add(self, member):
self.members.append(member)
def __len__(self):
return len(self.members)
def __str__(self):
return f"团队({', '.join(self.members)})"
team = Team()
team.add("Alice")
team.add("Bob")
team.add("Charlie")
print(len(team)) # 3——调用 __len__
print(team) # 团队(Alice, Bob, Charlie)
(3) __getitem__:让对象支持索引
PYTHON
class Playlist:
def __init__(self):
self.songs = []
def add(self, song):
self.songs.append(song)
def __getitem__(self, index):
return self.songs[index]
def __len__(self):
return len(self.songs)
playlist = Playlist()
playlist.add("晴天")
playlist.add("七里香")
playlist.add("夜曲")
print(playlist[0]) # 晴天——调用 __getitem__
print(len(playlist)) # 3
# 支持切片和遍历
for song in playlist:
print(song, end=" ") # 晴天 七里香 夜曲
3. 常用魔术方法速查
PYTHON
class Demo:
def __init__(self, value): # 构造
self.value = value
def __str__(self): # str() / print()
return str(self.value)
def __repr__(self): # repr() / 调试
return f"Demo({self.value})"
def __len__(self): # len()
return 1
def __bool__(self): # bool() / if 判断
return self.value != 0
def __eq__(self, other): # ==
return self.value == other.value
def __lt__(self, other): # <(用于排序)
return self.value < other.value
def __add__(self, other): # + 运算符
return Demo(self.value + other.value)
def __getitem__(self, key): # obj[key]
return key
▶ 示例:分数类(难度⭐⭐⭐)
PYTHON
class Fraction:
"""分数类——支持加减运算"""
def __init__(self, numerator, denominator=1):
if denominator == 0:
raise ValueError("分母不能为0")
self.numerator = numerator
self.denominator = denominator
def __str__(self):
if self.denominator == 1:
return str(self.numerator)
return f"{self.numerator}/{self.denominator}"
def __add__(self, other):
new_den = self.denominator * other.denominator
new_num = self.numerator * other.denominator + other.numerator * self.denominator
return Fraction(new_num, new_den)
def __eq__(self, other):
return (self.numerator * other.denominator) == (other.numerator * self.denominator)
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1) # 1/3
print(f1 + f2) # 9/18
print(Fraction(1, 2) == Fraction(2, 4)) # True
输出:
TEXT
📖 仅展示
# 函数定义成功
▶ 示例:图形面积与周长(难度⭐⭐)
PYTHON
import math
class Shape:
def area(self):
raise NotImplementedError
def perimeter(self):
raise NotImplementedError
def __str__(self):
return f"{self.__class__.__name__}:面积={self.area():.2f},周长={self.perimeter():.2f}"
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
shapes = [Rectangle(3, 4), Circle(5), Square(6)]
total_area = 0
for shape in shapes:
print(shape)
total_area += shape.area()
print(f"\n总面积:{total_area:.2f}")
输出:
TEXT
📖 仅展示
Rectangle:面积=12.00,周长=14.00
Circle:面积=78.54,周长=31.42
Square:面积=36.00,周长=24.00
总面积:126.54
4. @dataclass:快速定义数据类
写一个只存数据的类,手动写 __init__、__repr__、__eq__ 很烦人。@dataclass 自动帮你生成:
PYTHON
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Student:
name: str
age: int
score: float = 0.0 # 默认值
# 自动获得 __init__、__repr__、__eq__
p1 = Point(3.0, 4.0)
p2 = Point(3.0, 4.0)
print(p1) # Point(x=3.0, y=4.0)
print(p1 == p2) # True(自动比较所有字段)
s = Student("Alice", 20, 85.5)
print(s) # Student(name='Alice', age=20, score=85.5)
💡
@dataclass 什么时候用: ① 一个类主要存数据,没多少方法。② 需要 __init__、__repr__、__eq__ 等模板化代码。③ 不想手写这些样板代码。简单场景下比手写类高效得多。
5. 常见应用场景
- 鸭子类型:Python 的
len()、for...in、sorted()都是基于协议——你的类实现对应方法就能用。 - 数据容器:实现
__getitem__+__len__让你的类支持索引和遍历。 - 运算符重载:
__add__、__eq__、__lt__让自定义类支持+、==、<等运算符。 - 数据类:
@dataclass快速定义配置、DTO(数据传输对象)、值对象。
❓ 常见问题
Q 魔术方法和普通方法有什么区别?
A 魔术方法是 Python 内置操作调用的——
print() 调用 __str__,len() 调用 __len__,== 调用 __eq__。你不需要直接调用它们,只需要实现它们,Python 会在合适的时候自动调用。Q
@dataclass 和普通类有什么区别?A
@dataclass 自动生成你懒得写的那几个方法。它只是一个代码生成器——生成的类和手写的一模一样。用 @dataclass 的类也可以有自己的方法。当你发现一直在写 __init__ 存一堆属性时,就该用 @dataclass 了。Q Python 是动态类型语言,多态是不是很自然的事情?
A 是的。Python 的"鸭子类型"让多态不需要继承——只要有同名方法就行。其他语言(如 Java/C++)需要用接口或抽象类才能实现多态,Python 天然就支持。好处是灵活,坏处是难以通过类型检查发现错误。
📖 小节
- 多态:不同类的对象如果有同名方法,可以用相同的方式调用
- 鸭子类型:"只要它会叫,它就是鸭子"——不关心类型,只关心方法
- 魔术方法:
__str__(打印)、__len__(长度)、__getitem__(索引)、__eq__(等于)、__add__(加法)等 @dataclass自动生成__init__、__repr__、__eq__,适合纯数据类
📝 作业
-
基础题(难度⭐):定义一个
Book类,实现__str__和__len__(返回页数)。创建两本书,输出print(book)和len(book)。 -
进阶题(难度⭐⭐):使用
@dataclass定义一个Product类(name, price, quantity)。实现__add__——把两个同 name 的产品合并(数量相加,价格取平均)。提示:放在 @dataclass 类中实现。 -
挑战题(难度⭐⭐⭐):定义一个
Deck类(一副扑克牌),实现__len__、__getitem__(支持索引和切片)、shuffle()(洗牌,用random.shuffle)。然后写一段代码:创建一副牌,发前 5 张作为手牌。