Python迭代器与生成器
1. 迭代器 Iterator
什么是迭代器
-
迭代器是访问可迭代对象的工具
-
迭代器是指用 iter(obj) 函数返回的对象(实例)
-
迭代器可以用next(it)函数获取可迭代对象的数据
迭代器函数iter和next
函数 | 说明 |
---|---|
iter(iterable) | 从可迭代对象中返回一个迭代器,iterable必须是能提供一个迭代器的对象 |
next(iterator) | 从迭代器iterator中获取下一个记录,如果无法获取一下条记录,则触发 StopIteration 异常 |
迭代器说明
-
迭代器只能往前取值,不会后退
-
用iter函数可以返回一个可迭代对象的迭代器
迭代器示例:
# 示例 可迭代对象
L = [1, 3, 5, 7]
it = iter(L) # 从L对象中获取迭代器
next(it) # 1 从迭代器中提取一个数据
next(it) # 3
next(it) # 5
next(it) # 7
next(it) # StopIteration 异常
# 示例2 生成器函数
It = iter(range(1, 10, 3))
next(It) # 1
next(It) # 4
next(It) # 7
next(It) # StopIteration
for x in L 的工作原理
当使用 for x in L 时,Python 内部会执行以下步骤:
-
获取迭代器:调用
L.__iter__()
,获取一个迭代器对象。 -
迭代元素:通过迭代器的
__next__()
方法逐个获取元素,直到抛出StopIteration
异常。 -
赋值和循环:将每次获取的元素赋值给变量
x
,并执行循环体。
因此,for x in L 本身并不是迭代器,而是利用了迭代器的机制来实现循环。
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current < self.end:
self.current += 1
return self.current - 1
else:
raise StopIteration
# 使用迭代器
my_iter = MyIterator(1, 5)
for num in my_iter:
2. 生成器
生成器是在程序运行时生成数据,与容器不同,它通常不会在内存中保留大量的数据,而是现用现生成。
-
yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。
-
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
-
每次使用 yield 语句生产一个值后,函数都将暂停执行,等待被重新唤醒。
-
yield 语句相比于 return 语句,差别就在于 yield 语句返回的是可迭代对象,而 return 返回的为不可迭代对象。
-
然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。
生成器可以用算法动态的生成数据
主要特点:
-
惰性求值:生成器不会一次性计算所有值,而是逐个生成值,节省内存。
-
yield
语句:生成器通过yield
语句返回值,每次调用next()
时,生成器会从上次yield
的位置继续执行。 -
自动实现迭代器协议:生成器自动实现了
__iter__()
和__next__()
方法,因此可以直接用于for
循环或其他迭代工具。
生成器有两种
-
生成器函数
-
生成器表达式
生成器函数
生成器函数通过 yield
语句返回值。每次调用 next()
时,生成器会从上次 yield
的位置继续执行,直到遇到下一个 yield
或抛出 StopIteration
异常。
yield 语句的语法
yield 表达式
生成器函数示例1:
## 定义一个生成器函数, 有 yield 的函数调用后回返回生成器对象
def myrange(stop):
i = 0
while i < stop:
yield i # 为 遍历次生产器的for 语句提供数据
i += 1
for x in myrange(5):
print('x=', x)
# 创建一个生成器对象
gen = myrange(5)
# 使用 next() 函数迭代生成器
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
生成器表达式
生成器表达式类似于列表推导式,但使用圆括号 ()
而不是方括号 []
。生成器表达式会逐个生成值,而不是一次性生成所有值。
-
语法:
( 表达式 for 变量 in 可迭代对象 [if 真值表达式])
[] 内容代表可以省略
-
作用
用推导式的形式创建一个生成器
-
示例
>>> [x ** 2 for x in range(1, 5)] # 列表解析(列表推导式)
[1, 4, 9, 16]
>>>
>>> (x ** 2 for x in range(1, 5)) # 生成器表达式
<generator object <genexpr> at 0x7f41dcd30a40>
>>> for y in (x ** 2 for x in range(1, 5)):
... print(y)
...
1
4
9
16
代码实现斐波那契数列(最少十个数)
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
fib_seq = fibonacci(10) #10个数列的斐波那契的生成器,相当于自创的range。
print(fib_seq)
for i in fib_seq: 取生成器里的数列
print(i, end="\t")
<generator object fb at 0x0000027237E5D460> #fib_seq是一个生成器
0 1 1 2 3 5 8 13 21 34
python 函数式编程
函数式编程(Functional Programming,FP)是一种编程范式,它将计算视为数学函数的求值,并强调使用纯函数(Pure Functions)和不可变数据(Immutable Data)。函数式编程的核心思想是将程序分解为一系列可组合的函数,通过函数的组合来实现复杂的逻辑。
在函数式编程中,函数被视为一等公民(First-Class Citizens),可以像普通变量一样被传递、返回或存储。
函数式编程的核心在于三个基本原则:纯函数、不可变数据和高阶函数。
1.纯函数
纯函数是指 对于相同的输入,总是返回相同的输出,并且没有副作用 的函数。副作用是指函数修改了函数外部的状态,例如修改全局变量、写入文件等。
纯函数的优点:
可测试性: 纯函数易于测试,因为它们的输出只取决于输入,不受外部状态的影响。
可缓存性: 纯函数的结果可以缓存,因为相同的输入总是产生相同的输出。
可并行性: 纯函数可以安全地并行执行,因为它们没有副作用,不会相互干扰。
# 纯函数示例
def sum(a, b):
return a + b
# 非纯函数示例
total = 0
def add_to_total(x):
global total
total += x
return total
sum
函数是纯函数,因为它不依赖于任何外部状态,并且对于相同的输入总是返回相同的输出。而 add_to_total
函数不是纯函数,因为它修改了全局变量 total
,产生了副作用。
2.不可变数据
不可变数据是指数据一旦创建就不能被修改。任何对数据的操作都会返回一个新的数据,而不是修改原数据。
Python 中的不可变数据类型包括:整数、字符串、元组等。
使用不可变数据可以提高代码的可靠性,因为它可以防止数据被意外修改。
3.高阶函数
高阶函数是指可以接受函数作为参数或返回函数作为结果的函数。
高阶函数是函数式编程的核心特性之一,它允许函数之间的组合和抽象。
(1)变量可以指向函数
以 Python 内置的求绝对值的函数abs()
为例,调用该函数用以下代码:
print(abs(-10)) # 输出:10
print(abs)
abs()是函数的调用,而abs是函数本身。
如果把函数本身赋值给变量呢?
# 函数本身赋给变量,即f指向abs所指向的函数
f = abs
a = f(-10)
print(a)
说明变量f
现在已经指向了abs
函数本身。直接调用abs()
函数和调用变量f()
完全相同。
(2) 函数名也是变量
函数名其实就是指向函数的变量!对于abs()
这个函数,完全可以把函数名abs
看成变量,它指向一个可以计算绝对值的函数!
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
示例:
def add(x, y, f):
return f(x) + f(y)
print(add(-5,6,abs))
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式
3.1 内置高阶函数
定义:将函数作为参数或返回值的函数。
常用:
(1)map(函数,可迭代对象)
-
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
(2)reduce(函数,可迭代对象)
-
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
(2)filter(函数,可迭代对象)
-
根据条件筛选可迭代对象中的元素,返回值为新可迭代对象。
(3)sorted(可迭代对象, key=函数, reverse=True)
-
排序,返回值为排序后的列表结果。
(4)max(可迭代对象, key = 函数)
-
根据函数获取可迭代对象的最大值。
(5)min(可迭代对象,key = 函数)
-
根据函数获取可迭代对象的最小值。
示例
def f(x):
return x*x
r = map(f,[1,2,3,4,5])
print(list(r))
# 把序列[1, 3, 5, 7, 9]变换成整数13579
from functools import reduce
def f2(x,y):
return x*10+y
r1 = reduce(f2,[1,2,3,4,5])
print(r1)
# 在一个 list 中,删掉偶数,只保留奇数
def f3(x):
return x%2 != 0
it = filter(f3,[1,2,3,4,5])
print(list(it))
# 按绝对值大小排序
print(sorted([36, 5, -12, 9, -21], key=abs))
# 求列表中的最大元素值
numbers = [3, 1, 4, 1, 5, -9, 2]
print(max(numbers,key=abs)) # 输出: 9
3.2 lambda 表达式
-
定义:是一种匿名函数
作用:
-- 作为参数传递时语法简洁,优雅,代码可读性强。
-- 随时创建和销毁,减少程序耦合度。
语法
# 定义: 变量 = lambda 形参: 方法体 # 调用: 变量(实参)
说明:
-- 形参没有可以不填
-- 方法体只能有一条语句,且不支持赋值语句。
it = map(lambda x: x * x, range(10))
print(tuple(it))
4. 闭包 closure
什么是闭包?
-
闭包是指引用了此函数外部嵌套函数的变量的函数 闭包就是能够读取其他函数内部变量的函数。只有函数内部的嵌套函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数,同时这个函数又引用了外部的变量“。
-
在本质上,闭包是将内部嵌套函数和函数外部的执行环境绑定在一起的对象。
闭包必须满足以下三个条件:
缺点
装饰器的作用:
函数装饰器的语法:
def 装饰器函数名(fn): 语句块 return 函数对象 @装饰器函数名 <换行> def 被装饰函数名(形参列表): 语句块
5.1 基本装饰器
-
必须有一个内嵌函数
-
内嵌函数必须引用外部函数中变量
-
外部函数返回值必须是内嵌函数。
-
def factorial(fun): def inner(x,y): return fun(x,y) return inner def add(x,y): return x+y def multiply(x,y): return x*y fa = factorial(add) fa(1,2) print(fa(2,3))
闭包的优缺点
优点
-
提供数据封装,避免全局变量污染。
-
支持函数式编程的特性,如高阶函数和柯里化。
-
可能导致内存泄漏,因为闭包会保留外部函数的变量引用,无法被垃圾回收。
-
过度使用闭包可能使代码难以理解和调试。
-
可以创建私有变量和方法。
-
5. 装饰器 decorators(专业提高篇)
什么是装饰器
-
装饰器是一个函数,主要作用是来用包装另一个函数或类
-
-
在不修改被装饰的函数的源代码,不改变被装饰的函数的调用方式的情况下添加或改变原函数的功能。
-
# 日志打印函数 def appendLog(func): print(f"{func.__name__}打印日志") # 闭包 def funcOut(func): def funcIn(): appendLog(func) func() return funcIn # 闭包函数名作为装饰器 @funcOut def func1(): print("func1正在运行...") @funcOut def func2(): print("func2正在运行...") # 添加装饰器后,不需要显式调用闭包的外函数 # func1 = funcOut(func1) func1() # func2 = funcOut(func2) func2()
5.2 带参数的装饰器
假设有一个函数:
def sum(a,b): return a+b
使用基本装饰器实现:
def outer(func): def inner(): return func() return inner @outer def sum(a,b): return a+b print(sum(1,2)) # 报错:TypeError: inner() takes 0 positional arguments but 2 were given
修改代码,给内部函数添加参数:
def outer(func): def inner(x,y): return func(x,y) return inner @outer def sum(a,b): return a+b print(sum(1,2)) # 输出3
如果再添加一个add函数:
def add(a, b, c): return a + b + c
那么还需要定义一个闭包来作为装饰器:
def outer1(func): def inner(x,y,z): return func(x,y,z) return inner @outer1 def add(a, b, c): return a + b + c print(add(1,2,3))
从上边的代码来看,两个参数相加,三个参数相加都要相应的添加对应的装饰器,能定义一个通用的装饰器吗?
可以考虑将闭包的内部函数中的参数换成*args
def outer(func): def inner(*args): return func(*args) return inner @outer def add(a, b, c): return a + b + c @outer def sum(a,b): return a+b print(add(1,2,3)) print(sum(1,2))
为了后续方便扩展,在内部方法中再添加**kwargs:
def outer(func): def inner(*args, **kwargs): return func(*args,**kwargs) return inner
至此带参数装饰器完成。
-
5.3 装饰器链
def uppercase(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper def exclamation(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result + "!" return wrapper @exclamation @uppercase def say_hello(name): return f"Hello, {name}" greeting = say_hello("Bob") print(greeting) # 输出 "HELLO, BOB!"