加载中

Python修养4

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

    回顾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.])
  1. 场景
  • 别人写好了库(如 DummyNet),不能改源码
  • 但需要修改运行流程(如给线性层加 ReLU 激活)
  1. 闭包写法
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)

image-20260523221745092

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 ... yieldyield 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
指定作用域

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

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被包装)

本文作者:flowwalker之码艺Blog

本文链接:/coding-notes-blog/posts/1bcc/

版权声明:本文采用 CC BY-NC-SA 4.0 许可协议