🐍 函数进阶 — 从会写到写好
🕐 预计用时:2-3 小时 | 🎯 目标:掌握 *args/**kwargs、作用域、lambda 和闭包基础
📖 今日目录
1. *args:接收任意数量的位置参数
有时候你不知道调用者会传几个参数——用 *args 来"打包"接收。
# 普通函数:参数数量固定
def add(a, b):
return a + b
print(add(1, 2)) # 3
# print(add(1, 2, 3)) # ❌ TypeError
# *args:参数数量不固定
def add_all(*args):
print(f"args = {args}")
print(f"type = {type(args)}") # tuple!
return sum(args)
print(add_all(1, 2)) # args = (1, 2) → 3
print(add_all(1, 2, 3)) # args = (1, 2, 3) → 6
print(add_all(1, 2, 3, 4, 5)) # args = (1, 2, 3, 4, 5) → 15
💡 *args 本质上是一个元组!
Python 把多余的位置参数打包成元组,赋值给 args 这个变量名。
变量名不一定要叫 args,*numbers、*items 都行,* 才是关键。
📦 常见用法
# 日志函数:接收任意消息
def log(*messages):
for msg in messages:
print(f"[LOG] {msg}")
log("服务器启动", "端口8080", "等待连接")
# [LOG] 服务器启动
# [LOG] 端口8080
# [LOG] 等待连接
# 求平均值
def average(*numbers):
return sum(numbers) / len(numbers)
print(average(80, 90, 85)) # 85.0
print(average(100, 95, 88, 92, 78)) # 90.6
# 打印函数
def print_info(name, *hobbies):
print(f"{name} 的爱好:")
for h in hobbies:
print(f" - {h}")
print_info("张三", "编程", "游泳", "看书")
# 张三 的爱好:
# - 编程
# - 游泳
# - 看书
🔓 解包:把列表/元组展开为参数
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
print(add(*nums)) # 6(等价于 add(1, 2, 3))
# * 解包列表/元组为位置参数
def show(*args):
print(args)
show(*[1, 2, 3]) # (1, 2, 3)
show(*(4, 5, 6)) # (4, 5, 6)
show(*"hello") # ('h', 'e', 'l', 'l', 'o')
2. **kwargs:接收任意数量的关键字参数
**kwargs 接收"多余的关键字参数",打包成字典。
def print_profile(**kwargs):
print(f"kwargs = {kwargs}")
print(f"type = {type(kwargs)}") # dict!
for key, value in kwargs.items():
print(f" {key}: {value}")
print_profile(name="张三", age=25, city="北京")
# kwargs = {'name': '张三', 'age': 25, 'city': '北京'}
# name: 张三
# age: 25
# city: 北京
📦 常见用法
# 配置函数:接收任意配置项
def create_server(**config):
host = config.get("host", "127.0.0.1")
port = config.get("port", 8080)
debug = config.get("debug", False)
print(f"启动服务器: {host}:{port} (debug={debug})")
create_server() # 默认配置
create_server(port=3000, debug=True) # 自定义配置
# 合并字典
def merge(*dicts):
result = {}
for d in dicts:
result.update(d)
return result
defaults = {"color": "red", "size": "medium"}
custom = {"size": "large", "weight": "bold"}
print(merge(defaults, custom))
# {'color': 'red', 'size': 'large', 'weight': 'bold'}
🔓 解包:把字典展开为关键字参数
def greet(name, age, city):
print(f"{name}, {age}岁, {city}")
info = {"name": "张三", "age": 25, "city": "北京"}
greet(**info) # 等价于 greet(name="张三", age=25, city="北京")
# ** 解包字典为关键字参数
def show(**kwargs):
print(kwargs)
show(**{"a": 1, "b": 2}) # {'a': 1, 'b': 2}
show(**dict(x=10, y=20)) # {'x': 10, 'y': 20}
3. 参数组合的完整规则
Python 参数有严格的顺序,记住这个公式:
def func(pos_only, /, normal, *args, kw_only, **kwargs):
pass
# ↑ ↑ ↑ ↑ ↑
# 位置专属 普通参数 可变位置 关键字专属 可变关键字
📋 参数类型速查表
| | |
|---|
| a, b, / | |
| a, b | |
| *args | |
| *, a, b | |
| **kwargs | |
# 完整示例
def full_example(a, b, /, c, *args, d, e=100, **kwargs):
print(f"a={a}, b={b}, c={c}")
print(f"args={args}")
print(f"d={d}, e={e}")
print(f"kwargs={kwargs}")
full_example(1, 2, 3, 4, 5, d=6, e=7, x=8, y=9)
# a=1, b=2, c=3
# args=(4, 5)
# d=6, e=7
# kwargs={'x': 8, 'y': 9}
💡 日常开发中最常用的组合:
1. def f(a, b, *args) — 固定参数 + 可变位置参数
2. def f(a, b, **kwargs) — 固定参数 + 可变关键字参数
3. def f(*args, **kwargs) — 万能签名(接收一切)
4. 变量作用域(LEGB 规则)
Python 找变量的顺序:L → E → G → B,就像找钥匙一样——先翻自己的口袋,再找邻居的。
| | |
|---|
| L | | |
| E | | |
| G | | |
| B | | Python 内置的(len, print, range...) |
x = "全局变量" # G: Global
def outer():
x = "外层变量" # E: Enclosing
def inner():
x = "内部变量" # L: Local
print(x)
inner() # 内部变量
outer()
print(x) # 全局变量(不受函数影响)
🔍 LEGB 查找过程
# L → E → G → B 的查找
name = "全局张三"
def test():
# name = "局部张三" # 如果取消注释,优先用局部的
print(name) # 找到全局的 → 全局张三
test()
# 内置函数的 B 层
# len 就是 B 层的变量
print(len([1, 2, 3])) # 3(找到内置的 len 函数)
⚠️ 作用域陷阱:
x = 10
def test():
print(x) # ❌ UnboundLocalError!
x = 20
# 原因:函数内有 x = 20 的赋值,Python 认为 x 是局部变量
# 但 print(x) 在赋值之前,所以报错
解决方法:用 global 声明,或者不要在函数内同名赋值。
5. global 和 nonlocal
🌐 global:在函数内修改全局变量
counter = 0 # 全局变量
def increment():
global counter # 声明:我要用全局的 counter
counter += 1
print(f"计数器: {counter}")
increment() # 计数器: 1
increment() # 计数器: 2
increment() # 计数器: 3
print(counter) # 3(全局变量被修改了)
# 不用 global 的情况
name = "张三"
def change_name():
name = "李四" # 这是创建了一个局部变量,不是修改全局的
print(f"函数内: {name}")
change_name() # 函数内: 李四
print(f"函数外: {name}") # 函数外: 张三 ← 没变!
🔗 nonlocal:在内层函数修改外层函数的变量
def outer():
count = 0 # 外层函数的局部变量
def inner():
nonlocal count # 声明:我要用外层的 count
count += 1
print(f"inner: {count}")
inner() # inner: 1
inner() # inner: 2
inner() # inner: 3
print(f"outer: {count}") # outer: 3
outer()
💡 能不用 global 就不用!
大量使用 global 会让代码难以理解和调试。
更好的做法:用参数传递、用 return 返回结果。
6. lambda 匿名函数
lambda 是"一行函数"——临时用一下,不需要正式定义。
# 普通函数
def double(x):
return x * 2
# 等价的 lambda
double = lambda x: x * 2
print(double(5)) # 10
# lambda 的语法:lambda 参数: 表达式
# 只能写一行表达式,不能写多行语句
# 多个参数
add = lambda a, b: a + b
print(add(3, 5)) # 8
# 带默认值
greet = lambda name, msg="你好": f"{msg}, {name}!"
print(greet("张三")) # 你好, 张三!
print(greet("张三", "早上好")) # 早上好, 张三!
🎯 lambda 的常见用法
# 1. 排序的 key 参数
students = [("张三", 85), ("李四", 92), ("王五", 78)]
students.sort(key=lambda s: s[1]) # 按成绩排序
print(students) # [('王五', 78), ('张三', 85), ('李四', 92)]
students.sort(key=lambda s: -s[1]) # 按成绩降序
print(students) # [('李四', 92), ('张三', 85), ('王五', 78)]
# 2. sorted() 的 key
words = ["banana", "apple", "cherry", "date"]
by_length = sorted(words, key=lambda w: len(w))
print(by_length) # ['date', 'apple', 'banana', 'cherry']
# 3. 字典按值排序
scores = {"张三": 85, "李四": 92, "王五": 78}
ranked = sorted(scores.items(), key=lambda x: -x[1])
print(ranked) # [('李四', 92), ('张三', 85), ('王五', 78)]
# 4. 条件表达式
classify = lambda x: "正数" if x > 0 else ("零" if x == 0 else "负数")
print(classify(5)) # 正数
print(classify(-3)) # 负数
print(classify(0)) # 零
💡 lambda vs def 怎么选?
lambda:简单的一行表达式,临时用(排序 key、map 的函数)
def:复杂的逻辑、需要文档、需要多次调用
经验法则:如果逻辑超过一行,用 def。
7. 闭包(Closure)入门
闭包 = 内层函数 + 记住了外层变量。即使外层函数已经执行完毕,内层函数仍然能访问那些变量。
def make_multiplier(n):
"""创建一个乘以 n 的函数"""
def multiplier(x):
return x * n # n 来自外层函数
return multiplier
# 创建两个"记住"不同 n 的函数
double = make_multiplier(2) # n=2 被"记住"了
triple = make_multiplier(3) # n=3 被"记住"了
print(double(5)) # 10(5 × 2)
print(triple(5)) # 15(5 × 3)
# make_multiplier 已经执行完了,但 n 还活在闭包里!
print(double.__closure__[0].cell_contents) # 2
print(triple.__closure__[0].cell_contents) # 3
🎯 闭包的实际用法
# 1. 计数器工厂
def make_counter(start=0):
count = start
def counter():
nonlocal count
count += 1
return count
return counter
c1 = make_counter()
c2 = make_counter(100)
print(c1()) # 1
print(c1()) # 2
print(c1()) # 3
print(c2()) # 101
print(c2()) # 102
# 2. 缓存函数(简易版)
def make_cache():
cache = {}
def cached_func(x):
if x not in cache:
print(f"计算 {x}...")
cache[x] = x ** 2
return cache[x]
return cached_func
square = make_cache()
print(square(5)) # 计算 5... → 25
print(square(3)) # 计算 3... → 9
print(square(5)) # 25(直接返回缓存,不计算)
# 3. 权限检查器
def make_checker(required_role):
def check(user_role):
return user_role == required_role
return check
is_admin = make_checker("admin")
is_editor = make_checker("editor")
print(is_admin("admin")) # True
print(is_admin("user")) # False
print(is_editor("editor")) # True
💡 闭包三要素:
1. 有嵌套函数(函数里定义函数)
2. 内层函数引用了外层函数的变量
3. 外层函数返回内层函数
闭包是装饰器的基础——Day 34 会深入学习!
8. 实战练习
🎯 练习 1:万能格式化器(*args + **kwargs)
def format_report(title, *items, **options):
"""
生成格式化报告
参数:
title: 报告标题
*items: 报告条目(任意数量)
**options: 格式选项(width, separator, uppercase)
"""
width = options.get("width", 40)
separator = options.get("separator", "-")
uppercase = options.get("uppercase", False)
print(separator * width)
print(title.center(width))
print(separator * width)
for i, item in enumerate(items, 1):
text = f"{i}. {item}"
if uppercase:
text = text.upper()
print(text)
print(separator * width)
format_report("购物清单", "牛奶", "面包", "鸡蛋", "水果", width=30)
format_report("待办事项", "写代码", "开会", "运动", separator="=", uppercase=True)
🎯 练习 2:运算符工厂(闭包)
def make_operator(op):
"""根据运算符创建对应的函数"""
operators = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b if b != 0 else "除数不能为零",
"**": lambda a, b: a ** b,
}
if op not in operators:
return None
func = operators[op]
def operate(a, b):
result = func(a, b)
print(f"{a} {op} {b} = {result}")
return result
return operate
add = make_operator("+")
mul = make_operator("*")
pow_func = make_operator("**")
add(10, 5) # 10 + 5 = 15
mul(10, 5) # 10 * 5 = 50
pow_func(2, 10) # 2 ** 10 = 1024
🎯 练习 3:自动柯里化(闭包进阶)
def curry(func):
"""自动柯里化:把多参数函数变成链式调用"""
import inspect
params = inspect.signature(func).parameters
n = len(params)
def collect_args(args_so_far):
def inner(*new_args):
all_args = args_so_far + new_args
if len(all_args) >= n:
return func(*all_args[:n])
return collect_args(all_args)
return inner
return collect_args(())
# 使用
@curry
def add3(a, b, c):
return a + b + c
# 三种调用方式
print(add3(1, 2, 3)) # 6(一次传完)
print(add3(1)(2, 3)) # 6(分两次)
print(add3(1)(2)(3)) # 6(链式调用)
9. 今日小结
| |
|---|
| |
| |
| 位置专属 / 普通 *args 关键字专属 **kwargs |
| *list |
| Local → Enclosing → Global → Built-in |
| |
| |
| |
| |
🧠 记忆口诀:
星号打包 args 收,双星 kwargs 字典留。
LEGB 四层往外找,global 少用保平安。
lambda 一行够简洁,闭包装饰器在眼前。
🔮 预告: Day 15 内置函数 — map/filter/reduce、zip/enumerate、sorted/reversed、any/all。函数式编程的第一步!