md5 = hashlib.md5()

    # 用二进制方式读取大文件也不会卡
    with open(file_path, 'rb') as f:
        # 分次读取,防止内存爆炸
        for chunk in iter(lambda: f.read(4096), b''):
            md5.update(chunk)

一、先讲:iter () 有两种语法形式

形式 1:普通用法

iter(可迭代对象)

比如:

iter([1,2,3])

形式 2:带两个参数的特殊语法

iter(函数, 哨兵值)

语法规则:

  1. 第一个参数:必须是一个无参函数,每次调用返回一个值
  2. 第二个参数:叫哨兵(sentinel),终止标记
  3. 工作逻辑:
    • 循环不停调用这个无参函数
    • 只要返回值 ≠ 哨兵值,就产出这个值
    • 一旦返回值 == 哨兵值,立刻终止迭代,结束循环

二、第二点:lambda 可以不带任何参数

平时习惯写:

lambda x: func(x)

这是带一个形参的 lambda。

但 lambda 完全可以空括号、无参数:

lambda: f.read(4096)

关键点:

  • lambda 后面可以不写任何变量
  • 冒号后面直接写表达式就行
  • 作用就是:造一个不用传参、调用就返回结果的临时函数

放在 iter 里刚好适配:因为 iter(函数, 哨兵) 要求第一个参数就是无参函数。

三、第三点:b'' 是什么意思

  1. Python 里 带 b 前缀 = 字节串(bytes)
  2. '' 是空字符串,b'' 就是空字节串
  3. 文件二进制读取时:
    • f.read(4096) 读到有内容:返回 b'xxxx'
    • 文件读到末尾,没东西可读了:固定返回 b''

所以:b'' 就是 文件读完的终止信号。

四、把整句拼起来逐行拆解

iter(lambda: f.read(4096), b'')

拆解翻译:

  1. lambda: f.read(4096)造一个无参函数:一调用,就从文件读 4096 字节

  2. b''设定哨兵:如果读到空字节串,就停止

  3. iter(无参函数, 哨兵)反复调用读文件函数,每次返回一块数据;一旦读到 b'',马上停。

然后 for chunk in ... 就是一块一块遍历读取的内容。

五、等价写成熟悉的 while 版本,彻底对照

# 优雅简写版
for chunk in iter(lambda: f.read(4096), b''):
    md5.update(chunk)

等价老式写法:

while True:
    chunk = f.read(4096)
    if chunk == b'':  # 碰到哨兵,退出
        break
    md5.update(chunk)

完全一模一样的逻辑,只是用 iter 语法做了精简。

测试一下理解得对不对:

aa = [1,2,3]
for bb in iter(lambda:aa.pop(0), 3):
    print(bb)

输出结果是:

六、总结三个新认知

  1. iter(函数, 哨兵):反复调用无参函数,等于返回哨兵就停;
  2. lambda: 可以无参数,直接写后续表达式,适合临时做无参回调;
  3. b'' 是二进制文件读到末尾的空字节标记,作为循环终止条件。

更多推荐