| Day 9 | 函数进阶:*args / **kwargs / lambda / 递归 |

1 *args 与 **kwargs —— 打包与拆包的对称魔法

是什么?为什么学?

*args 把「任意多个位置参数」打包成元组,**kwargs 把「任意多个关键字参数」打包成字典,它们让函数不再被参数个数限制。写工具库、配置类函数、装饰器(Day 20+)时几乎必用。

底层原理

调用 f(1, 2, 3) 时,Python 先把实参收集成「参数列表」再传给函数;def f(*args) 就是告诉 Python「把剩下的所有位置参数收集成一个元组塞给 args」。**kwargs 同理,收集 key=value 形式的实参打包成字典。反过来,调用时在列表前加 *(如 f(*[1,2,3]))、在字典前加 **(如 f(**d))则是拆包——把容器展开成多个独立实参。打包与拆包是一对对称操作。

生活类比

*args超市购物袋:你往袋子里装多少东西都行(位置参数),店员装袋后交给你一个袋子(元组),你回家慢慢清点。**kwargs填表:每一项都是「项目=内容」(如「姓名=小明」),表格自动把所有项目整理成一本册子(字典)。而拆包是反过来——把袋子里的东西一样样摆到柜台上(*),把册子里的条目一条条念出来(**)。

注意事项总结

1 *args 打包任意多个位置参数

def my_sum(*args):
    print("收到参数:", args)      # args 是元组
    return sum(args)

print(my_sum(1, 2, 3))
print(my_sum(4, 5, 6, 7, 8))

运行结果:

收到参数: (1, 2, 3)
6
收到参数: (4, 5, 6, 7, 8)
30

传 2 个、5 个、100 个都行——这是固定参数函数做不到的。

2 拆包——把列表 / 字典「展开」成独立实参

def show(a, b, c):
    print(a, b, c)

nums = [1, 2, 3]
show(*nums)              # 列表加 *:展开成 show(1, 2, 3)

info = {"a": 10, "b": 20, "c": 30}
show(**info)             # 字典加 **:展开成 show(a=10, b=20, c=30)

运行结果:

1 2 3
10 20 30

打包在定义处(def f(*args)),拆包在调用处(f(*lst)),一收一放,正好对称。

3 **普通参数 + *args 混用**:

def greet(title, *names):
    print("尊敬的", title)
    for name in names:
        print("欢迎", name)

greet("嘉宾", "小明", "小红", "小刚")

运行结果:

尊敬的 嘉宾
欢迎 小明
欢迎 小红
欢迎 小刚

易错点

  • 错误写法:def f(*args, x) 把普通参数写在 args 后面 → 问题:报 SyntaxErrorargs 之后只能跟关键字参数 → 正确写法:*args 写在普通参数后面def f(x, *args)
  • 错误写法:调用 f(*nums) 时列表元素个数和形参个数对不上 → 问题:报 TypeError(多了或少了) → 正确写法:拆包前先确认个数匹配。
  • 错误写法:def f(**kwargs, x) → 问题:报 SyntaxError**kwargs 必须是最后一个参数 → 正确写法:**kwargs 永远放参数列表最后。

记忆口诀:一颗星打包元组,两颗星打包字典;定义处收,调用处拆,一收一放对称玩。

2 lambda —— 一句话函数与「函数是一等公民」

是什么?为什么学?

lambda 是用一行表达式写完的匿名函数lambda 参数: 返回值表达式。它适合「逻辑简单、只用一次」的场景,最常见的用法是给 sorted / map / filter 提供排序或处理规则。

底层原理

Python 里函数是一等公民(first-class citizen):函数和数字、字符串一样,可以赋值给变量、放进列表、作为参数传递、作为返回值。lambda 只是「快速创建一个函数对象」的语法糖——lambda x: x * 2def 定义的函数在底层是同一类对象,区别只是 lambda 没有名字、只能写一个表达式、没有独立代码块。

术语小词典:一等公民(first-class citizen)就是「和普通数据平起平坐」:函数也能被存进变量、塞进容器、当参数传,想怎么用就怎么用。

生活类比

lambda 像临时叫的代驾:事情简单(就一句话)、只用这一次,没必要专门请个全职司机(def 定义、起名字、写文档)。工作复杂(多行逻辑、多次复用)时,还是得雇正式的「函数员工」——有名有姓、有独立代码块,可读性更好。

注意事项总结

1 lambda 与 def 的等价写法对比

def double(x):            # 传统写法:def + return
    return x * 2

double2 = lambda x: x * 2   # lambda 写法:一行搞定

print(double(5))          # 10
print(double2(5))         # 10
print(type(double2))      # <class 'function'>,和 def 函数是同一类对象

运行结果:

10
10
<class 'function'>

lambda x: x * 2 里,冒号前是参数,冒号后是返回表达式(自带 return,不用写)。

2 函数是一等公民——函数可以放进列表 / 字典

def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

tools = [add, multiply]            # 函数放进列表(注意:没写括号)
print(tools[0](3, 4))              # 取出 add 再调用
ops = {"+": add, "*": multiply}    # 函数放进字典
print(ops["+"](10, 5))

运行结果:

7
15

tools = [add, multiply] 里写的是函数名字(没有括号)——存的是函数对象本身。函数和普通数据一样能被存放、传递,这是理解 map / filter / sorted 的基础。

3 lambda 实际最常配合排序用

students = ["Tom", "alice", "Bob"]
print(sorted(students))                        # 默认按字母排:['Bob', 'Tom', 'alice']
print(sorted(students, key=lambda s: s.lower()))  # 忽略大小写排:['alice', 'Bob', 'Tom']

运行结果:

['Bob', 'Tom', 'alice']
['alice', 'Bob', 'Tom']

易错点

  • 错误写法:lambda x: x * 2 后面加赋值或 if 语句 → 问题:报 SyntaxError,lambda 只能写一个表达式 → 正确写法:复杂逻辑改用 def
  • 错误写法:tools = [add()] 误加括号 → 问题:存进去的是调用结果而不是函数本身 → 正确写法:存函数不加括号:[add]
  • 错误写法:sorted(words, lambda w: w.lower()) 忘了 key= → 问题:lambda 被当成容器,报 TypeError → 正确写法:必须写成 key=lambda ...

记忆口诀:lambda 一行成函数,冒号前面是参数,冒号后面是结果;复杂逻辑用 def,可读优先记心间。

3 map / filter —— 批量处理数据的迭代器

是什么?为什么学?

map(函数, 容器) 把函数逐个应用到容器的每个元素(转换);filter(函数, 容器) 把「函数返回 True」的元素保留下来(筛选)。它们是「对一堆数据批量操作」的标准工具,代码比 for 循环更短、意图更明确。

底层原理

map / filter 返回的不是列表,而是迭代器(iterator)——一种「按需产出、边取边算」的对象。它们不会一次性算完所有结果,而是每次 next() 时才取一个元素处理一个,所以处理 100 万个元素也只占固定内存。正因如此,print(map(...)) 只能看到 <map object ...>,必须用 list() 把它「拉完」转成列表才能看到全部结果。

术语小词典:迭代器(iterator)就是「按需产出数据的对象」——像自动售货机,按一下(next)出一样,不按不产,所以省内存。

生活类比

map 像流水线传送带:每个零件(元素)经过工位(函数)加工一下再出来——零件数量不变,只是个个都变了样。filter 像安检门:每个乘客(元素)过安检(函数判断),合格(True)放行、不合格(False)拦下——人数变少,但留下来的都达标。

注意事项总结

1 map 批量转换——把每个数字翻倍

nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
print(doubled)

运行结果:

[2, 4, 6, 8, 10]

元素个数不变(5 进 5 出),但每个值都变了——这就是「转换」。

2 filter 批量筛选——只保留偶数

nums = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)

运行结果:

[2, 4, 6]

元素个数变少(6 进 3 出)——这就是「筛选」。

3 map + filter 搭配使用(先筛后平方)

nums = [1, 2, 3, 4, 5, 6]
result = list(map(lambda x: x * x, filter(lambda x: x % 2 == 0, nums)))
print(result)

运行结果:

[4, 16, 36]

易错点

  • 错误写法:直接 print(map(...)) → 问题:打印出 <map object at 0x...>,看不到数据 → 正确写法:外面套 list()list(map(...))
  • 错误写法:filter 的 lambda 里写 x % 2(忘了 == 0) → 问题:返回的是 0/1 而非 True/False,偶数被错误筛掉 → 正确写法:写完整判断:x % 2 == 0
  • 错误写法:对字典用 map 没注意迭代的是键 → 问题:map 默认遍历的是字典的 → 正确写法:想处理值:map(func, d.values())

记忆口诀:map 逐个换新颜,filter 合格才放行;迭代器按需算,套上 list 才见全。

4 sorted 的 key 参数 —— 自定义排序的灵魂

是什么?为什么学?

sorted(容器, key=函数) 中的 key 参数决定「按什么规则排序」:默认按元素本身的大小排,而 key 函数可以对每个元素「加工出一个比较依据」。按绝对值排、忽略大小写排、按字典某键排——全部靠 key 实现。

底层原理

sorted 的排序分两步:先对每个元素调用一次 key 函数得到「键值」,然后按键值大小对原元素排序(内置 TimSort 算法),最后返回新列表(原列表不被修改)。注意 key 只负责「算出比较依据」,真正排序的还是原元素——所以 sorted(students, key=lambda s: s["score"]) 返回的仍是完整字典,只是顺序变了。

生活类比

学校按成绩排名:默认排序像直接按「姓名的拼音字母」排;key 排序像按「身高」排——人还是那些人(元素不变),只是排队依据换成了别的东西(key 算出的键值)。想按哪个维度排,就告诉教练(key 函数)怎么量。

注意事项总结

1 key 按绝对值排序

nums = [-5, 3, -1, 2]
print(sorted(nums))                                    # 默认按数值
print(sorted(nums, key=lambda x: abs(x)))              # 按绝对值

运行结果:

[-5, -1, 2, 3]
[-1, 2, 3, -5]

默认排序按数值大小:-5 < -1 < 2 < 3;按绝对值排时,每个数先取绝对值再比较:1、2、3、5,对应原数是 -1、2、3、-5。key 只改变「比较依据」,结果里还是原来的数。

2 key 对字典列表按成绩排序(最常用场景)

students = [
    {"name": "小明", "score": 92},
    {"name": "小红", "score": 88},
    {"name": "小刚", "score": 95},
]
top = sorted(students, key=lambda s: s["score"], reverse=True)
for s in top:
    print(s["name"], s["score"])

运行结果:

小刚 95
小明 92
小红 88

key=lambda s: s["score"] 让排序依据是每个字典的 score 值;reverse=True 表示从高到低(默认从小到大)。字典列表排序是真实项目里的高频操作,这一行是标准答案。

易错点

  • 错误写法:sorted(students, key=s["score"]) 少了 lambda → 问题:直接报 NameError,key 要的是一个函数 → 正确写法:key=lambda s: s["score"]
  • 错误写法:忘了 reverse=True 想要从大到小 → 问题:得到的是从小到大 → 正确写法:降序加 reverse=True
  • 错误写法:以为 sorted 会修改原列表 → 问题:原列表不变,返回新列表 → 正确写法:想原地排序用 列表.sort()

记忆口诀:sorted 要排序,key 来定依据;函数算键值,元素不变序;降序加 reverse,新表不伤原。

5 递归 —— 自己调用自己的「栈」游戏

是什么?为什么学?

递归(recursion)是函数在函数体里调用自己,适合解决「大问题能拆成同构的小问题」的场景:阶乘、倒计时、目录遍历、树形结构(Day 20+)。理解递归,等于打开了「分而治之」的思维大门。

底层原理

每次函数调用,Python 都会在调用栈(call stack)上压入一个栈帧(记录参数、局部变量、返回位置)。递归调用自己时,栈帧一层层往上叠:factorial(5)factorial(4) → …… 直到遇到基准条件(base case)不再调用,然后栈帧一层层「弹栈」返回结果。所以递归必须满足两条:①有出口(基准条件)②每次调用让问题变小——参数朝出口方向逼近。缺了出口,栈帧无限叠加,最终触发 RecursionError: maximum recursion depth exceeded(默认约 1000 层)。

生活类比

递归像俄罗斯套娃:打开最外面的大娃,里面是一个小一号的娃,再打开又是更小的……直到最小的那个(基准条件)——里面什么都没有了。整个过程「一层层打开(递),再一层层合上(归)」。如果厂家忘了做最小的娃(没有出口),你就永远拆不完。

注意事项总结

1 递归算阶乘——最经典的递归

def factorial(n):
    if n <= 1:                  # 出口:n 到 1 就停
        return 1
    return n * factorial(n - 1)  # 递进:问题变小(n-1)

print(factorial(5))

运行结果:

120

执行过程:factorial(5)5 * factorial(4)factorial(4) 又要算 4 * factorial(3)……一直压栈到 factorial(1) 返回 1,再一层层弹栈相乘:5×4×3×2×1 = 120。出口(n <= 1)保证递归能停。

2 递归求斐波那契数列(前 10 项)

def fib(n):
    if n <= 1:                  # 出口:第 0、1 项直接返回
        return n
    return fib(n - 1) + fib(n - 2)   # 递进:拆成两个更小的子问题

result = []
for i in range(10):
    result.append(fib(i))
print(" ".join(str(x) for x in result))

运行结果:

0 1 1 2 3 5 8 13 21 34

斐波那契数列第 n 项 = 前两项之和(第 0 项是 0,第 1 项是 1)。fib(5) = fib(4) + fib(3)……这种朴素写法会重复计算很多次(性能差),以后学了「记忆化」再优化。

3 递归必须有两个部分——出口 + 递进(倒计时)

def count_down(n):
    if n <= 0:               # 1. 出口(基准条件):n 到 0 就停
        print("发射!")
        return
    print(n)                 # 2. 递进:打印当前值
    count_down(n - 1)        # 然后调用自己,参数变小

count_down(3)

运行结果:

3
2
1
发射!

4 没有出口的递归(错误示范,千万不要运行)

# def forever():
#     return forever() + 1
#
# print(forever())
# 上面这段没有「出口」,函数无限调用自己,栈帧无限叠加,
# 运行会报 RecursionError: maximum recursion depth exceeded。
# 不小心运行了无限递归,在终端按 Ctrl + C 强制终止即可。

易错点

  • 错误写法:递归函数没有基准条件 → 问题:无限调用自己,报 RecursionError → 正确写法:先写出口:if 条件: return 直接结果
  • 错误写法:基准条件写了但永远达不到(参数没变小) → 问题:同样 RecursionError → 正确写法:检查每次递归调用时参数是否朝出口逼近。
  • 错误写法:用递归解决简单循环问题 → 问题:性能差、代码绕 → 正确写法:能用循环就用循环(更简单、更快);树形结构等场景才用递归。

记忆口诀:递归两件套:出口保停,变小逼近;去程递、回程归,栈满报错 RecursionError。

6 动手实践:排序与过滤工具

把今天学的知识组合起来,做一个工具库:能对数字列表排序(支持自定义规则)、筛选出符合条件的数、批量转换数据。

# 排序与过滤工具
import math


def sort_by_abs(nums):
    """按绝对值从小到大排序"""
    return sorted(nums, key=lambda x: abs(x))


def sort_by_len(words):
    """按字符串长度排序"""
    return sorted(words, key=lambda x: len(x))


def filter_by_condition(nums, func):
    """按传入的函数筛选(func 返回 True 的保留)"""
    return list(filter(func, nums))


def double_all(nums):
    """把所有数字翻倍"""
    return list(map(lambda x: x * 2, nums))


def sum_all(*args):
    """任意个数求和"""
    return sum(args)


# 调用测试
print(sort_by_abs([-5, 3, -1, 2]))           # [-1, 2, 3, -5]
print(sort_by_len(["abc", "a", "abcd", "ab"]))  # ['a', 'ab', 'abc', 'abcd']
print(filter_by_condition(range(1, 11), lambda x: x % 3 == 0))  # [3, 6, 9]
print(double_all([1, 2, 3]))                 # [2, 4, 6]
print(sum_all(1, 2, 3, 4))                   # 10

完成标准:每个函数输出与注释一致。进阶:用 lambda 给一组学生成绩字典按分数排序,如 students = [{"name": "小明", "score": 92}, {"name": "小红", "score": 88}],按 score 从高到低排。

今日总结

*args 打包位置参数成元组,**kwargs 打包关键字参数成字典;调用时 * / ** 还可以拆包展开 函数是一等公民:可以赋值、放进容器、作为参数传递——这是 map / filter / sorted 能工作的基础 lambda 适合一行简单函数:lambda 参数: 表达式;逻辑复杂时用 def 更清晰 map 逐个转换、filter 按条件筛选,返回迭代器,记得套 list() 查看结果 sortedkey 参数决定排序依据,reverse=True 降序;原列表不被修改 递归必须有出口(基准条件),且每次调用要让问题变小;递归有「去程」和「回程」两个阶段 能用循环优先用循环,递归留给树形遍历等天然递归的场景 完成排序与过滤工具,并尝试用 lambda 给字典列表按成绩排序

报错原因修复
TypeError: 'map' object is not subscriptable忘了把 map/filter 结果转成列表外面套 list()list(map(...))
RecursionError: maximum recursion depth exceeded递归没有出口,无限调用自己检查递归函数是否缺少基准条件,或条件永远不满足
TypeError: <lambda>() missing 1 required positional argumentlambda 参数个数和调用处不匹配检查 lambda 写了几个参数、传了几个参数
SyntaxError: invalid syntaxlambda 后面写了多行语句或用了赋值lambda 只能写一个表达式,复杂逻辑改用 def
TypeError: 'int' object is not iterable把单个数字传给了期待容器的函数检查实参是列表/元组,而不是单个值
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇