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. 常见应用场景


❓ 常见问题

Q 魔术方法和普通方法有什么区别?
A 魔术方法是 Python 内置操作调用的——print() 调用 __str__len() 调用 __len__== 调用 __eq__。你不需要直接调用它们,只需要实现它们,Python 会在合适的时候自动调用。
Q @dataclass 和普通类有什么区别?
A @dataclass 自动生成你懒得写的那几个方法。它只是一个代码生成器——生成的类和手写的一模一样。用 @dataclass 的类也可以有自己的方法。当你发现一直在写 __init__ 存一堆属性时,就该用 @dataclass 了。
Q Python 是动态类型语言,多态是不是很自然的事情?
A 是的。Python 的"鸭子类型"让多态不需要继承——只要有同名方法就行。其他语言(如 Java/C++)需要用接口或抽象类才能实现多态,Python 天然就支持。好处是灵活,坏处是难以通过类型检查发现错误。

📖 小节


📝 作业

  1. 基础题(难度⭐):定义一个 Book 类,实现 __str____len__(返回页数)。创建两本书,输出 print(book)len(book)

  2. 进阶题(难度⭐⭐):使用 @dataclass 定义一个 Product 类(name, price, quantity)。实现 __add__——把两个同 name 的产品合并(数量相加,价格取平均)。提示:放在 @dataclass 类中实现。

  3. 挑战题(难度⭐⭐⭐):定义一个 Deck 类(一副扑克牌),实现 __len____getitem__(支持索引和切片)、shuffle()(洗牌,用 random.shuffle)。然后写一段代码:创建一副牌,发前 5 张作为手牌。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏