Python这些坑别踩:默认参数、闭包、浅拷贝,一次讲透
Python里不少麻烦来自两个时间点:名字什么时候绑定,对象什么时候创建。把这两件事看清,很多行为看着不对,也能解释通。 默认参数常出问题。看函数: def add(item, box=[]): box.append(item) return box 这段代码在定义函数时创建了列表。 box 的默认值不是每次调用新建。 第一次调用拿到 [1],第二次拿到 [1, 2]。想每次新列表,用 None 当哨兵: def add(item, box=None): if box is None: box = [] box.append(item) return box 这里检查 is None ,不用 if not box 。空列表也是有效输入。用 not 会把传入的空列表替换掉,调用者拿不到自己传的对象。 闭包也会遇到求值时机。下面三个函数都返回 2: funcs = [lambda: i for i in range(3)] print([f() for f in funcs]) lambda 里没有保存 i 的值,保存的是名字 i。 循环结束后 i 停在 2。要让每个函数记住当前值,把值绑到默认参数上: funcs = [lambda i=i: i for i in range(3)] print([f() for f in funcs]) 默认参数在定义 lambda 时求值,i=i 把当前 i 存进函数对象。这个写法够用。代码里出现这种模式,读的人要能看懂。 生成器把求值推后。读大文件时别一次 readlines。用逐行迭代: def read_lines(path): with open(path, encoding='utf-8') as f: for line in f: yield line.rstrip('\n') 这个函数返回生成器。文件不会一次进内存。for 循环每要一行,函数跑到 yield 停住。适合日志、csv、管道处理。 注意生成器只能走一遍。 走过一次后再 list 得到空列表。需要重复用,重新创建生成器,或者把结果存下来。存之前想清楚数据量。 浅拷贝的坑也跟对象创建有关。下面写法三行指向同一个列表: grid = [[0] * 3] * 3 grid[0][0] = 1 print(grid) 外层列表的三个位置放的是同一个内层列表。 改一个,三行都变。要独立行,用推导式逐行创建: grid = [[0] * 3 for _ in range(3)] grid[0][0] = 1 print(grid) 这里每轮循环新建一个 [0, 0, 0] 。外层列表装三个不同对象。二维数组、矩阵初始化都该这么写。 这些技巧背后是同一件事: Python 不复制对象,只绑定名字。 默认参数在定义时求值。闭包捕获名字。生成器延迟执行。容器乘法复制引用。写代码时多问一句:这个对象在哪创建,谁在引用它。调试能少绕路。 💬 一 起 聊 聊 你平时写 Python 最常碰到哪类问题? 评论里说一个具体场景,我挑一个下次拆开讲。 Python报 · 张姐