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

学习路径: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 只能包含单个表达式,不能包含语句(赋值、returnif/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。

相关推荐
陆枫Larry44 分钟前
英伟达 H100 是啥,到底好在哪?
人工智能
wno7041 小时前
Spring Boot中使用Servlet
spring boot·后端·servlet
老郑聊AI业财智造1 小时前
Spring AI 技术架构与源码分析
java·人工智能·后端·spring·架构·软件工程
一点一木1 小时前
豆包工作发布:飞书,才是它真正的底牌
人工智能·ai编程·产品
Csvn1 小时前
第 1 章 AI Agent 是什么
人工智能·aigc
阿里云大数据AI技术1 小时前
知衣科技 × 阿里云:以MaxCompute 向量检索打通商品与海外社媒内容,让跨境选品看见真实热度
人工智能·agent
省长1 小时前
别人绕过我的网关直接调用资源服务怎么办?使用 Sa-Token 解决:网关转发鉴权、RPC调用鉴权
java·后端·开源
MetaLite1 小时前
SpringBoot异常处理-到底该转换还是继续抛-入口层与调用层不能一刀切
java·spring boot·后端
2601_960017621 小时前
胶黏剂PLM选型指南:为什么垂直行业专用PLM更合适
大数据·人工智能