学习路径:Python_AIGC · Day 5
函数是 Python 中最重要的「一等公民」------ 可以赋值、传参、嵌套、返回。
一、参数的五种类型
Python 的参数系统极其灵活,掌握它们是写好 API 的基石。
python
def demo(a, b, *args, c=10, d=20, **kwargs):
"""
a, b --- 位置参数(必填)
*args --- 可变位置参数(元组)
c, d --- 默认参数(可选)
**kwargs --- 可变关键字参数(字典)
"""
print(f"位置: a={a}, b={b}")
print(f"多余位置: {args}")
print(f"默认: c={c}, d={d}")
print(f"额外关键字: {kwargs}")
demo(1, 2, 3, 4, 5, c=100, x="hello", y="world")
# 位置: a=1, b=2
# 多余位置: (3, 4, 5)
# 默认: c=100, d=20
# 额外关键字: {'x': 'hello', 'y': 'world'}
仅限关键字参数(Keyword-Only)
* 分隔符后的参数只能以关键字形式传入:
python
def send_email(to, subject, *, cc=None, bcc=None, attachment=None):
"""cc/bcc 只能通过关键字指定"""
print(f"发送到: {to}")
print(f"主题: {subject}")
if cc:
print(f"抄送: {cc}")
# ✅ 正确
send_email("user@example.com", "Hello", cc="boss@example.com")
# ❌ 错误:TypeError: 位置参数不能传给 cc
send_email("user@example.com", "Hello", "boss@example.com")
仅限位置参数(Positional-Only)--- Python 3.8+
/ 分隔符前的参数只能以位置形式传入:
python
def divide(a, b, /):
"""a 和 b 只能通过位置传入"""
return a / b
# ✅ 正确
print(divide(10, 3))
# ❌ 错误:TypeError
print(divide(a=10, b=3))
🔑 完整签名语法 :
def func(pos_only, /, pos_or_kw, *, kw_only):
默认参数的陷阱
python
# ❌ 灾难级错误:可变默认参数
def add_item(item, items=[]): # items 只创建一次!
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] ← 同一个列表!
print(add_item("c")) # ['a', 'b', 'c'] ← 炸了
# ✅ 正确做法
def add_item(item, items=None):
if items is None: # 每次调用都创建新列表
items = []
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['b'] ← 独立列表 ✅
为什么默认参数只在定义时 计算一次?因为 Python 在
def执行时就创建了函数对象,默认值作为函数对象的属性保存,后续调用复用同一对象。
二、作用域与 LEGB 规则
Python 按 LEGB 顺序查找变量:
| 层级 | 全称 | 说明 |
|---|---|---|
| L | Local | 当前函数内部 |
| E | Enclosing | 外层函数(嵌套函数) |
| G | Global | 模块全局 |
| B | Built-in | 内置函数/类型 |
python
x = "global" # G
def outer():
x = "enclosing" # E
def inner():
x = "local" # L
print(x)
inner()
outer() # local
global 与 nonlocal
python
count = 0 # 全局
def increment():
global count # 声明要修改全局变量
count += 1
increment()
print(count) # 1
# ---- nonlocal 用于嵌套函数 ----
def make_counter():
count = 0 # 外层变量
def counter():
nonlocal count # 声明要修改外层变量
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
闭包(Closure)
当嵌套函数引用了外层变量,就形成了闭包:
python
def make_multiplier(factor):
def multiply(x):
return x * factor # factor 来自外层
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# 查看闭包捕获的变量
print(double.__closure__[0].cell_contents) # 2
三、函数是一等公民
python
# 1. 赋值给变量
def greet(name):
return f"你好, {name}"
my_func = greet
print(my_func("小明")) # 你好, 小明
# 2. 作为参数传递
def apply(func, values):
return [func(v) for v in values]
print(apply(str.upper, ["hello", "world"])) # ['HELLO', 'WORLD']
# 3. 作为返回值
def get_operation(op):
if op == "+":
return lambda a, b: a + b
elif op == "*":
return lambda a, b: a * b
add = get_operation("+")
print(add(3, 4)) # 7
四、类型标注入门
python
from typing import List, Optional, Union, Callable
# 基本类型注解
def greeting(name: str, age: int = 18) -> str:
return f"{name} 今年 {age} 岁"
# 复杂类型
def process_items(items: list[int]) -> List[str]:
return [str(x) for x in items]
# Optional 等价于 Union[X, None]
def find_user(user_id: int) -> Optional[str]:
db = {1: "Alice", 2: "Bob"}
return db.get(user_id) # 可能返回 None
# Callable
def execute(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
五、lambda 表达式
lambda 是匿名函数,适用于简单的单行操作:
python
# 排序时指定 key
students = [
{"name": "Alice", "score": 88},
{"name": "Bob", "score": 72},
{"name": "Charlie", "score": 95},
]
students.sort(key=lambda s: s["score"], reverse=True)
print(students)
# [{'name': 'Charlie', 'score': 95}, {'name': 'Alice', 'score': 88}, ...]
# map/filter 中配合使用
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, nums))
evens = list(filter(lambda x: x % 2 == 0, nums))
⚠️ 限制 :lambda 只能包含单个表达式,不能包含语句(赋值、
return、if/for等)。复杂逻辑请用普通函数。
六、实战:参数校验 + 缓存
python
from typing import Optional, Union
import time
# 智能缓存函数:带过期时间的计算结果缓存
def memoize_with_ttl(ttl_seconds: int = 60):
"""装饰器:缓存函数返回值,ttl_seconds 后过期"""
def decorator(func):
cache = {}
def wrapper(*args, **kwargs):
# 使用参数作为缓存的 key
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
result, timestamp = cache[key]
if time.time() - timestamp < ttl_seconds:
return result
result = func(*args, **kwargs)
cache[key] = (result, time.time())
return result
return wrapper
return decorator
@memoize_with_ttl(ttl_seconds=30)
def fetch_data(url: str) -> str:
"""模拟从网络获取数据"""
print(f"正在获取: {url}")
time.sleep(2) # 模拟网络延迟
return f"<data from {url}>"
# 第一次调用:实际执行
print(fetch_data("https://api.example.com/users"))
# 第二次调用(30秒内):命中缓存,直接返回
print(fetch_data("https://api.example.com/users"))
📝 避坑指南
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
| 可变默认参数 | 默认列表/字典被所有调用共享 | 用 None + 内部创建 |
| 闭包延迟绑定 | lambda 捕获的是变量引用,不是值 | 用默认参数绑定 lambda x, i=i: x+i |
忘记 return |
默认返回 None |
需要返回值时明确 return |
| 全局变量误改 | 函数内赋值会创建局部变量,不会改全局 | 显式声明 global |
*args 放在默认参数后 |
语法错误! | *args 必须在默认参数之前 |
python
# 闭包延迟绑定的经典坑
funcs = [lambda: i for i in range(5)]
print([f() for f in funcs]) # [4, 4, 4, 4, 4] --- 所有 lambda 用的都是最后的 i
# ✅ 修复:用默认参数绑定当前值
funcs = [lambda i=i: i for i in range(5)]
print([f() for f in funcs]) # [0, 1, 2, 3, 4]
Day 5 总结:函数是 Python 的基石。掌握参数类型(位置/关键字/可变/仅限位置/仅限关键字)、LEGB 作用域规则、默认参数陷阱、闭包机制,你就能写出灵活且健壮的 API。