Python修养4
函数式程序设计
思想
函数可以用来给变量赋值
python中的函数是对象, 可像普通变量一样赋值,传值,返回
函数可作为函数的参数
python中的可调用对象( 即可以用
()调用 ) 包括普通函数和实现了__call__方法的类实例class A: def __init__(self,n): self.n = n def __call__(self,x): return self.n + x def add(x, y, f): return f(x) + f(y) print(add(1,10,abs)) #=> 11 print(add(1,10,A(5))) #=> 21lambda表达式可以被赋值给变量, 也可作为函数的返回值和参数
回顾
filter(function,iterable)挑选出可迭代对象**中每一个满足function的元素返回一个迭代器trick:
b=lambda x:lambda y:x+y #b是一个lambda表达式, 返回值是一个lambda表达式 a=b(3) #暂时存放3 print(a(2)) # 5⚠️函数内部声明的变量自动视为局部变量
⚠️可以直接读取外部全局变量, 但是若要修改🌊需要
global x的声明
闭包(closure)
能够维持外部变量值的函数
⚠️修改维持的变量要使用
nonlocal x声明告诉python不要创建局部变量, 要去外围func函数中查找x并修改
def func(x):
def g(y):
nonlocal x
x+=1
return x+y #g是一个闭包,即能够维持外部变量值的函数
return g
f=func(10)
print(f(4))
f=func(20)
print(f(4))
print(f(5))
闭包的作用
调用/补充别人的库函数而不修改别人的库
# ========== 原始库(dummy_network.py)==========
class DummyNet():
def __init__(self, weight, bias):
self.weight = weight
self.bias = bias
def forward(self, input):
return input * self.weight + self.bias
def __call__(self, input):
return self.forward(input)
# 使用方式
import numpy as np
data = np.array([-1., 0., 1., 2.])
mynet = DummyNet(weight=2.0, bias=-1.0)
print(mynet(data)) # array([-3., -1., 1., 3.])
- 场景
- 别人写好了库(如
DummyNet),不能改源码 - 但需要修改运行流程(如给线性层加 ReLU 激活)
- 闭包写法
def my_forward(self): # 外层函数接收 mynet 实例
def forward(input): # 内层函数才是真正的 forward
tmp = input * self.weight + self.bias
tmp = np.maximum(0, tmp) # 加上 ReLU 非线性激活
return tmp
return forward # 返回内层函数(闭包)
# 关键:把闭包绑定到实例上
mynet.forward = my_forward(mynet) # 不修改类,只改这个实例的 forward
内层 forward 函数捕获了外层 self 变量(即 mynet 实例),即使外层函数执行完毕,self 仍然被内层函数”关”在里面,随时可用。
print(mynet(data)) # array([0., 0., 1., 3.])
# 负数被 ReLU 截断为 0
等价于:
# my_forward(mynet) 执行后: # self = mynet # 返回一个绑定了 self 的 forward 函数 # 等价于: mynet.forward = lambda input: np.maximum(0, input * mynet.weight + mynet.bias)
偏应用函数(Partical Application)
用于固定函数中的某些参数
from functools import partical
add=lambda a,b:a+b
add1024=partical(add,1024)#固定a
print(add1024(1))
print(add1024(10))
迭代器
基本概念
能用
for i in x形式遍历的对象x称为可迭代对象(iterable)可迭代对象必须实现迭代器协议, 即
__iter__()和__next__()方法- 前者方法返回对象本身
- 后者方法返回下一元素
for i in x循环中python会自动调用x.__iter__()方法获得迭代器p, 并自动调用next(p)获取元素, 并自动检查StopIteration异常, 碰到即结束(否则无限循环)x = [1,2,3,4] for i in x: print(i) #→ 等价于 it = iter(x) while True: try: print(next(it)) except StopIteration: break #空语句,什么都不做
操作
获取与移动
x=[1,2,3,4]
it=iter(x) #获取迭代器
print(next(it))
print(next(it)) #操作迭代器移向下一位
若到达末尾, 执行next将抛出异常
设置类迭代器
class MyRange:
def __init__(self,n):
self.idx=0
self.n=n
def __iter__(self):
return self
def __next__(self):
if self.idx<self.n:
val=self.idx
self.idx+=1
return val
else:
#self.idx=0 #不加这行无法进行多次迭代
raise StopIteration()
for i in MyRange(5):
print(i)
print([i for i in MyRange(5)])
更佳的复用方式
class MyRange: # 容器:只存数据,不存迭代状态 def __init__(self, n): self.n = n def __iter__(self): return MyRangeIterator(self.n) # 每次新建迭代器! class MyRangeIterator: # 迭代器:存状态(i, n) def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self #可不写 def __next__(self): if self.i < self.n: val = self.i self.i += 1 return val raise StopIteration() x = MyRange(5) print([i*i for i in x]) print([i for i in x]) it=iter(x) print(next(it)) print(next(it)) for i in x: print(i) #才不会阶段输出 # [0, 1, 4, 9, 16] # [0, 1, 2, 3, 4] # 0 # 1 # 0 # 1 # 2 # 3 # 4
迭代重载
只写 __getitem__,不写 __iter__,for 循环照样能跑。
class stepper:
def __getitem__(self, i):
return self.data[i]
X = stepper()
X.data = 'Spam' #临时创建一个
for item in X: # 没写 __iter__ 也能 for 循环!
print(item) # S p a m
Python 的 for 循环有回退机制:
for item in X:
...
实际执行逻辑:
# 第1步:找 __iter__
try:
iterator = iter(X) # 调用 X.__iter__()
except TypeError:
# 第2步:没有 __iter__,就用 __getitem__ 硬凑
iterator = 内部迭代器(X) # Python 自动创建一个临时迭代器
# 内部迭代器的行为:
i = 0
while True:
try:
item = X[i] # 调用 X.__getitem__(i)
i += 1
except (IndexError, StopIteration):
break # 越界就停
过程拆解
X.data = 'Spam'
for 调用 iter(X):
└─ 发现没有 __iter__
└─ Python 新建临时迭代器,i = 0
├─ X[0] = 'S' → 输出 S
├─ X[1] = 'p' → 输出 p
├─ X[2] = 'a' → 输出 a
├─ X[3] = 'm' → 输出 m
└─ X[4] → IndexError → for 捕获 → 结束
stepper 本身没有状态。每次 for 循环,Python 都新建一个临时迭代器(从零开始计数),所以:
print(list(X)) # ['S', 'p', 'a', 'm']
print(list(X)) # ['S', 'p', 'a', 'm'] ✅ 还能用!
生成器(generator)
概念
- 一种延时求值对象, 内部包含计算过程, 真正需要时才完成计算
例子:
a=(i*i for i in range(5)) #a为生成器,其内容并未生成 print(a) #<generator object <genexpr> at 0x105b27b90> for x in a: print(x,end=' ') #0 1 4 9 16 #后面就迭代不了了matrix = ((i*3+j for j in range(3)) for i in range(3)) # matrix 是生成器,其元素也是生成器 for x in matrix: for y in x: print(y,end=" ") # 0 1 2 3 4 5 6 7 8 #区别于写法二 # matrix = ([i*3+j for j in range(3)] for i in range(3)) # # matrix 是生成器,其元素也是生成器 # for x in matrix: # for y in x: # print(y,end=" ") # 0 1 2 3 4 5 6 7 8延迟绑定Late Binding
写法1如果先全部取出再遍历,结果会错:
matrix = ((i*3+j for j in range(3)) for i in range(3)) rows = list(matrix) # ❌ 先全部取出来 for row in rows: print(list(row)) # [6, 7, 8] # [6, 7, 8] # [6, 7, 8] ← 全是 i=2 的结果!为什么?
内层生成器
(i*3+j for j in range(3))没有立刻算,它只记住了”以后要用i“。当
list(matrix)把外层迭代完后,i的最终值是2。之后你再遍历内层生成器,它们才想起来去查i,但此时i已经是2了。# 内层生成器实际上存的是: # gen0: "等我运行时,去看看 i 是多少" → 运行时 i=2 # gen1: "等我运行时,去看看 i 是多少" → 运行时 i=2 # gen2: "等我运行时,去看看 i 是多少" → 运行时 i=2写法2没问题,因为内层列表推导式立即计算:
matrix = ([i*3+j for j in range(3)] for i in range(3)) rows = list(matrix) for row in rows: print(row) # [0, 1, 2] # [3, 4, 5] # [6, 7, 8] ✅ 正确!列表推导式在
i=0,1,2的当下就把值算好存进列表了,不会受后续i变化的影响。
写法1什么时候是对的
边取边用,在内层生成器被消费时,外层还没跑完:
matrix = ((i*3+j for j in range(3)) for i in range(3)) for row in matrix: # 外层取一个 print(list(row)) # 内层立刻消费 ✅ # [0, 1, 2] # [3, 4, 5] # [6, 7, 8]
- 与列表解析式(即列表推导式list comprehension)区别:
i=(x+1 for x in lst)生成器表达式, 不需要生成结果列表, 延时求值print([x+1 for x in lst])列表解析, 生成了结果列表
yield关键字定义生成器
使用了yield的函数称为生成器
def test_yield():
yield 1
yield 2
yield (1,2)
a=test_yield()
while True:
try:
print(next(a))
except StopIteration:
break
def h():
print ('To be brave')
yield 5
x = h() #无输出
print(next(x)) #To be brave #5
next(x) #引发异常
使用 yield 实现斐波那契数列:
def fibonacci(n): # 生成器函数 – 用于求斐波那契数列前n项 a, b, counter = 0, 1, 0 while counter <= n: yield a a, b = b, a + b counter += 1 f = fibonacci(10) # f 是一个迭代器,由生成器返回生成 while True: try: print (next(f), end=" ") except StopIteration: break #或 #for i in f: # print (i, end=" ")好处是:不用事先准备过程需要的所有元素, 仅仅迭代到该元素时才计算, 可以处理♾️
称之为延迟计算 或 惰性求值(lazy evaluation)
send语句
特点
- 带
yield的函数,调用时返回生成器对象(迭代器) - 执行到
yield暂停,下次从断点恢复 - 遍历完即耗尽,不能复用
yield右边产出值给外部,左边接收send()的值
示例
def h():
print ('hello')
m = yield 5 #m的值要从外部获得
print ("m=", m)
d = yield 12
print ('d=' ,d )
yield 13
c = h() #无输出
x = next(c)
#默认等价于多了一句c.send(None)
print("x=",x)
y = next(c) #此时m赋值为了None
print("y=",y)
z = c.send("good")
print("z=",z)
# hello
# x= 5
# m= None
# y= 12
# d= good
# z= 13
应用⚠️⚠️⚠️
# ========== 1. 生成器做任务控制器(协程/状态机)==========
def task_controller():
task_id = 1
while True:
action = yield f"任务{task_id} 等待指令"
if action == "start":
print(f"任务{task_id} 开始执行...")
elif action == "pause":
print(f"任务{task_id} 已暂停")
elif action == "stop":
print(f"任务{task_id} 已停止")
task_id += 1
elif action is None:
break
controller = task_controller()
next(controller) # 启动
print(controller.send("start")) # 任务1开始 → 产出"任务2等待指令"
print(controller.send("pause")) # 任务2暂停 → 产出"任务3等待指令"
print(controller.send("stop")) # 任务3停止 → task_id=2 → 产出"任务4等待指令"
# ========== 2. 用生成器实现 map(惰性求值)==========
def myMap(func, iterable):
for arg in iterable:
yield func(arg)
names = ["ana", "bob", "dogge"]
x = myMap(lambda x: x.capitalize(), names)
print(x) # <generator object ...>
print(list(x)) # ['Ana', 'Bob', 'Dogge']
for name in x: # ❌ 无输出,已耗尽
# ========== 3. 用生成器求全排列(递归回溯)==========
def perm(items):
n = len(items)
if n == 1:
yield items
for i in range(n):
v = items[i:i+1]
rest = items[:i] + items[i+1:]
for p in perm(rest):
yield v + p
a = perm('abc')
print(next(a)) # abc
print(next(a)) # acb
print("****")
for b in a: # 从剩余继续
print(b) # bac, bca, cab, cba
双向通信:任务控制器(协程)
action = yield msg # 产出 msg 给外部;恢复后 action = send 的值
调用 生成器内部 next(controller)启动,跑到 yield,产出状态send("start")恢复, action="start",执行逻辑,再yield暂停惰性计算:自定义 map
对比 列表推导式 生成器版 内存 全部存列表 用到一个算一个 返回 列表 生成器对象 多次遍历 ✅ ❌ 只能一次 # 惰性 map:遍历时才计算 def myMap(func, iterable): for arg in iterable: yield func(arg) # 等价于 map(func, iterable) # 内置 map 也是返回迭代器陷阱:
list(x)或for遍历完后,生成器耗尽,再次遍历为空。递归回溯:全排列
def perm(items): if len(items) == 1: yield items for i in range(len(items)): v = items[i:i+1] rest = items[:i] + items[i+1:] for p in perm(rest): # 递归子问题 yield v + p # 拼接产出执行流程(
perm('abc')):perm('abc') ├─ 固定 'a' + perm('bc') → abc, acb ├─ 固定 'b' + perm('ac') → bac, bca └─ 固定 'c' + perm('ab') → cab, cba特点:
- 深度优先遍历解空间
- 遇到完整解就
yield,不用等全部算完next()和for可混用,共用迭代状态易错点
错误 原因 生成器遍历为空 迭代器已耗尽,需重新创建 send(非None)启动未启动的生成器只能用 next()或send(None)递归生成器没 yield from子生成器的结果需要用 for ... yield或yield from传递# 3.3+ 可用 yield from 简化 def perm(items): if len(items) == 1: yield items for i in range(len(items)): rest = items[:i] + items[i+1:] yield from (items[i:i+1] + p for p in perm(rest))
其他语法特性
eval函数
概念
把字符串看作python表达式请求其值, 返回其值
expression=input("请输入算式")
result=eval(expression)
print(result)
compile和exec函数
| 函数 | 作用 | 返回值 |
|---|---|---|
eval |
将字符串看作Python表达式求值 | ✅ 有返回值 |
exec |
执行字符串中的Python语句 | ❌ 无返回值(None) |
compile |
编译字符串为代码对象,供eval/exec使用 | 代码对象 |
# eval有返回值
result = eval("3*4+5") # 17
# exec无返回值
exec('a=10')
print(a) # 10
result = exec("x = 10")
print(result) # None
exec可执行多行代码:
exec("""
a = 10
b = 20
result = a + b
print(result)
""") # 输出: 30
compile提高多次运行效率:
str = "for i in range(0,10): print (i,end = ' ')"
c = compile(str,'','exec') # 编译
exec(c) #=> 0 1 2 3 4 5 6 7 8 9
x=4
y=5
str2 = "3*x + 4*y"
c2 = compile(str2, '', 'eval') # compile过运行更快,适合多次调用
result = eval(c2)
print(result) # 32
动态导入模块:
module_name = "math"
exec(f"import {module_name}")
print(sqrt(16)) # 4.0
指定作用域
eval和exec可以通过字典指定作用域:
a = 10
b = 20
def func(x):
return x+1
scope={} #用字典当作用域
scope['a'] = 3
scope['b'] = 4
scope['func'] = lambda x:x*x
print(eval("a+b")) #=> 30 (使用全局作用域)
print(eval("a+b",scope)) #=> 7 (使用自定义作用域)
# print(eval("a+b+c",scope)) # error, c无定义
print(eval("func(7)")) #=> 8 (调用全局func)
print(eval("func(7)",scope)) #=> 49 (调用scope中的lambda)
exec("print(a)") #=> 10
exec("print(a)",scope) #=> 3
⚠️ 作用域查找规则:
参数 说明 不提供scope 使用当前全局/局部作用域 提供 scope字典从字典中查找变量 变量不存在 抛出 NameError
反射(reflection)
概念
获取并使用对象的信息
反射的核心是在运行时动态获取对象的属性和方法,并对其进行操作。
class A():
def __init__(self,x):
self.x = x
def func(self):
print("x=",self.x)
a = A(12)
四大反射函数:
| 函数 | 作用 | 示例 |
|---|---|---|
dir(obj) |
列出对象的所有属性和方法 | dir(a) |
hasattr(obj, name) |
判断对象是否有指定属性/方法 | hasattr(a, 'x') |
getattr(obj, name) |
获取对象的属性值或方法 | getattr(a, 'x') |
setattr(obj, name, value) |
设置对象的属性值 | setattr(a, 'x', 100) |
a = A(12)
# dir: 列出所有属性和方法
print(dir(a))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
# '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
# '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__',
# '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
# '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
# 'func', 'x']
# hasattr: 判断是否存在
if hasattr(a,'x'): # True
setattr(a,'x','hello,world') # 修改属性值
print(getattr(a,'x')) # hello,world
# getattr获取方法并调用
if hasattr(a,'func'):
getattr(a,'func')() # x= hello,world
# 等价于 a.func()
反射的应用场景:
- 动态调用方法: 根据字符串名称调用对象方法
- 动态配置: 通过配置文件指定要操作的属性名
- 调试 introspection: 查看对象内部结构
- 插件系统: 动态加载和调用模块中的功能
异常处理
try … except
捕获异常,防止程序崩溃:
# 捕获所有异常
try:
f = open("ssr.txt","r")
except:
print("error")
# 捕获特定异常并获取错误信息
try:
print(aa)
except Exception as e:
print(e)
# name aa is not defined
多分支捕获:
try:
print(aa)
except IOError as e:
print(f"IO错误: {e}")
except NameError as e:
print(f"名称错误: {e}")
# name 'aa' is not defined
# 兜底捕获
try:
可能出错的代码
except 特定异常 as e:
处理特定错误
except Exception as e: # 捕获一切错误
print(f"其他错误: {e}")
try … finally
无论有无异常,finally块都会执行:
try:
print(aa)
except:
pass # 空操作,忽略异常
finally:
print("done") # 不论有无异常都会执行
异常处理最佳实践:
写法 说明 except:捕获所有异常(过于宽泛,不推荐) except SpecificError:捕获特定异常 ✅ except Exception as e:捕获所有异常并获取信息 try...finally:用于资源释放(如关闭文件) pass空语句,什么都不做
装饰器
基本概念
在不改变原有函数代码的情况下扩展函数功能
也许你已经在深度学习中见过装饰器:
import torch
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
@torch.no_grad() # 装饰器语法
def double(tensor):
return tensor * 2
result = double(x)
print(result)
# tensor([2., 4., 6.])
# 对比:不用装饰器
def double(tensor):
return tensor * 2
result = double(x)
print(result)
# tensor([2., 4., 6.], grad_fn=<MulBackward0>)
自定义装饰器
def good(func):
def wrapper(*args):
print("%s called" % func.__name__)
return func(*args)+5
return wrapper
@good # good是一个装饰器
def mysum(a,b,c):
return a + b + c
print(mysum(3,4,5))
# mysum called
# 17
# @good 等价于 mysum = good(mysum)
# mysum(3,4,5) 等价于 good(mysum)(3,4,5)
带参数的装饰器
def good2(var1):
def decorator(func):
def wrapper(*args):
print("%s called" % func.__name__)
return func(*args) + var1
return wrapper
return decorator
@good2(10)
def f(x,y):
return x * y
@good2(100)
def f2(x,y):
return x * y
print(f(1,2)) # f called 12 (1*2+10)
print(f2(1,2)) # f2 called 102 (1*2+100)
# @good2(10) 等价于 good2(10)(f)(1,2)
用对象做装饰器
class decorator:
def __init__(self,x):
self.x = x
def de(self,func):
def wrapper(*args):
print("%s called" % func.__name__)
return func(*args) + self.x
return wrapper
myobj = decorator(100)
@myobj.de
def f3(x,y,z):
return x + y + z
print(f3(1,2,3)) # f3 called 106 (1+2+3+100)
myobj.x = 7 # 修改装饰器的参数
print(f3(1,2,3)) # f3 called 13 (1+2+3+7)
装饰器执行时机:
阶段 行为 定义时 @decorator立即执行,原函数被替换为wrapper调用时 执行的是wrapper函数,其中再调用原函数 三层嵌套解析(带参数装饰器):
@good2(10) def f(x,y): return x*y # 执行步骤: # 1. good2(10) → 返回 decorator 函数 # 2. decorator(f) → 返回 wrapper 函数 # 3. f = wrapper (原f被包装)
