基础语法你会了,但离 "写优雅的 Python" 还差几个高级武器。老哥这篇把拷贝、迭代器、生成器、闭包、装饰器一次补齐。
一、浅拷贝 vs 深拷贝
先分清三种 "复制":
- 直接赋值:只是引用的别名,不产生拷贝;
- 浅拷贝:拷贝父对象,但内部子对象还是共享(只有第一层独立);
- 深拷贝:父对象和子对象全都独立拷贝。
浅拷贝的三种方式:切片[:]、工厂函数list()/set()、copy.copy()。
用内存地址看深浅拷贝的区别(核心:可变类型的子对象是否独立):
import copy list1 = [1, 2, 3, [100, 200, 300]] list2 = copy.copy(list1) # 浅拷贝 print(id(list2[3]) == id(list1[3])) # True 子列表还是同一个! list1[3].append(400) # 改子列表 print(list2) # [1, 2, 3, [100, 200, 300, 400]] 浅拷贝也被改了! list3 = copy.deepcopy(list1) # 深拷贝 print(id(list3[3]) == id(list1[3])) # False 子列表独立了! list1[3].append(500) print(list3) # 深拷贝不受影响特殊情况:数字、字符串等 "原子类型" 无法拷贝(copy 后地址不变);只含原子类型的元组深拷贝也不会变(地址不变)。
二、迭代器:记住位置、往前遍历
迭代器是一个能记住遍历位置的对象,只能往前不会后退。字符串、列表、元组都能创建迭代器。
用iter()创建、next()取下一个,取完抛StopIteration:
lst = [1, 2, 3] it = iter(lst) print(next(it)) # 1 print(next(it)) # 2 print(next(it)) # 3 print(next(it)) # StopIteration!判断可迭代 / 迭代器:
from collections.abc import Iterable, Iterator print(isinstance([], Iterable)) # True 列表可迭代 print(isinstance([], Iterator)) # False 但本身不是迭代器 print(isinstance((x for x in range(10)), Iterator)) # True 生成器是迭代器自定义迭代器:实现__iter__()(返回自己)和__next__()(取元素,取完抛StopIteration):
class Reverse: def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == 0: raise StopIteration self.index -= 1 return self.data[self.index] for char in Reverse([2, 3, 5, 7]): print(char) # 7 5 3 2三、生成器:按需产生值,省内存
生成器(generator)是创建迭代器的简单工具,用yield替代return。每次遇到yield暂停、保存状态,下次从暂停处继续。最大优势:按需生成,避免一次性占用大量内存。
两种创建方式:
# 方式一:推导式(注意是圆括号) gen = (x for x in range(5)) print(gen) # <generator object ...> for x in gen: print(x) # 方式二:yield 函数 def fibo(): # 斐波那契 a, b = 0, 1 while True: yield b a, b = b, a + b f = fibo() print(next(f)) # 1 print(next(f)) # 1 print(next(f)) # 2send():向生成器 "发送" 值,实现两个任务交替执行:
def gen(): task_id = 0 int_value = 0 char_value = "A" while True: match task_id: case 0: task_id = yield int_value # 返回并接收 send 的值 int_value += 1 case 1: task_id = yield char_value char_value = chr(ord(char_value) + 1) case _: task_id = yield g = gen() print(next(g)) # 0 print(g.send(1)) # A print(g.send(0)) # 1 print(g.send(1)) # B注意:第一次启动生成器要用send(None)(此时没有 yield 可接收值)。
四、闭包:函数 "记住" 外层变量
函数执行完,内部变量本应销毁。但闭包能让外层函数的变量被保存、重复使用,同时避免全局变量污染。
构建闭包三条件:外层函数定义内层函数、内层用到外层变量、外层把内层返回。
def linear(a, b): # 外层 def inner(x): # 内层 return a * x + b # 用到外层变量 a、b return inner # 返回内层 y1 = linear(1, 1) print(y1(5)) # 6 虽然 linear 已结束,但 a、b 还在 # 查看闭包保存的值 print(y1.__closure__[0].cell_contents) # 1 print(y1.__closure__[1].cell_contents) # 1五、装饰器:不改原函数,动态加功能
装饰器接收一个函数,返回一个包装后的新函数,从而不修改原代码就给函数加功能。
最基础的装饰器(让sqrt先求绝对值,负数也能开根号):
from math import sqrt def decorator(f): def inner(x): x = abs(x) # 加功能:先取绝对值 return f(x) return inner @decorator # 等价于 func = decorator(func) def func(x): return sqrt(x) print(func(-4)) # 2.0多层装饰器:离函数最近的先装饰,从内到外:
@get_integer @get_absolute def func(x): return sqrt(x)带参数的装饰器(如 "开 n 次方"):
def times(n): def get_absolute(f): def inner(x): x = abs(x) for i in range(n): x = f(x) return x return inner return get_absolute @times(2) # 开两次方 def func(x): return sqrt(x) print(func(-16)) # 2.0类装饰器:实现__call__()的类也能当装饰器:
class DecoratorClass: def __init__(self, f): self.f = f def __call__(self, x): x = abs(x) return self.f(x) @DecoratorClass def func(x): return sqrt(x) print(func(-4)) # 2.0老哥收尾
一句话总结这批 "进阶神器":
浅拷贝共享子对象、深拷贝全独立;迭代器记住位置只能往前;生成器用 yield 按需生成省内存;闭包记住外层变量;装饰器不改原函数就能加功能。
下一篇讲进程与线程—— 让程序 "多任务并行"。有问题随时回来找老哥。