【Python 基础全栈·第 04 篇】函数与作用域:参数·闭包·装饰器·Lambda——代码复用的艺术

系列回顾:第 01-02 篇搭建了环境、掌握了数据类型。第 03 篇学会了流程控制——if 选择、for/while 循环、推导式创建。本篇进入 Python 的"代码复用"核心——函数与作用域。函数是编程最基础的抽象机制——把一段逻辑封装成可复用的单元,通过参数传入数据,通过返回值传出结果。Python 的函数有三个核心特点:函数是一等公民(可以赋值、传参、返回)、参数极其灵活(位置/默认/可变/仅关键字)、闭包+装饰器(函数记住外层变量,动态增强函数行为)。本篇涵盖四大主题:函数定义与参数(5种参数类型/可变默认值陷阱/参数顺序规则)、作用域规则(LEGB 查找规则/global/nonlocal)、闭包与装饰器(闭包原理/装饰器模式/带参数装饰器/functools.wraps)、Lambda 与函数式编程(匿名函数/map/filter/sorted/key)。实战项目是任务管理器——一个支持增删查改、优先级、装饰器增强的命令行任务管理工具。


在这里插入图片描述

📑 文章目录


📦 一、函数定义与参数:5 种参数类型

1.1 函数定义基础

Python 用 def 关键字定义函数,用 return 返回结果。

def greet(name):
    """向指定用户打招呼"""
    return f"Hello, {name}!"

# 调用函数
message = greet("Python")
print(message)  # Hello, Python!

# 无返回值的函数(实际返回 None)
def say_hello(name):
    print(f"Hello, {name}!")

result = say_hello("World")  # Hello, World!
print(result)  # None

关键要点

  1. def 关键字:定义函数,后面跟函数名和参数列表
  2. docstring:三引号字符串,描述函数功能,可用 help(func) 查看
  3. return:返回结果,可返回多个值(实际是元组),无 return 则返回 None
  4. 函数是一等公民:可以赋值给变量、作为参数传递、作为返回值
# 函数是一等公民
def add(a, b):
    return a + b

# 赋值给变量
operation = add
print(operation(3, 5))  # 8

# 作为参数传递
def apply(func, x, y):
    return func(x, y)

print(apply(add, 10, 20))  # 30

# 作为返回值
def get_operator(op):
    if op == '+':
        return add
    # ...

my_add = get_operator('+')
print(my_add(1, 2))  # 3

1.2 五种参数类型

Python 函数支持 5 种参数类型,按严格顺序排列:

def func(pos1, pos2,          # 1. 位置参数
         key1=val1, key2=val2, # 2. 默认参数
         *args,                # 3. 可变位置参数
         *, kw_only,           # 4. 仅关键字参数
         **kwargs):            # 5. 可变关键字参数
    pass

1. 位置参数(Positional Arguments)

最基础的参数类型,调用时按位置对应。

def power(base, exp):
    return base ** exp

print(power(2, 10))  # 1024

2. 默认参数(Default Arguments)

参数有默认值,调用时可以省略。

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Python"))           # Hello, Python!
print(greet("Python", "Hi"))     # Hi, Python!
print(greet("Python", greeting="Hey"))  # Hey, Python!

⚠️ 可变默认值陷阱——这是 Python 最著名的陷阱之一:

# ❌ 错误:可变默认值
def append_to(item, lst=[]):
    lst.append(item)
    return lst

print(append_to(1))  # [1]
print(append_to(2))  # [1, 2]  ← 不是 [2]!默认值被共享了!

# ✅ 正确:用 None 替代
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

print(append_to(1))  # [1]
print(append_to(2))  # [2]  ← 正确!

原因:默认值在函数定义时创建一次,不是每次调用时创建。可变对象(list/dict/set)作为默认值会被所有调用共享。

*3. args(可变位置参数)

收集多余的位置参数为元组。

def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))      # 6
print(sum_all(1, 2, 3, 4, 5))  # 15

# 解包传入
numbers = [1, 2, 3, 4]
print(sum_all(*numbers))  # 10

4. 仅关键字参数(Keyword-Only Arguments)

*args 或单独的 * 之后的参数,必须用关键字传递。

# 用 * 强制关键字参数
def create_user(name, *, age, email):
    return f"User: {name}, Age: {age}, Email: {email}"

# create_user("Alice", 25, "a@b.com")  # ❌ TypeError
create_user("Alice", age=25, email="a@b.com")  # ✅

# Python 3.8+ 用 / 强制位置参数
def greet(name, /, greeting="Hello"):
    return f"{greeting}, {name}!"

greet("Python")              # ✅ 位置
greet("Python", "Hi")        # ✅ 位置
greet("Python", greeting="Hi")  # ✅ name只能位置
# greet(name="Python")       # ❌ name是仅位置参数

**5. kwargs(可变关键字参数)

收集多余的关键字参数为字典。

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Python", version=3.12, creator="Guido")
# name: Python
# version: 3.12
# creator: Guido

# 解包传入
config = {"host": "localhost", "port": 8080}
print_info(**config)

1.3 参数顺序规则

Python 强制要求参数按以下顺序定义:

位置参数 → 默认参数 → *args → 仅关键字参数 → **kwargs
def func(a, b,           # 位置参数
         c=10,           # 默认参数
         *args,          # 可变位置
         d,              # 仅关键字(在*args后)
         e=20,           # 仅关键字(有默认值)
         **kwargs):      # 可变关键字
    print(f"a={a}, b={b}, c={c}")
    print(f"args={args}")
    print(f"d={d}, e={e}")
    print(f"kwargs={kwargs}")

func(1, 2, 3, 4, 5, d=6, f=7, g=8)
# a=1, b=2, c=3
# args=(4, 5)
# d=6, e=20
# kwargs={'f': 7, 'g': 8}

🔭 二、作用域规则:LEGB·global·nonlocal

2.1 LEGB 规则

Python 用 LEGB 规则查找变量名:

层级 名称 说明 示例
L Local 函数内部 函数内定义的变量
E Enclosing 外层函数 闭包中的外层变量
G Global 模块级 模块顶层定义的变量
B Built-in 内置 print/len/range 等
x = "global"           # G: Global

def outer():
    x = "enclosing"    # E: Enclosing
    
    def inner():
        x = "local"    # L: Local
        print(x)       # 查找顺序: L → E → G → B
    
    inner()

outer()  # local

查找规则:从内到外,找到第一个就停止。

# Built-in 层
print(len("hello"))  # len 是 Built-in

# 如果被 Global 覆盖
len = "I'm not a function"  # ⚠️ 覆盖了内置 len
# print(len("hello"))  # ❌ TypeError
del len  # 恢复内置
print(len("hello"))  # 5

2.2 global 与 nonlocal

global:在函数内声明全局变量,允许修改。

counter = 0

def increment():
    global counter  # 声明使用全局变量
    counter += 1

increment()
increment()
print(counter)  # 2

nonlocal:在闭包内修改外层函数的变量。

def make_counter():
    count = 0
    
    def increment():
        nonlocal count  # 声明使用外层变量
        count += 1
        return count
    
    return increment

c = make_counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3

global vs nonlocal

关键字 修改的层级 使用场景
global 修改 Global 层 模块级配置(少用)
nonlocal 修改 Enclosing 层 闭包计数器/状态

🎭 三、闭包与装饰器:函数增强的终极模式

3.1 闭包(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

闭包的三要素

  1. 有嵌套函数(内层函数)
  2. 内层函数引用外层变量
  3. 外层函数返回内层函数

闭包的应用

# 1. 状态封装
def make_averager():
    numbers = []
    def add_number(n):
        numbers.append(n)
        return sum(numbers) / len(numbers)
    return add_number

avg = make_averager()
print(avg(10))  # 10.0
print(avg(20))  # 15.0
print(avg(30))  # 20.0

# 2. 延迟计算
def lazy_property(func):
    """延迟计算属性"""
    attr_name = '_lazy_' + func.__name__
    
    @property
    def wrapper(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, func(self))
        return getattr(self, attr_name)
    
    return wrapper

3.2 装饰器(Decorator)

装饰器是闭包的经典应用——在不修改原函数代码的情况下,动态增强函数行为

import time
import functools

def timer(func):
    """计时装饰器:测量函数执行时间"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 执行耗时: {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "done"

slow_function()  # slow_function 执行耗时: 1.0012s

装饰器的本质

@timer
def func():
    pass

# 等价于:
func = timer(func)

带参数的装饰器

def retry(max_attempts=3, delay=1):
    """重试装饰器:失败时自动重试"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"第{attempt}次失败: {e}{delay}秒后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_api_call():
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络错误")
    return "成功"

常用装饰器模式

# 1. 日志装饰器
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"返回 {result}")
        return result
    return wrapper

# 2. 缓存装饰器
def memoize(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

# 3. 权限装饰器
def require_auth(func):
    @functools.wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated'):
            raise PermissionError("需要登录")
        return func(user, *args, **kwargs)
    return wrapper

# 4. 类型检查装饰器
def typecheck(**expected_types):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(**kwargs):
            for name, expected in expected_types.items():
                if name in kwargs and not isinstance(kwargs[name], expected):
                    raise TypeError(f"{name} 应为 {expected}")
            return func(**kwargs)
        return wrapper
    return decorator

functools.wraps 的重要性

# ❌ 不用 wraps
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_function():
    """这是我的函数"""
    pass

print(my_function.__name__)  # wrapper ← 丢失了原函数名!
print(my_function.__doc__)   # None ← 丢失了文档字符串!

# ✅ 用 wraps
def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def my_function():
    """这是我的函数"""
    pass

print(my_function.__name__)  # my_function ← 保留了!
print(my_function.__doc__)   # 这是我的函数 ← 保留了!

3.3 Lambda 表达式

Lambda 是一行匿名函数,语法:lambda 参数: 表达式

# 基本用法
square = lambda x: x ** 2
print(square(5))  # 25

add = lambda a, b: a + b
print(add(3, 5))  # 8

# 与 sorted 配合
students = [
    {'name': 'Alice', 'score': 85},
    {'name': 'Bob', 'score': 92},
    {'name': 'Charlie', 'score': 78},
]

students_sorted = sorted(students, key=lambda s: s['score'], reverse=True)
# [{'name': 'Bob', 'score': 92}, {'name': 'Alice', 'score': 85}, ...]

# 与 map/filter 配合
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x ** 2, numbers))  # [1, 4, 9, 16, 25]
evens = list(filter(lambda x: x % 2 == 0, numbers))  # [2, 4]

# 但推导式更 Pythonic!
squares = [x ** 2 for x in numbers]  # 更好
evens = [x for x in numbers if x % 2 == 0]  # 更好

Lambda 的使用原则

  1. 简单逻辑用 Lambda:一行能写完的
  2. 复杂逻辑用 def:多行、有分支、有赋值的
  3. sorted/key 是最佳场景:排序的 key 函数
  4. 不要滥用:如果 Lambda 让代码更难读,就用 def

🛠️ 实战项目:任务管理器

综合运用函数、闭包、装饰器,实现一个命令行任务管理器:

"""
任务管理器 - 综合运用函数、闭包、装饰器
功能:增删查改、优先级、装饰器增强
"""
import functools
from datetime import datetime

# ===== 装饰器 =====

def log_action(func):
    """日志装饰器:记录操作"""
    @functools.wraps(func)
    def wrapper(manager, *args, **kwargs):
        result = func(manager, *args, **kwargs)
        action = func.__name__.replace('_', ' ')
        print(f"  📋 操作: {action}")
        return result
    return wrapper

def validate_task_id(func):
    """验证任务ID装饰器"""
    @functools.wraps(func)
    def wrapper(manager, task_id, *args, **kwargs):
        if task_id not in manager.tasks:
            print(f"  ❌ 任务 #{task_id} 不存在")
            return None
        return func(manager, task_id, *args, **kwargs)
    return wrapper

# ===== 任务管理器 =====

def create_task_manager():
    """工厂函数:创建任务管理器(闭包)"""
    tasks = {}  # 任务存储
    next_id = [1]  # 用列表包装以便 nonlocal 修改
    
    def add_task(title, priority="medium"):
        """添加任务"""
        nonlocal next_id
        task_id = next_id[0]
        next_id[0] += 1
        tasks[task_id] = {
            'title': title,
            'priority': priority,
            'done': False,
            'created': datetime.now().strftime('%Y-%m-%d %H:%M')
        }
        print(f"  ✅ 任务 #{task_id} 已添加: {title} [{priority}]")
        return task_id
    
    @validate_task_id
    def complete_task(task_id):
        """完成任务"""
        tasks[task_id]['done'] = True
        print(f"  ✅ 任务 #{task_id} 已完成")
    
    @validate_task_id
    def delete_task(task_id):
        """删除任务"""
        title = tasks[task_id]['title']
        del tasks[task_id]
        print(f"  🗑️ 任务 #{task_id} 已删除: {title}")
    
    def list_tasks(filter_by=None):
        """列出任务"""
        if not tasks:
            print("  📭 暂无任务")
            return
        
        priority_order = {'high': 0, 'medium': 1, 'low': 2}
        sorted_tasks = sorted(
            tasks.items(),
            key=lambda x: (x[1]['done'], priority_order.get(x[1]['priority'], 1))
        )
        
        print("\n  📋 任务列表:")
        print("  " + "-" * 50)
        for tid, task in sorted_tasks:
            status = "✅" if task['done'] else "⬜"
            pri = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(task['priority'], "⚪")
            print(f"  {status} #{tid}: {task['title']} {pri} ({task['created']})")
        print("  " + "-" * 50)
    
    def get_stats():
        """统计信息"""
        total = len(tasks)
        done = sum(1 for t in tasks.values() if t['done'])
        return {'total': total, 'done': done, 'pending': total - done}
    
    # 返回管理器对象(字典形式)
    return {
        'add': log_action(add_task),
        'complete': log_action(complete_task),
        'delete': log_action(delete_task),
        'list': list_tasks,
        'stats': get_stats,
        'tasks': tasks
    }

# ===== 主程序 =====

def main():
    manager = create_task_manager()
    
    # 添加任务
    manager['add']("学习Python函数", "high")
    manager['add']("完成项目报告", "high")
    manager['add']("买咖啡", "low")
    manager['add']("回复邮件", "medium")
    
    # 列出任务
    manager['list']()
    
    # 完成任务
    manager['complete'](1)
    manager['complete'](3)
    
    # 列出更新后的任务
    manager['list']()
    
    # 统计
    stats = manager['stats']()
    print(f"\n  📊 统计: 总计{stats['total']}个, 完成{stats['done']}个, 待办{stats['pending']}个")

if __name__ == '__main__':
    main()

📌 第 04 篇总结

维度 核心内容
函数参数 5种参数类型 / 可变默认值陷阱 / 参数顺序规则
作用域 LEGB查找规则 / global / nonlocal
闭包与装饰器 闭包三要素 / 装饰器模式 / 带参数装饰器 / functools.wraps
Lambda 匿名函数 / sorted+key / map+filter / 推导式更Pythonic
实战项目 任务管理器——闭包+装饰器+优先级

三大要点

  1. 参数顺序:位置→默认→*args→仅关键字→**kwargs,可变默认值用 None 替代
  2. LEGB 规则:从内到外查找变量,global 修改全局,nonlocal 修改外层
  3. 装饰器是函数增强的终极模式:不修改原函数,动态添加功能,functools.wraps 保留元信息

第 04 篇的核心 = 一等公民(基础) + 闭包(记忆) + 装饰器(增强) + Lambda(简洁),函数是代码复用的艺术


Python 基础全栈系列进度

篇号 主题 核心内容 状态
01 全景搭建 历史/安装/解释器/IDE/Pythonic
02 类型与运算 数字/字符串/布尔/类型转换/计算器
03 流程控制 if/for/while/推导式/猜数字
04 函数与作用域(本文) 参数/闭包/装饰器/Lambda/任务管理器
05 数据结构 列表/元组/字典/集合/通讯录 ⏳ 下一篇
06 面向对象 类/继承/多态/封装/魔术方法 待写
07 模块与包 import/pip/虚拟环境/标准库 待写
08 文件与异常 读写/with/异常/上下文管理器 待写
09 高级特性 生成器/迭代器/描述符/元类 待写
10 实战工程 规范/测试/部署/项目结构 待写

下一篇预告:第 05 篇将深入数据结构——列表(list)/元组(tuple)/字典(dict)/集合(set)/推导式进阶/序列解包,实战项目是通讯录系统


参考链接

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐