🐍 Day 5: Python 函数详解 — 参数、作用域与一等公民

0 阅读5分钟

学习路径:Python_AIGC · Day 5

函数是 Python 中最重要的「一等公民」—— 可以赋值、传参、嵌套、返回。


一、参数的五种类型

Python 的参数系统极其灵活,掌握它们是写好 API 的基石。

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)

* 分隔符后的参数只能以关键字形式传入

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+

/ 分隔符前的参数只能以位置形式传入

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):

默认参数的陷阱

# ❌ 灾难级错误:可变默认参数
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 顺序查找变量:

层级全称说明
LLocal当前函数内部
EEnclosing外层函数(嵌套函数)
GGlobal模块全局
BBuilt-in内置函数/类型
x = "global"  # G

def outer():
    x = "enclosing"  # E

    def inner():
        x = "local"   # L
        print(x)

    inner()

outer()  # local

global 与 nonlocal

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)

当嵌套函数引用了外层变量,就形成了闭包:

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

三、函数是一等公民

# 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

四、类型标注入门

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 是匿名函数,适用于简单的单行操作:

# 排序时指定 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 只能包含单个表达式,不能包含语句(赋值、returnif/for 等)。复杂逻辑请用普通函数。


六、实战:参数校验 + 缓存

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 必须在默认参数之前
# 闭包延迟绑定的经典坑
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。