1. 一等对象(First-Class Objects)
(1)概念引入
在 Python 中,函数是一等对象(First-Class Objects)。
所谓“一等对象”,是指在某种编程语言中,某类实体可以像普通数据一样被自由使用,而不受特殊限制。
如果一种语言中的函数能够像变量、字符串或数值一样被对待,那么我们就称该语言支持一等函数(First-Class Functions)特性。
Python、JavaScript 等现代高级语言均具备这一特性。
具体而言,作为一等对象的函数,至少应具备以下能力:
- 可以赋值给变量
- 可以作为参数传入另一个函数
- 可以作为另一个函数的返回值
- 可以存储在容器类型(如列表、元组、字典)中
这使得函数不仅仅是“可执行的代码块”,而是完整的对象,能够参与更高级的程序抽象。
(2)函数赋值给变量
在 Python 中,函数名本质上是一个指向函数对象的引用。因此,函数可以被直接赋值给变量。
def add(a, b): return a + b # 将函数赋值给变量 calc = add print(calc(2, 3)) # 输出:5此时,calc与add指向同一个函数对象。
通过变量调用函数,与通过原函数名调用在行为上完全一致。
这一特性为函数别名、策略切换等编程模式提供了基础。
(2)函数作为参数传入另一个函数
函数既然是对象,就可以作为参数传入其他函数。这一点与数学中的复合函数概念高度一致。
def apply(func, x, y): return func(x, y) def multiply(a, b): return a * b result = apply(multiply, 3, 4) print(result) # 输出:12在该示例中:
multiply被作为参数传入applyapply并不关心具体执行什么运算- 实际的行为由传入的函数决定
这种设计方式显著提升了代码的通用性与可扩展性。
(4)函数作为返回值
Python 中的函数还可以由另一个函数返回。这一能力是闭包(Closure)与函数工厂(Function Factory)的基础。
def make_power(n): def power(x): return x ** n return power square = make_power(2) cube = make_power(3) print(square(5)) # 输出:25 print(cube(5)) # 输出:125在上述代码中:
make_power根据参数动态生成函数- 返回的函数保留了外部变量
n - 不同返回值对应不同行为的函数实例
这使得函数可以被“按需生成”,极大增强了程序的表达能力。
(5)函数存入容器类型
函数既然是对象,也可以像普通数据一样存储在列表、元组或字典中。
def add(a, b): return a + b def sub(a, b): return a - b operations = { "add": add, "sub": sub } print(operations["add"](10, 5)) # 输出:15 print(operations["sub"](10, 5)) # 输出:5这种写法在实际项目中非常常见,例如:
- 命令分发表
- 路由映射
- 插件机制
- 状态机行为绑定
相比大量的if-elif结构,函数映射更加简洁、易维护。
(6)高阶函数的概念
高阶函数(Higher-Order Function),是指满足以下任意条件的函数:
- 接收一个或多个函数作为参数
- 返回一个函数作为结果
由于 Python 中函数是一等对象,因此高阶函数成为一种自然且常用的编程手段。
Python 内置的map、filter、sorted等,都是典型的高阶函数。
nums = [1, 2, 3, 4, 5] result = list(map(lambda x: x * 2, nums)) print(result) # 输出:[2, 4, 6, 8, 10](7)与其他语言的对比
在 Python、JavaScript 等语言中,函数与字符串、数字、对象拥有相同的“公民权利”,因此被称为一等公民(First-Class Citizens)。
而在传统的 Java、C++(不考虑现代函数对象与 Lambda 的情况下)中:
- 函数本身不能直接赋值给变量
- 不能作为普通参数或返回值使用
- 通常需要通过接口、函数指针或类进行间接实现
这使得函数在语言层面上属于“二等对象”,表达能力相对受限。
2. 装饰器(Decorator)
(1)装饰器的本质
在前一节中我们提到,以函数作为参数或返回值的函数称为高阶函数。
在 Python 中,使用高阶函数对另一个函数进行“包装(wrapping)”是一种非常常见的编程模式。
先看一个不使用装饰器语法的示例:
def higherOrder(inner): def func(): # 前置逻辑 print("before inner function") inner() # 后置逻辑 print("after inner function") return func def someFunc(): print("someFunc running") finalFunc = higherOrder(someFunc) finalFunc()执行结果为:
before inner function someFunc running after inner function在该示例中:
higherOrder是一个高阶函数inner是被包装的原始函数func是新的函数,用于在调用前后插入额外逻辑higherOrder(someFunc)返回一个“增强版”的函数
这正是装饰器的核心思想:
在不修改原函数代码的前提下,为其添加额外功能。
(2)装饰器语法糖的由来
虽然上述写法功能完整,但在实际开发中存在明显问题:
- 代码冗长
- 原函数名被新的变量覆盖
- 可读性和可维护性较差
为了解决这些问题,Python 提供了装饰器语法糖(Decorator Syntax),用于简化高阶函数的使用方式。
使用装饰器语法后,代码可以改写为:
def higherOrder(inner): def func(): # 前置逻辑 print("before inner function") inner() # 后置逻辑 print("after inner function") return func @higherOrder def someFunc(): print("someFunc running") someFunc()此时:
@higherOrder def someFunc(): ...等价于:
someFunc = higherOrder(someFunc)也就是说,装饰器本质上只是一个语法层面的简化写法,并没有引入新的语言能力。
(3)装饰器的执行时机
需要特别注意的一点是:
装饰器在函数定义阶段执行,而不是在函数调用阶段执行。
def decorator(func): print("decorator executed") def wrapper(): print("wrapper executed") func() return wrapper @decorator def test(): print("test executed") test()执行顺序为:
decorator executed wrapper executed test executed这说明:
decorator在解释器加载函数定义时立即执行wrapper在调用test()时执行
这一特性在框架设计和代码自动注册机制中尤为重要。
(4)带参数的函数装饰器(基础版)
现实中的函数通常都包含参数,因此装饰器也需要能够处理任意参数。
def log(func): def wrapper(*args, **kwargs): print(f"call function: {func.__name__}") return func(*args, **kwargs) return wrapper @log def add(a, b): return a + b result = add(3, 5) print(result)输出结果:
call function: add 8这里的关键点包括:
- 使用
*args和**kwargs接收任意参数 - 在不影响原函数行为的前提下增加日志功能
这是装饰器最常见、最实用的使用模式之一。
(5)装饰器的工程价值
装饰器的最大优势在于横切关注点(Cross-Cutting Concerns)的抽离,例如:
- 日志记录
- 权限校验
- 参数校验
- 性能统计
- 缓存控制
- 事务管理
通过装饰器,这些逻辑可以与业务代码彻底解耦。
def auth_required(func): def wrapper(user): if not user.get("is_login"): raise PermissionError("User not logged in") return func(user) return wrapper @auth_required def view_profile(user): print("view profile") user = {"is_login": True} view_profile(user)(6)装饰器在框架中的应用:以 Flask 为例
装饰器在 Python Web 框架中被大量使用,其中最典型的代表是Flask。
在传统方式下,开发 Web 服务器需要手动处理:
- TCP 连接
- HTTP 协议解析
- 路由分发
- 请求与响应生命周期
而在 Flask 中,只需通过装饰器即可完成路由绑定:
from flask import Flask app = Flask(__name__) @app.route("/hello") def hello(): return "Hello, Flask!"这里的@app.route("/hello")本质上是:
- 接收
hello函数 - 将其注册到路由表中
- 自动完成 HTTP 请求与函数调用的映射
开发者无需关心底层协议细节,只需专注业务逻辑。
3. 生成器(Generator)
(1)生成器的回顾与引入
在第 4 章中,我们已经接触过生成器语法,即与列表推导式非常相似的表达方式。例如:
gen = (x * x for x in range(5)) for value in gen: print(value)这种写法被称为生成器表达式(Generator Expression)。
它不会一次性生成所有结果,而是在遍历过程中按需计算并返回值。
然而,生成器表达式适用于逻辑简单的场景。当生成过程包含:
- 多分支判断
- 循环嵌套
- 状态保存
- 分阶段产出结果
时,就需要一种更通用、可控性更强的方式来创建生成器。
这正是yield语句存在的意义。
(2)使用yield定义生成器函数
在 Python 中,只要函数体中出现了yield关键字,该函数就不再是普通函数,而是一个生成器函数。
与return的核心区别在于:
return:结束函数执行,并返回一个值yield:返回一个值,并暂停函数执行状态
示例如下:
def count_up_to(n): i = 1 while i <= n: yield i i += 1调用该函数时:
gen = count_up_to(3) print(next(gen)) # 1 print(next(gen)) # 2 print(next(gen)) # 3此过程中:
- 每次调用
next(),函数都会从上一次yield的位置继续执行 - 局部变量的状态会被自动保存
- 直到函数执行结束,抛出
StopIteration异常
(3)yield与return的本质区别
从执行模型上看,生成器函数具有以下特点:
特性 | 普通函数 | 生成器函数 |
返回值 |
| 多次 |
执行状态 | 不可恢复 | 可暂停、可恢复 |
内存占用 | 可能较高 | 极低(惰性计算) |
示例对比:
def normal_func(): return [1, 2, 3] def generator_func(): yield 1 yield 2 yield 3normal_func()会立即构造完整列表,而generator_func()则按需产生数据。
(4)生成器的典型应用场景
生成器非常适合以下场景:
- 大数据量遍历
- 文件逐行读取
- 流式数据处理
- 网络数据消费
- 状态机实现
例如,逐行读取文件:
def read_lines(filepath): with open(filepath, "r", encoding="utf-8") as f: for line in f: yield line.strip()该方式相比一次性读取整个文件,更加节省内存,且具备良好的可扩展性。
(5)yield from的动机与作用
在 Python 3.3 中,引入了新的关键字yield from,用于生成器之间的委托(delegation)。
在没有yield from之前,如果一个生成器需要迭代另一个生成器,通常需要写成:
def gen1(): yield 1 yield 2 def gen2(): for value in gen1(): yield value yield 3虽然功能正确,但语法略显冗长。
(6)使用yield from简化生成器嵌套
使用yield from后,上述代码可以改写为:
def gen1(): yield 1 yield 2 def gen2(): yield from gen1() yield 3此时:
yield from gen1()会自动迭代gen1- 将
gen1产生的每一个值逐个yield给调用方 - 不需要显式编写循环逻辑
从行为上看,两种写法是完全等价的,但yield from更简洁,也更易维护。
(7)yield from的高级语义(概念层面)
在更深层次上,yield from不仅仅是语法简化,它还负责:
- 自动传递
StopIteration的返回值 - 转发
send()、throw()等生成器方法 - 构建生成器协程的基础能力
这些能力是实现协程(Coroutine)与 async / await 机制的重要基础(本书后续章节将详细展开)。
(8)综合示例:拆分生成逻辑
def produce_numbers(): yield from range(1, 4) def produce_letters(): yield from ["a", "b", "c"] def combined(): yield from produce_numbers() yield from produce_letters() for item in combined(): print(item)输出结果:
1 2 3 a b c该示例展示了:
- 多个生成器的组合
- 清晰的职责拆分
- 极低的额外内存开销
4. 迭代、可迭代对象与迭代器
(1)迭代(Iteration)的概念
迭代是一个通用术语,指的是逐个获取某一对象中元素的过程。
当我们使用显式或隐式循环结构,对一组元素进行顺序访问时,这一过程就称为迭代。
例如:
for x in [1, 2, 3]: print(x)无论是for循环、列表推导式,还是map、filter等高阶函数,其底层行为都依赖于“迭代”这一抽象概念。
需要强调的是:
迭代本身只是一个概念,而不是某种具体的数据结构或对象类型。
(2)可迭代对象(Iterable)
根据 Python 官方文档的定义,可迭代对象(Iterable)是指满足以下任一条件的对象:
- 实现了
__iter__()方法,该方法返回一个迭代器 - 或者实现了
__getitem__()方法,并且:
- 支持从
0开始的连续整数索引 - 当索引无效时抛出
IndexError异常
- 支持从
常见的可迭代对象包括:
listtuplestrdictsetrange- 文件对象
示例:
nums = [1, 2, 3] print(hasattr(nums, "__iter__")) # True(3)迭代器(Iterator)
迭代器是实现了迭代器协议(Iterator Protocol)的对象,必须同时具备以下两个方法:
__iter__():返回迭代器自身__next__():返回下一个元素;若无元素可返回,则抛出StopIteration异常
形式化定义如下:
Iterator = __iter__() + __next__()示例:手动获取列表的迭代器
nums = [1, 2, 3] it = iter(nums) print(next(it)) # 1 print(next(it)) # 2 print(next(it)) # 3 # next(it) -> StopIteration此处:
nums是可迭代对象it是由iter(nums)返回的迭代器
(4)可迭代对象与迭代器的区别
对比项 | 可迭代对象 | 迭代器 |
是否可多次遍历 | 是 | 否(一次性) |
是否保存状态 | 否 | 是 |
是否实现 | 不一定 | 必须 |
是否实现 | 必须 | 必须 |
示例对比:
nums = [1, 2, 3] it1 = iter(nums) it2 = iter(nums) print(next(it1)) # 1 print(next(it1)) # 2 print(next(it2)) # 1(独立的迭代器)同一个可迭代对象可以生成多个彼此独立的迭代器。
(5)iter()函数的作用
当可迭代对象作为参数传入内置函数iter()时,Python 会返回该对象对应的迭代器。
s = "abc" it = iter(s) print(next(it)) # a print(next(it)) # b print(next(it)) # c对于用户代码而言,通常无需显式调用iter(),因为 Python 会在必要时自动完成这一步。
(6)for循环背后的真实机制
在 Python 中,for循环本质上是基于迭代器协议的语法糖。
以下两段代码在行为上是等价的:
for x in iterable: print(x)等价于:
_it = iter(iterable) while True: try: x = next(_it) print(x) except StopIteration: break由此可以看出:
for循环会自动调用iter()创建迭代器- 在每一次循环中调用
next() - 捕获
StopIteration以结束循环
这也是为什么只要对象“可迭代”,就能直接用于for循环。
(7)自定义迭代器示例
class CountDown: def __init__(self, start): self.current = start def __iter__(self): return self def __next__(self): if self.current <= 0: raise StopIteration value = self.current self.current -= 1 return value for num in CountDown(3): print(num)输出结果:
3 2 1该示例完整实现了迭代器协议。
(8)生成器与迭代器的关系
需要特别说明的是:
Python 中的生成器,本身就是一种迭代器。
生成器对象自动实现了:
__iter__()__next__()
因此可以直接用于for循环、next()等所有迭代场景。
def gen(): yield 1 yield 2 g = gen() print(next(g)) # 1 print(next(g)) # 25. 标准输入、标准输出与管道
(1)基本概念概述
在终端(Terminal)或命令提示符(Command Prompt)中执行 Python 程序时,标准输入(stdin)、标准输出(stdout)与管道(pipe)是最基础、也是最常见的 I/O 机制。
在操作系统层面,一个进程在启动时,默认会关联三种标准数据流:
- 标准输入(stdin):程序读取数据的来源
- 标准输出(stdout):程序正常输出数据的去向
- 标准错误(stderr):程序异常或错误信息的输出通道
在本节中,我们重点关注前两者以及它们之间的组合方式。
(2)Python 中的标准输入与标准输出
在 Python 中:
input()用于从 标准输入 读取数据print()用于向 标准输出 写入数据
示例如下:
name = input("Enter your name: ") print("Hello,", name)当该程序在终端中运行时:
- 用户通过键盘输入内容
- 输入内容被传递给
input() - 结果通过
print()输出到终端屏幕
此时,终端既是程序的输入源,也是输出目标。
(3)标准输入与标准输出的重定向
标准输入和标准输出并不一定只能连接到终端。
在命令行环境中,它们可以被重定向到文件或其他程序。
① 输出重定向
输出重定向常用符号包括:
>:覆盖写入>>:追加写入
示例:
python demo.py > output.txt该命令会将程序原本输出到终端的内容,全部写入output.txt文件中(若文件已存在则覆盖)。
python demo.py >> output.txt则表示在文件末尾追加内容。
② 输入重定向
输入重定向使用<符号,用于将文件作为程序的输入来源。
python demo.py < input.txt在这种情况下:
- 程序中的
input()将从input.txt中读取数据 - 程序无需关心数据来自键盘还是文件
示例代码(demo.py):
text = input() print(text.upper())(4)使用命令行参数实现文件读写
在实际工程中,更通用的方式是通过命令行参数指定输入与输出文件。
import sys input_file = sys.argv[1] output_file = sys.argv[2] with open(input_file, "r", encoding="utf-8") as f: content = f.read() with open(output_file, "w", encoding="utf-8") as f: f.write(content.upper())执行方式如下:
python convert.py input.txt output.txt这种方式相比纯重定向更清晰,也更利于脚本复用与自动化。
(5)管道(Pipe)的概念
管道是一种用于连接多个程序的数据传递机制,其核心思想是:
将一个程序的标准输出,直接作为另一个程序的标准输入。
管道使用|符号表示。
program_a | program_b在该结构中:
program_a负责产生数据program_b负责消费数据
两者之间不需要中间文件。
(6)Python 程序中的管道示例
示例 1:统计文本行数
cat data.txt | python count_lines.pycount_lines.py内容如下:
import sys count = 0 for _ in sys.stdin: count += 1 print(count)这里:
cat负责读取文件并输出内容- Python 程序从
sys.stdin中读取数据 - 管道完成了程序间的数据衔接
示例 2:多程序组合处理数据
cat data.txt | python clean.py | python analyze.py每个程序只负责一项功能:
clean.py:清洗数据analyze.py:分析数据
这种方式具有极高的组合灵活性。
(7)UNIX 管道思想与软件设计哲学
管道的概念源自 UNIX 系统设计哲学,其核心原则包括:
- 程序应当只做一件事,并把它做好
- 程序应当通过标准输入/输出进行通信
- 程序应当可以被自由组合
正因如此,小程序之间可以通过管道形成强大的功能组合。
例如,假设有 5 个功能单一的程序:
- 每个程序只负责一种处理逻辑
- 通过管道进行任意组合
理论上可以形成:
5 × 5 = 25 种不同的组合程序而无需编写 25 个独立程序。