Python 可变默认参数 & 闭包延迟绑定的实现

 更新时间:2026年06月23日 08:18:36   作者:Csvn  
本文主要介绍了Python 可变默认参数 & 闭包延迟绑定的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 知识点简介

Python 的「灵活」有时也是双刃剑。下面这两个坑点隔三差五就出现在生产事故和面试题中,而且它们都来自同一个根源:Python 的绑定时机问题

  • 坑 ①:可变默认参数 —— 默认参数在定义时求值,而非调用时
  • 坑 ②:闭包延迟绑定 —— 闭包捕获的是变量引用,而非创建时的值

理解这两个坑 = 理解 Python 作用域 + 绑定机制的关键。

2. 坑 ①:可变默认参数

现象

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item(1))          # [1]
print(add_item(2))          # [1, 2]  👈 预期是 [2]!
print(add_item(3))          # [1, 2, 3]

每次调用 add_item 时,items 指向的都是 同一个列表对象

原因

Python 的默认参数值在函数定义时def 语句执行时)计算并绑定。之后每次调用若不传该参数,用的都是同一个对象。

验证一下:

def test(items=[]):
    print(id(items))  # 每次调用都打印相同的内存地址

test()  # 4395790912
test()  # 4395790912
test()  # 4395790912

正确姿势

# ✅ 用 None 做哨兵,函数内部创建新对象
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item(1))   # [1]
print(add_item(2))   # [2]  ✅ 符合预期

经典变种

# 不只是 list,任何可变对象都一样
def bad(d={}):
    d["count"] = d.get("count", 0) + 1
    return d

print(bad())  # {'count': 1}
print(bad())  # {'count': 2}  ❌
# 甚至连 Datetime 和 lambda 也不例外
import datetime

def log_time(t=datetime.datetime.now()):
    print(t)

log_time()  # 每次打印相同的时刻 ❌

3. 坑 ②:闭包延迟绑定

现象

def create_multipliers():
    return [lambda x: i * x for i in range(5)]

multipliers = create_multipliers()
for m in multipliers:
    print(m(2))  # 输出:8 8 8 8 8  👈 预期:0 2 4 6 8

所有函数都返回 8?因为循环结束时 i = 4,所有闭包引用的都是同一个变量 i最终值

原理拆解

# 等价写法,更容易看穿
funcs = []
for i in range(5):
    funcs.append(lambda x: i * x)

i = 4  # 循环结束后的 i

for f in funcs:
    print(f(2))  # 全部 8

lambda 捕获的是变量 i 本身(引用),而非创建时 i 的值。当闭包真正执行时,才去查找 i 的当前值。

正确姿势

方式 1:默认参数绑定(利用坑 ① 的特性反制)

def create_multipliers():
    return [lambda x, i=i: i * x for i in range(5)]
    #          ^^^^  默认参数在循环每次迭代时求值,i 的值被固定

方式 2:functools.partial

from functools import partial

def multiply(x, i):
    return i * x

def create_multipliers():
    return [partial(multiply, i) for i in range(5)]

方式 3:闭包嵌套(立即执行外层函数)

def create_multipliers():
    def make_multiplier(i):
        return lambda x: i * x
    return [make_multiplier(i) for i in range(5)]

三种方式都正确输出:0 2 4 6 8

4. 组合起来:一个更隐蔽的例子

def create_actions():
    actions = []
    for i in range(3):
        def action(item, cache=[]):  # 默认参数绑定 + 可变对象
            cache.append(item)
            return f"i={i}, cache={cache}"  # i 是延迟绑定的
        actions.append(action)
    return actions

actions = create_actions()
print(actions[0]("a"))  # i=2, cache=['a']  ❌ i 预期 0
print(actions[1]("b"))  # i=2, cache=['a', 'b']  ❌ 双重坑
print(actions[2]("c"))  # i=2, cache=['a', 'b', 'c']

同时踩了两个坑。修正后:

def create_actions():
    actions = []
    for i in range(3):
        def action(item, cache=None):
            if cache is None:
                cache = []
            cache.append(item)
            return f"i={i}, cache={cache}"
        actions.append(action)
    return actions

嗯…这样也只修了可变参数坑,i 还是延迟绑定。需要两个一起修:

def create_actions():
    actions = []
    for i in range(3):
        def action(item, cache=None, i=i):  # 同时解决两个坑
            if cache is None:
                cache = []
            cache.append(item)
            return f"i={i}, cache={cache}"
        actions.append(action)
    return actions

5. 避坑清单

# ❌ 不要这样写
def process(data=[]):           # 可变默认参数
    ...

# ✅ 改成这样
def process(data=None):
    data = data or []           # 或严格判断:if data is None: data = []


# ❌ 不要在循环中直接创建闭包
funcs = [lambda: i for i in range(5)]

# ✅ 绑定当前值
funcs = [lambda i=i: i for i in range(5)]

6. 底层原理速记

概念理解要点
函数定义时def 语句执行时,默认参数对象被创建并绑定到函数对象上
函数调用时未传参 → 复用绑定好的默认对象;传参 → 使用新对象
闭包(closure)内层函数引用了外层函数的变量,形成闭包,变量本身被「捕获」而非值
LEGB 规则闭包执行时按 Local → Enclosing → Global → Built-in 查找变量

简单记忆口诀:默认参数看定义时,闭包变量看执行时

7. 总结

  • 可变默认参数:[] {} set() 不要放参数默认值,用 None + 函数内部初始化
  • 闭包延迟绑定:循环内创建闭包时,用 i=i 默认参数技巧固定当前值
  • 两个坑同时出现时:逐个排查,各行其是地修
  • 这些不是 bug,而是设计选择。理解它们后,你反而能利用这些特性写出更优雅的代码(比如缓存、参数绑定等)

这两个坑是 Python 开发者的「成人礼」。踩过,修过,才算是真正理解了 Python 的对象模型和作用域。

到此这篇关于Python 可变默认参数 & 闭包延迟绑定的实现的文章就介绍到这了,更多相关Python 可变默认参数 & 闭包延迟绑定内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python实现隐马尔科夫模型HMM

    python实现隐马尔科夫模型HMM

    这篇文章主要为大家详细介绍了python实现隐马尔科夫模型HMM,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • 详解Python多线程Selenium跨浏览器测试

    详解Python多线程Selenium跨浏览器测试

    本篇文章主要介绍了Python多线程Selenium跨浏览器测试,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-04-04
  • Django框架视图层URL映射与反向解析实例分析

    Django框架视图层URL映射与反向解析实例分析

    这篇文章主要介绍了Django框架视图层URL映射与反向解析,结合实例形式分析了Django框架普通url映射、命名URL参数映射、分布式URL映射、反向解析等相关操作技巧,需要的朋友可以参考下
    2019-07-07
  • python安装和pycharm环境搭建设置方法

    python安装和pycharm环境搭建设置方法

    这篇文章主要介绍了python安装和pycharm环境搭建和设置方法,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 ,
    2020-05-05
  • 使用pycharm和pylint检查python代码规范操作

    使用pycharm和pylint检查python代码规范操作

    这篇文章主要介绍了使用pycharm和pylint检查python代码规范操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-06-06
  • Python入门教程 超详细1小时学会Python

    Python入门教程 超详细1小时学会Python

    本文适合有经验的程序员尽快进入Python世界.特别地,如果你掌握Java和Javascript,不用1小时你就可以用Python快速流畅地写有用的Python程序.
    2006-09-09
  • python3如何获取子线程中函数返回值

    python3如何获取子线程中函数返回值

    这篇文章主要介绍了python3如何获取子线程中函数返回值问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Python lambda和Python def区别分析

    Python lambda和Python def区别分析

    Python支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做lambda的函数,是从Lisp借用来的,可以用在任何需要函数的地方
    2014-11-11
  • 基于Python手写拼音识别

    基于Python手写拼音识别

    这篇文章主要介绍了基于Python手写拼音识别,因项目组需要使用到拼写识别,考虑到每个字母的复杂度不高,所以使用KNN算法来尝试实现,下面来看看具体实现详情吧,需要的小伙伴也可以参考一下
    2022-01-01
  • Python中操作mysql的pymysql模块详解

    Python中操作mysql的pymysql模块详解

    这篇文章给大家演示了如何安装以及使用Python中操作mysql的pymysql模块,本文介绍的很详细,对大家学习Python具有一定参考借鉴价值,有需要的朋友们一起来看看吧。
    2016-09-09

最新评论