Python修养4

Python修养4

目录

[TOC]

函数式程序设计

思想

  • 函数可以用来给变量赋值

    python中的函数是对象, 可像普通变量一样赋值,传值,返回

  • 函数可作为函数的参数

    python中的可调用对象( 即可以用()调用 ) 包括普通函数和实现了__call__方法的类实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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))) #=> 21
  • lambda表达式可以被赋值给变量, 也可作为函数的返回值和参数

    回顾filter(function,iterable) 挑选出可迭代对象**中每一个满足function的元素返回一个迭代器

    trick:

    1
    2
    3
    4
    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并修改

1
2
3
4
5
6
7
8
9
10
11
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))

闭包的作用

调用/补充别人的库函数而不修改别人的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ========== 原始库(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.])
  1. 场景
  • 别人写好了库(如 DummyNet),不能改源码
  • 但需要修改运行流程(如给线性层加 ReLU 激活)
  1. 闭包写法
1
2
3
4
5
6
7
8
9
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 仍然被内层函数"关"在里面,随时可用。

1
2
print(mynet(data))        # array([0., 0., 1., 3.])
# 负数被 ReLU 截断为 0

等价于:

1
2
3
4
5
# my_forward(mynet) 执行后:
# self = mynet
# 返回一个绑定了 self 的 forward 函数
# 等价于:
mynet.forward = lambda input: np.maximum(0, input * mynet.weight + mynet.bias)

偏应用函数(Partical Application)

用于固定函数中的某些参数

1
2
3
4
5
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异常, 碰到即结束(否则无限循环)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    x = [1,2,3,4]
    for i in x:
    print(i)
    #→ 等价于
    it = iter(x)
    while True:
    try:
    print(next(it))
    except StopIteration:
    break #空语句,什么都不做

操作

获取与移动

1
2
3
4
x=[1,2,3,4]
it=iter(x) #获取迭代器
print(next(it))
print(next(it)) #操作迭代器移向下一位

若到达末尾, 执行next将抛出异常

设置类迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)])

更佳的复用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 循环照样能跑

1
2
3
4
5
6
7
8
9
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 循环有回退机制

1
2
for item in X:
...

实际执行逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 第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 # 越界就停

过程拆解

1
2
3
4
5
6
7
8
9
10
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 都新建一个临时迭代器(从零开始计数),所以:

1
2
print(list(X))   # ['S', 'p', 'a', 'm']
print(list(X)) # ['S', 'p', 'a', 'm'] ✅ 还能用!

生成器(generator)

概念

  • 一种延时求值对象, 内部包含计算过程, 真正需要时才完成计算

例子:

1
2
3
4
5
6
7
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
#后面就迭代不了了
1
2
3
4
5
6
7
8
9
10
11
12
13
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如果先全部取出再遍历,结果会错

1
2
3
4
5
6
7
8
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 了。

1
2
3
4
# 内层生成器实际上存的是:
# gen0: "等我运行时,去看看 i 是多少" → 运行时 i=2
# gen1: "等我运行时,去看看 i 是多少" → 运行时 i=2
# gen2: "等我运行时,去看看 i 是多少" → 运行时 i=2

写法2没问题,因为内层列表推导式立即计算

1
2
3
4
5
6
7
8
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什么时候是对的

边取边用,在内层生成器被消费时,外层还没跑完:

1
2
3
4
5
6
7
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的函数称为生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 实现斐波那契数列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

image-20260523221745092

send语句

特点

  • yield 的函数,调用时返回生成器对象(迭代器)
  • 执行到 yield 暂停,下次从断点恢复
  • 遍历完即耗尽,不能复用
  • yield 右边产出值给外部,左边接收 send() 的值

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# ========== 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

双向通信:任务控制器(协程)

1
action = yield msg      # 产出 msg 给外部;恢复后 action = send 的值
调用生成器内部
next(controller)启动,跑到 yield,产出状态
send("start")恢复,action="start",执行逻辑,再 yield 暂停

惰性计算:自定义 map

对比列表推导式生成器版
内存全部存列表用到一个算一个
返回列表生成器对象
多次遍历❌ 只能一次
1
2
3
4
5
6
7
# 惰性 map:遍历时才计算
def myMap(func, iterable):
for arg in iterable:
yield func(arg)

# 等价于
map(func, iterable) # 内置 map 也是返回迭代器

陷阱list(x)for 遍历完后,生成器耗尽,再次遍历为空。

递归回溯:全排列

1
2
3
4
5
6
7
8
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')):

1
2
3
4
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 ... yieldyield from 传递
1
2
3
4
5
6
7
# 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表达式请求其值, 返回其值

1
2
3
expression=input("请输入算式")
result=eval(expression)
print(result)

compile和exec函数

函数作用返回值
eval将字符串看作Python表达式求值✅ 有返回值
exec执行字符串中的Python语句❌ 无返回值(None)
compile编译字符串为代码对象,供eval/exec使用代码对象
1
2
3
4
5
6
7
8
9
# eval有返回值
result = eval("3*4+5") # 17

# exec无返回值
exec('a=10')
print(a) # 10

result = exec("x = 10")
print(result) # None

exec可执行多行代码:

1
2
3
4
5
6
exec("""
a = 10
b = 20
result = a + b
print(result)
""") # 输出: 30

compile提高多次运行效率:

1
2
3
4
5
6
7
8
9
10
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

动态导入模块:

1
2
3
module_name = "math"
exec(f"import {module_name}")
print(sqrt(16)) # 4.0
指定作用域

evalexec可以通过字典指定作用域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

概念

获取并使用对象的信息

反射的核心是在运行时动态获取对象的属性和方法,并对其进行操作。

1
2
3
4
5
6
7
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

捕获异常,防止程序崩溃:

1
2
3
4
5
6
7
8
9
10
11
12
# 捕获所有异常
try:
f = open("ssr.txt","r")
except:
print("error")

# 捕获特定异常并获取错误信息
try:
print(aa)
except Exception as e:
print(e)
# name aa is not defined

多分支捕获:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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块都会执行:

1
2
3
4
5
6
try:
print(aa)
except:
pass # 空操作,忽略异常
finally:
print("done") # 不论有无异常都会执行

异常处理最佳实践:

写法说明
except:捕获所有异常(过于宽泛,不推荐)
except SpecificError:捕获特定异常 ✅
except Exception as e:捕获所有异常并获取信息
try...finally:用于资源释放(如关闭文件)
pass空语句,什么都不做

装饰器

基本概念

在不改变原有函数代码的情况下扩展函数功能

也许你已经在深度学习中见过装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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>)

自定义装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

带参数的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

用对象做装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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函数,其中再调用原函数

三层嵌套解析(带参数装饰器):

1
2
3
4
5
6
7
@good2(10)
def f(x,y): return x*y

# 执行步骤:
# 1. good2(10) → 返回 decorator 函数
# 2. decorator(f) → 返回 wrapper 函数
# 3. f = wrapper (原f被包装)