Python 基础知识:多态、闭包与装饰器

通过示例理解 Python 的多态、抽象基类、鸭子类型、闭包和装饰器。

已发布文章编程语言与开发入门11 分钟阅读发布于 2026年8月14日更新于 2026年8月29日
文章目录
  1. Python 基础知识:多态、闭包与装饰器
  2. 一、Python 语法
  3. MRO(Method Resolution Order)方法解析顺序
  4. Python 中的多态
  5. 二、闭包与装饰器
  6. 1. 闭包
  7. 2. 装饰器
  8. 3. 装饰器通用模板
LOCAL NOTE高亮并记笔记
0 / 1000 · 只保存在这台设备
READER NOTESPython 基础知识:多态、闭包与装饰器》的本机笔记

Python 基础知识:多态、闭包与装饰器

本文整理 Python 面向对象、多态、闭包与装饰器的基础知识和代码示例。

一、Python 语法

MRO(Method Resolution Order)方法解析顺序

延伸阅读:Python MRO 详解

本节目前仅收录延伸阅读链接,MRO 的 C3 线性化规则与多继承示例后续补充。

Python 中的多态

"""
概述:
    多态指的是同一个事物在不同场景下表现出来的不同形态、状态。
    Python 中的多态指的是,同一个函数传入不同的对象,会产生不同的结果。
多态的常见前提:
    1. 存在继承或共同协议。
    2. 对象提供同名方法。
好处:
    提高代码的可维护性,实现一个函数、多种效果。
应用场景:
    函数接收具有共同接口的对象,传入什么对象,就调用其对应的功能。
"""

class Animal(object):
    """定义一个 Animal 类。"""

    def run(self):
        print("动物都会跑")


class Cat(Animal):
    """定义一个 Cat 类,继承 Animal。"""

    def run(self):
        print("猫会跑")


class Dog(Animal):
    """定义一个 Dog 类,继承 Animal。"""

    def run(self):
        print("狗会跑")


class Test:
    def run(self):
        print("Test 类也有 run 方法")


def func(obj):
    obj.run()


# 根据不同对象执行不同的 run 方法。
func(Animal())
func(Cat())
func(Dog())

# Python 采用鸭子类型,只要对象提供 run 方法,就可以传入。
func(Test())

1. 关于“伪多态”这一说法

多态是指同一个方法名由不同对象调用时表现出不同行为。Python 采用动态类型和鸭子类型,不要求所有对象继承同一个父类。

有些学习材料把“子类没有重写父类方法,导致所有子类执行相同父类逻辑”称为“伪多态”。这个词并不是 Python 社区的标准术语,更准确的说法是:子类继承了父类的默认实现,但没有提供特化行为。

下面是这种情况的示例:

class Animal:
    def speak(self):
        print("动物叫声")


class Dog(Animal):
    # 没有重写 speak 方法。
    pass


class Cat(Animal):
    # 没有重写 speak 方法。
    pass


def make_sound(animal):
    animal.speak()


make_sound(Dog())  # 输出:动物叫声
make_sound(Cat())  # 输出:动物叫声

如果业务目标是让不同动物发出不同叫声,就需要由子类提供不同实现。

2. 解决方案一:重写子类方法

子类显式重写父类同名方法:

class Animal:
    def speak(self):
        print("动物叫声")


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


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


def make_sound(animal):
    animal.speak()


make_sound(Dog())
make_sound(Cat())

输出:

汪汪汪
喵喵喵

3. 解决方案二:使用抽象基类强制实现接口

使用 abc.ABC@abstractmethod,可以要求具体子类实现抽象方法;否则该子类无法实例化。

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        """子类必须实现该方法。"""
        raise NotImplementedError


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


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


def make_sound(animal):
    animal.speak()


make_sound(Dog())
make_sound(Cat())

如果子类忘记实现抽象方法:

class Pig(Animal):
    pass


pig = Pig()
# TypeError: Can't instantiate abstract class Pig with abstract method speak

抽象基类可以在实例化阶段发现接口没有实现的问题,适合需要明确接口约束的工程代码。

4. 鸭子类型与运行时容错

如果不能修改父类,可以在运行时判断具体类型是否重写了方法。不过这种写法会让函数依赖某个具体父类,通常不如明确协议或抽象基类清晰。

class Animal:
    def speak(self):
        print("通用叫声")


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


class Fish(Animal):
    pass


def safe_speak(animal):
    if type(animal).speak is not Animal.speak:
        animal.speak()
    else:
        print(f"{type(animal).__name__} 没有特化叫声")


safe_speak(Dog())
safe_speak(Fish())

输出:

汪汪
Fish 没有特化叫声

5. 常见问题总结

  1. 子类继承父类,但没有重写需要特化的同名方法。
  2. 调用者假定不同对象会有不同行为,但父类提供了相同的默认实现。
  3. 接口约束不明确,问题只能在运行时暴露。

6. 最佳实践建议

  1. 需要明确接口约束时,优先使用 ABCProtocol 或清晰的公共接口。
  2. 简单脚本可以直接依赖鸭子类型,但应保证错误信息清晰。
  3. 不要只为了“实现多态”而强制建立继承关系;组合和协议有时更合适。

二、闭包与装饰器

1. 闭包

构成条件

  1. 函数嵌套。
  2. 内层函数使用外层函数的局部变量。
  3. 外层函数返回内层函数对象。

分步示例

def outer(x):
    def inner(y):
        return x + y

    return inner


# 调用 outer(10),创建 x=10,并返回 inner 函数对象。
f = outer(10)

# outer 已执行结束,但闭包仍保留 x=10。
result = f(5)
print(result)  # 输出 15

相关关键字:

  • nonlocal:在内层函数中修改外层函数的局部变量。
  • global:在函数中修改模块级全局变量。

2. 装饰器

装饰器可以在不修改原函数调用方式的情况下,为函数增加额外行为。函数装饰器通常建立在闭包和高阶函数之上。

无参装饰器

from functools import wraps


def log_decorator(fn):
    @wraps(fn)
    def inner(*args, **kwargs):
        print("函数开始执行")
        result = fn(*args, **kwargs)
        print("函数执行结束")
        return result

    return inner


@log_decorator
def add(a, b):
    return a + b


print(add(3, 5))

@log_decorator 等价于:

add = log_decorator(add)

执行流程:

  1. 定义完 add 后,Python 调用 log_decorator 包装原函数。
  2. log_decorator 返回 inner,名称 add 随后指向包装后的函数。
  3. 调用 add(3, 5) 时,实际执行 inner(3, 5)
  4. inner 执行前置逻辑、原函数和后置逻辑,并返回原函数结果。

有参装饰器

from functools import wraps


def log_type(flag):
    def decorator(fn):
        @wraps(fn)
        def inner(*args, **kwargs):
            if flag == "+":
                print("【加法运算日志】")
            elif flag == "-":
                print("【减法运算日志】")
            return fn(*args, **kwargs)

        return inner

    return decorator


@log_type("+")
def add(a, b):
    return a + b


print(add(10, 20))

@log_type("+") 可以拆解为:

temporary_decorator = log_type("+")
add = temporary_decorator(add)

3. 装饰器通用模板

无参装饰器:

from functools import wraps


def decorator(fn):
    @wraps(fn)
    def inner(*args, **kwargs):
        # 前置逻辑
        result = fn(*args, **kwargs)
        # 后置逻辑
        return result

    return inner

有参装饰器:

from functools import wraps


def decorator(parameter):
    def middle(fn):
        @wraps(fn)
        def inner(*args, **kwargs):
            # 前置逻辑
            result = fn(*args, **kwargs)
            # 后置逻辑
            return result

        return inner

    return middle

模板说明:

  1. *args 接收所有位置参数。
  2. **kwargs 接收所有关键字参数。
  3. 包装函数应返回原函数结果,避免返回值丢失。
  4. 使用 functools.wraps 保留原函数的名称、文档和其他元数据。
JARVIS · 当前文章

有哪里没看懂?可以只问这篇。

Jarvis 会限定在《Python 基础知识:多态、闭包与装饰器》及其公开关联内容中检索,并把引用定位回原文章节。

JARVIS / ARTICLE针对《Python 基础知识:多态、闭包与装饰器》提问
当前范围Python 基础知识:多态、闭包与装饰器不会悄悄扩大到全站
0 / 1000

准备好了。当前只会围绕Python 基础知识:多态、闭包与装饰器回答。

READER SIGNAL

这篇内容对你有帮助吗?

不需要登录。你的反馈会直接进入作者待处理列表。