高级语法

1 浅拷贝与深拷贝

拷贝是 Python 中数据复用的核心手段,核心差异在于是否复制嵌套对象,直接决定数据独立性。理解拷贝机制能避免开发中因数据共享导致的意外修改问题。

1.1 核心概念与适用场景

拷贝类型 核心特点 内存机制 适用场景
直接赋值 仅传递引用,不产生新对象 多个变量指向同一内存地址 无需数据独立,共享原始数据(如只读配置)
浅拷贝 拷贝父对象,嵌套子对象仍共享引用 顶层对象新地址,嵌套对象同地址 单层数据结构(如扁平列表),无需修改嵌套内容
深拷贝 完全拷贝父对象及所有子对象 顶层 + 嵌套对象均为新地址 嵌套数据结构(如列表套字典),需完全独立修改

1.2 浅拷贝实现方式

浅拷贝有 3 种常用实现方式,效果完全一致,可根据场景选择:

  1. 切片操作:list2 = list1[:](仅适用于序列类型,如列表、元组)
  2. 工厂函数:list2 = list(list1)set2 = set(set1)(支持序列和集合)
  3. copy模块:list2 = copy.copy(list1)(通用型,支持所有可拷贝对象)

1.3 浅拷贝实战案例(含嵌套数据)

1768976585501

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
import copy

# 定义含嵌套列表的原始数据(模拟复杂业务数据)
user_data = [
"张三", # 顶层不可变数据(字符串)
25, # 顶层不可变数据(整数)
["北京", "13800138000"] # 嵌套可变数据(列表)
]

# 浅拷贝
user_data_copy = copy.copy(user_data)

# 验证内存地址变化
print("原始数据地址:", id(user_data)) # 输出:原始数据地址: 28974056
print("拷贝数据地址:", id(user_data_copy)) # 输出:拷贝数据地址: 28974120(顶层地址不同)
print("嵌套列表地址是否相同:", id(user_data[2]) == id(user_data_copy[2]) # 输出:True(嵌套对象共享)

# 修改顶层不可变数据(无影响)
user_data[0] = "李四"
print("修改顶层后 - 原始数据:", user_data[0]) # 输出:李四
print("修改顶层后 - 拷贝数据:", user_data_copy[0]) # 输出:张三(不受影响)

# 修改嵌套可变数据(同步变化)
user_data[2][1] = "13900139000"
print("修改嵌套后 - 原始数据:", user_data[2][1]) # 输出:13900139000
print("修改嵌套后 - 拷贝数据:", user_data_copy[2][1]) # 输出:13900139000(同步修改)

1.4 深拷贝实现与案例

深拷贝仅通过copy模块的deepcopy()实现,完全隔离所有层级数据:

1768976650153

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy

# 复用上面的user_data原始数据
user_data_deep = copy.deepcopy(user_data)

# 验证内存地址变化
print("深拷贝嵌套列表地址:", id(user_data_deep[2])) # 输出:28974256(新地址)
print("地址是否独立:", id(user_data[2]) != id(user_data_deep[2]) # 输出:True

# 修改原始数据的嵌套内容(深拷贝不受影响)
user_data[2][1] = "13700137000"
print("修改后 - 原始嵌套数据:", user_data[2][1]) # 输出:13700137000
print("修改后 - 深拷贝嵌套数据:", user_data_deep[2][1]) # 输出:13900139000(完全独立)

1.5 特殊场景说明

  1. 不可变类型(int、str、tuple)的拷贝:无论浅拷贝还是深拷贝,都不会产生新对象(因不可变类型无法修改,复用内存更高效)

    1
    2
    3
    4
    5
    import copy
    a = (1, 2, 3) # 元组(不可变类型)
    b = copy.copy(a)
    c = copy.deepcopy(a)
    print(id(a) == id(b) == id(c)) # 输出:True(均指向同一地址)
  2. 元组嵌套可变对象:元组本身不可变,但嵌套的可变对象仍遵循拷贝规则

    1
    2
    3
    4
    5
    import copy
    t = (1, [2, 3])
    t_copy = copy.copy(t)
    t[1].append(4)
    print(t_copy[1]) # 输出:[2, 3, 4](嵌套列表共享引用)

2 迭代器

迭代器是 Python 中遍历可迭代对象的核心机制,是可以记住遍历位置的对象,大幅提升内存效率,尤其适用于大数据量场景。

2.1 核心概念区分

概念 定义 判定方式
可迭代对象(Iterable) 支持被iter()函数调用生成迭代器的对象 isinstance(obj, Iterable)
迭代器(Iterator) 实现__iter__()__next__()方法的对象 isinstance(obj, Iterator)
  • 可迭代对象

    • 可迭代对象(Iterable)指能被 for 循环遍历的对象,通常为实现了 iter() 方法,或实现了 getitem() 并支持从 0 开始的整数索引的对象

    • 使用 isinstance(obj, collections.abc.Iterable) 进行类型判定

      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
      from collections.abc import Iterable

      # 列表:list,内置可变序列类型,支持迭代
      print(isinstance([], Iterable)) # True

      # 元组:tuple,内置不可变序列类型,支持迭代
      print(isinstance((), Iterable)) # True

      # 集合:set,内置可变集合类型,支持迭代
      print(isinstance(set(), Iterable)) # True

      # 字典:dict,内置映射类型;默认迭代的是“键”
      print(isinstance({}, Iterable)) # True

      # 字符串:str,内置序列类型,按字符迭代
      print(isinstance("100", Iterable)) # True

      # 整数:int,内置数值类型,非可迭代对象
      print(isinstance(100, Iterable)) # False


      for element in [1, 2, 3]:
          print(element)
      for element in (1, 2, 3):
          print(element)
      for key in {"one": 1, "two": 2}:
          print(key)
      for char in "123":
          print(char)

      for line in open("test.txt"):
          print(line, end="")
    • 内置可迭代类型总览

      分类 代表类型 说明
      容器类 list、tuple、str、set、dict、frozenset 常见内置容器,均可在 for 中遍历;dict 默认迭代键
      序列与字节 range、bytes、bytearray、memoryview 序列或字节视图,支持索引/切片(memoryview 为内存视图)
      文件对象 io.TextIOBase/io.BufferedIOBase(文本/二进制文件) 文件对象本身可逐行/逐块迭代
      迭代器与生成器 iterator、generator、generator expression 迭代器遵循迭代协议;生成器是特殊的迭代器
      高阶返回迭代器 enumerate、zip、map、filter、reversed 内置函数/类型,返回迭代器用于惰性组合、过滤、映射、反转
      字典视图 dict_keys、dict_values、dict_items 视图对象,惰性反映字典变化,可迭代
      其他常见 collections.deque、queue.Queue、itertools.chain/islice/… 标准库容器与工具,返回迭代器或自身可迭代

2.2 迭代器及其与可迭代对象关系

  1. 可迭代对象通过iter()函数生成迭代器
  2. 迭代器通过next()函数获取下一个元素,耗尽时抛出StopIteration异常
  3. 迭代器本身也是可迭代对象(__iter__()返回自身)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from collections.abc import Iterable, Iterator

# 验证可迭代对象
lst = [1, 2, 3]
print("列表是否可迭代:", isinstance(lst, Iterable)) # 输出:True
print("列表是否迭代器:", isinstance(lst, Iterator)) # 输出:False

# 生成迭代器
lst_iter = iter(lst)
print("迭代器是否可迭代:", isinstance(lst_iter, Iterable)) # 输出:True
print("迭代器是否迭代器:", isinstance(lst_iter, Iterator)) # 输出:True

# 遍历迭代器
print(next(lst_iter)) # 输出:1
print(next(lst_iter)) # 输出:2
print(next(lst_iter)) # 输出:3
# print(next(lst_iter)) # 抛出 StopIteration 异常

print("===================================================")
# 创建迭代器对象后使用for循环遍历
it = iter(lst)
for i in it:
print(i)

2.3 自定义迭代器(实战案例)

通过实现__iter__()__next__()方法,创建自定义迭代器(如斐波那契数列生成器):

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
class FibIterator:  #0 1,1,2,3,5....
"""自定义斐波那契数列迭代器"""
def __init__(self, max_count):
self.max_count = max_count # 最大生成个数
self.count = 0 # 已生成个数
self.a, self.b = 0, 1 # 斐波那契初始值

def __iter__(self):
return self # 迭代器返回自身

def __next__(self):
if self.count < self.max_count:
result = self.b
self.a, self.b = self.b, self.a + self.b
self.count += 1
return result
else:
raise StopIteration # 耗尽时抛出异常

# 使用自定义迭代器
fib = FibIterator(5)
for num in fib:
print(num) # 输出:1 1 2 3 5

# 再次遍历(迭代器耗尽后需重新创建)
fib2 = FibIterator(3)
print(list(fib2)) # 输出:[1, 1, 2]

练习,定义反向列表遍历器,并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 = self.index - 1
return self.data[self.index]

rev = Reverse([2, 3, 5, 7, 11, 13, 17, 19])
iter(rev)
for char in rev:
print(char)

2.4 迭代器的优势与应用场景

  1. 内存高效:无需一次性加载所有数据,按需生成(如处理 100 万条数据时仅占用少量内存)
  2. 支持无限序列:如无限斐波那契数列(通过迭代器可一直生成,不会内存溢出)
  3. 统一遍历接口:无论何种可迭代对象,都可通过for循环统一遍历

3 生成器

生成器是迭代器的简化实现,通过yield关键字实现,语法更简洁,是 Python 中处理大数据量的首选工具。

3.1 生成器的核心特性

  1. 语法简洁:无需手动实现__iter__()__next__()方法
  2. 惰性计算:遇到yield暂停,再次调用时从暂停处继续执行
  3. 状态保留:自动保留函数执行状态(如变量值、执行位置)

3.2 生成器的两种创建方式

1. 生成器表达式(简洁版)

语法:(表达式 for 变量 in 可迭代对象 if 条件)(类似列表推导式,将[]改为()

1
2
3
4
5
6
7
# 生成1-10的平方(生成器表达式)
square_gen = (x*x for x in range(1, 11))
print(type(square_gen)) # 输出:<class 'generator'>

# 遍历生成器
for num in square_gen:
print(num, end=" ") # 输出:1 4 9 16 25 36 49 64 81 100

2. 生成器函数

通过def定义,包含yield关键字的函数即为生成器函数,调用后返回生成器对象:

1
2
3
4
5
6
7
8
9
10
11
12
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))  # 2
print(next(f))  # 3
print(next(f))  # 5

如果我们要获取生成器中 return 的值,我们需要捕获 StopIteration异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
def fibo(n):  # 斐波那契数列
    a, b, counter = 0, 1, 0
    while counter < n:
        yield b
        a, b, counter = b, a + b, counter + 1
    return "done"

f = fibo(10)
try:
    while True:
        print(next(f))
except StopIteration as result:
    print("StopIteration", result)  # StopIteration done

3.3 生成器的高级用法

1. send()方法:向生成器发送数据

send()可在唤醒生成器的同时传递数据,实现双向通信:send()发送的数据作为yield表达式的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
def echo_generator():
"""回声生成器:接收数据并返回"""
while True:
data = yield # 接收send传递的数据
if data == "exit":
break
yield f"收到:{data}"

gen = echo_generator()
next(gen) # 启动生成器(执行到第一个yield)或者使用gen.send(None)
print(gen.send("Hello")) # 输出:收到:Hello
print(gen.send("Python")) # 注意这里会再次走到data=yield位置停止,等待下次赋值,而非返回
# gen.send("exit") # 终止生成器

2. close()方法:手动终止生成器

1
2
3
4
gen = (x for x in range(10))
print(next(gen)) # 输出:0
gen.close() # 手动关闭生成器
# print(next(gen)) # 抛出 StopIteration 异常

3.4 生成器实战:处理大数据文件

场景:读取 10GB 日志文件,统计包含 “error” 的行数(无需加载整个文件到内存)

1
2
3
4
5
6
7
8
9
10
11
12
13
def error_log_counter(file_path):
"""统计日志中包含error的行数(生成器版)"""
count = 0
with open(file_path, "r", encoding="utf-8") as f:
for line in f: # 文件对象本身是可迭代对象,逐行读取
print(line.strip())
if "error" in line.lower():
count += 1
yield count # 实时返回统计结果

# 使用生成器(实时查看统计进度)
for count in error_log_counter("../log/large_log.txt"):
print(f"已找到 {count} 条错误日志")

4 命名空间与作用域

命名空间和作用域决定了变量的访问规则,理解它们能避免变量命名冲突和访问异常。

4.1 命名空间(NameSpace)

命名空间是变量名到对象的映射字典,用于隔离不同上下文的变量,避免命名冲突。

三种命名空间

命名空间类型 创建时机 生命周期 访问方式
内置命名空间 Python 解释器启动时 解释器退出时销毁 全局可访问(如print()len()
全局命名空间 模块加载时 模块卸载时销毁 模块内全局访问
局部命名空间 函数调用时 函数执行结束后销毁 仅函数内部访问

命名空间查找顺序

当访问变量时,Python 按以下顺序查找:局部命名空间 → 外层嵌套命名空间 → 全局命名空间 → 内置命名空间,找不到则抛出NameError

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
# 全局作用域
x = "global"

def outer():
x = "enclosing" # 外层嵌套作用域(Enclosing)

def inner():
x = "local" # 局部作用域(Local)
print("inner:", x) # 找到局部 x -> local

def shadow_builtin():
# 局部遮蔽内置:在本作用域内,len 不再是内置函数
len = "I'm not len!"
print("shadow_builtin len:", len) # 找到局部 len -> I'm not len!
# 如需使用内置 len,可用 globals()/builtins.len
print("builtins.len:", __builtins__.len([1, 2, 3]))

def use_builtin():
# 未遮蔽时直接使用内置
print("use_builtin len:", len([1, 2, 3]))

inner()
shadow_builtin()
use_builtin()
print("outer:", x) # 找到外层 x -> enclosing

outer()

# 全局未定义时访问会触发 NameError
# print(undefined_name) # NameError: name 'undefined_name' is not defined

4.2 作用域(Scope)

作用域是命名空间的可访问范围,分为四种:

  1. L(Local):局部作用域(函数内部)
  2. E(Enclosing):外层嵌套作用域(如闭包外层函数)
  3. G(Global):全局作用域(模块内部)
  4. B(Built-in):内置作用域(Python 内置)

1765883430839

作用域修饰符

  1. global:声明变量为全局变量,可在局部修改全局变量

    1
    2
    3
    4
    5
    6
    7
    8
    count = 0  # 全局变量

    def increment():
    global count # 声明使用全局变量
    count += 1

    increment()
    print(count) # 输出:1
  2. nonlocal:声明变量为外层嵌套作用域变量(适用于闭包)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def outer():
    x = 10 # 外层嵌套变量
    def inner():
    nonlocal x # 声明使用外层变量
    x += 5
    inner()
    return x

    print(outer()) # 输出:15

5 闭包

闭包是 Python 高级特性之一,允许函数嵌套中内层函数访问外层函数的变量,即使外层函数已执行完毕。核心价值是数据封装和状态保留。

5.1 闭包的构成条件

  1. 外层函数内定义内层函数
  2. 内层函数引用外层函数的变量(非全局变量)
  3. 外层函数返回内层函数对象

闭包理解案例:

1
2
3
4
5
6
7
8
9
10
# 构建闭包
def linear(a, b):
def inner(x):
return a * x + b

return inner

y1 = linear(1, 2)
print(y1)
print(y1(5))

5.2 闭包实战案例

案例 1:计数器(状态保留)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def counter():
count = 0 # 外层函数变量(被闭包保留)
def increment():
nonlocal count # 声明使用外层变量
count += 1
return count
return increment

# 创建闭包实例
c1 = counter()
print(c1()) # 输出:1
print(c1()) # 输出:2
print(c1()) # 输出:3

# 多个闭包实例相互独立
c2 = counter()
print(c2()) # 输出:1(不影响c1的状态)

案例 2:配置型函数(数据封装)

1
2
3
4
5
6
7
8
9
10
11
12
def make_multiplier(factor):
"""创建乘法器函数(闭包封装因子)"""
def multiply(num):
return num * factor
return multiply

# 创建不同因子的乘法器
double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5)) # 输出:10
print(triple(5)) # 输出:15

5.3 闭包的注意事项

  1. 闭包会保留外层变量的引用,可能导致内存占用(避免保留大对象)

  2. 外层变量若为可变类型(列表、字典),无需nonlocal即可修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def outer():
    lst = []
    def inner(item):
    lst.append(item) # 可变类型无需nonlocal
    return lst
    return inner

    add_item = outer()
    print(add_item(1)) # 输出:[1]
    print(add_item(2)) # 输出:[1, 2]

6 装饰器

装饰器是 Python 中动态增强函数 / 类功能的高级语法,基于闭包实现,核心优势是不修改原始代码即可扩展功能,符合 “开放 - 封闭” 原则。

思考:为什么装饰器基于闭包实现?

6.1 装饰器的核心语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 装饰器函数(接收函数为参数,返回新函数)
def decorator(func):
def wrapper(*args, **kwargs):
# 增强功能(前处理)
result = func(*args, **kwargs) # 调用原始函数
# 增强功能(后处理)
return result
return wrapper

# 使用装饰器(@语法糖)
@decorator
def target_func():
pass

# 当我们使用 @decorator 前缀在 target_func 定义前,Python会自动将 target_func 作为参数传递给 decorator,然后将返回的 inner 函数替换掉原来的 target_func

*args 参数
作用:接收任意数量的位置参数(positional arguments)
特点:将传入的位置参数收集为一个元组(tuple)
使用场景:当不确定函数会接收多少个位置参数时使用
**kwargs 参数
作用:接收任意数量的关键字参数(keyword arguments)
特点:将传入的关键字参数收集为一个字典(dictionary)
使用场景:当不确定函数会接收多少个关键字参数时使用

语法糖:

语法糖是指编程语言中为了提高代码可读性和编写便利性而提供的简化语法,它并不增加语言的功能,只是让某些操作更容易表达。

6.2 常用装饰器实战

1. 计时装饰器(统计函数执行时间)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time

def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行时间:{end_time - start_time:.4f}秒")
return result
return wrapper

@timer_decorator
def calculate_sum(n):
return sum(range(n))

print(calculate_sum(1000000)) # 输出:499999500000 → calculate_sum 执行时间:0.0234秒

2. 缓存装饰器(缓存函数返回结果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def cache_decorator(func):
cache = {} # 闭包保留缓存字典
def wrapper(*args):
if args in cache:
print(f"从缓存获取结果")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper

@cache_decorator
def fib(n):
if n <= 2:
return 1
return fib(n-1) + fib(n-2)

print(fib(10)) # 首次计算,输出:55
print(fib(10)) # 从缓存获取,输出:从缓存获取结果 → 55

3. 带参数的装饰器

需要额外一层函数接收参数,实现装饰器的灵活配置:

案例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from math import sqrt

# 求根号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

案例2:参数直接指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def log_decorator(prefix="INFO"):
"""带参数的日志装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{prefix}] 调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator

# 使用带参数的装饰器
@log_decorator(prefix="DEBUG")
def subtract(a, b):
return a - b

print(subtract(5, 3)) # 输出:[DEBUG] 调用函数:subtract → 2

6.3 类装饰器

通过类的__call__()方法实现装饰器,支持更复杂的状态管理:

  • 不带参数类装饰器

不带参数时,类的逻辑直接作为装饰器使用,init函数接收的是函数调用时传入的参数

两层结构:类初始化接收函数 → call 接收函数调用参数并执行得到结果,即call方法就是包装方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from math import sqrt

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
  • 带参数类装饰器

带参时,先init初始化创建装饰器实例,然后该实例的逻辑装饰函数

init方法的参数为装饰器参数

call方法参数为被装饰方法

call方法的内部闭包包装方法,参数为函数调用时的参数

需要三层结构:类初始化,创建对象 → call 接收函数 → 返回包装函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RetryDecorator:
def __init__(self, max_retry=2):
self.max_retry = max_retry

def __call__(self, func):
def wrapper(*args, **kwargs):
for i in range(self.max_retry):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第{i+1}次执行失败:{e}")
raise Exception(f"{self.max_retry}次执行均失败")
return wrapper

# 使用类装饰器(重试5次)
@RetryDecorator(max_retry=5)
def risky_operation():
import random
if random.random() < 0.7:
raise ValueError("随机失败")
return "执行成功"

print(risky_operation()) # 失败时重试,成功则返回结果

6.4 装饰器其他

  1. 保留原始函数元信息:使用functools.wraps避免装饰器掩盖原始函数信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from functools import wraps

    def decorator(func):
    @wraps(func) # 保留原始函数元信息
    def wrapper(*args, **kwargs):
    return func(*args, **kwargs)
    return wrapper

    @decorator
    def test():
    """测试函数"""
    pass

    print(test.__name__) # 输出:test(未用wraps则输出wrapper)
    print(test.__doc__) # 输出:测试函数(未用wraps则输出None)
  2. 多层装饰器:装饰器按从上到下的顺序执行

    1
    2
    3
    4
    5
    @decorator1
    @decorator2
    def func():
    pass
    # 等价于:func = decorator1(decorator2(func))