4.python函数与模块化

4.python函数与模块化

_

🚀 引言:为什么需要函数?

在编程中,DRY (Don't Repeat Yourself) 是核心原则。如果你发现自己在复制粘贴相同的代码块,那就是需要函数的信号。

函数不仅仅是代码的封装,它是逻辑的抽象。一个好的函数应该像是一个清晰的命令,告诉读者"做什么",而不是"怎么做"。

本章我们将深入 Python 函数的核心机制,并学习如何组织大型项目的代码结构。


1. 函数定义与类型注解 (Function Definition & Type Hints)

1.1 基础语法

使用 def 关键字定义函数。现代 Python 开发中,必须配合类型提示。

# ✅ 现代标准写法
def greet(name: str, times: int = 1) -> str:
    """
    生成问候语
    
    Args:
        name: 用户姓名
        times: 重复次数,默认 1
    
    Returns:
        生成的问候字符串
    """
    return f"Hello {name}! " * times

# 调用
message: str = greet("Alice", 2)
print(message)  # Hello Alice! Hello Alice! 

1.2 参数传递方式

Python 支持多种参数传递方式,灵活但需要规范。

def create_user(username: str, age: int, is_active: bool = True) -> dict[str, str | int | bool]:
    return {
        "username": username,
        "age": age,
        "is_active": is_active
    }

# 1. 位置参数 (Positional Arguments) - 按顺序
user1 = create_user("Bob", 25)

# 2. 关键字参数 (Keyword Arguments) - 按名称,推荐!
user2 = create_user(age=30, username="Alice")

# 3. 混合使用 - 位置参数必须在关键字参数之前
user3 = create_user("Charlie", 35, is_active=False)

💡 最佳实践:调用函数时,尽量使用关键字参数。这样代码可读性更高,且不受参数顺序变化的影响。

# ❌ 可读性差
connect("localhost", 8080, True, 30)

✅ 可读性强

connect(host="localhost", port=8080, use_ssl=True, timeout=30) ```


2. 高级参数:*args 与 **kwargs

当参数数量不确定时,使用可变参数。

2.1 *args (可变位置参数)

收集多余的位置参数到一个元组 (tuple) 中。

def sum_all(*numbers: int) -> int:
    """计算任意数量数字的和"""
    return sum(numbers)

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

2.2 **kwargs (可变关键字参数)

收集多余的关键字参数到一个字典 (dict) 中。

def print_config(**config: str) -> None:
    """打印任意配置项"""
    for key, value in config.items():
        print(f"{key}: {value}")

print_config(host="localhost", port="8080", debug="true")
# 输出:
# host: localhost
# port: 8080
# debug: true

2.3 强制关键字参数 (Keyword-Only Arguments)

*args 之后定义的参数,必须使用关键字传递。这是 Python 3 的优秀特性。

def create_order(item: str, quantity: int, *, discount: float = 0.0) -> float:
    """
    创建订单
    discount 必须使用关键字传递:create_order("apple", 5, discount=0.1)
    """
    price = 10.0 * quantity * (1 - discount)
    return price

# ✅ 正确
create_order("apple", 5, discount=0.1)

# ❌ 报错:TypeError: create_order() takes 2 positional arguments but 3 were given
# create_order("apple", 5, 0.1) 

3. 作用域规则 (Scope Rules)

Python 遵循 LEGB 规则 查找变量:

  1. Local (局部):函数内部

  2. Enclosing (嵌套):外层函数内部

  3. Global (全局):模块级别

  4. Built-in (内置):Python 内置名称

3.1 局部与全局

count: int = 0  # 全局变量

def increment() -> None:
    # count = count + 1  # ❌ 报错:UnboundLocalError
    # 如果在函数内赋值,Python 认为它是局部变量,但读取时还没赋值
    
    global count  # ✅ 声明使用全局变量
    count += 1

increment()
print(count)  # 1

⚠️ 避坑指南:尽量避免使用 global。它会让代码状态难以追踪。推荐通过返回值类属性来管理状态。

3.2 嵌套作用域 (nonlocal)

用于在内层函数修改外层函数的变量。

def outer() -> callable:
    count: int = 0
    
    def inner() -> int:
        nonlocal count  # ✅ 声明使用外层变量
        count += 1
        return count
    
    return inner

counter = outer()
print(counter())  # 1
print(counter())  # 2

4. 模块与包 (Modules & Packages)

4.1 模块导入

将代码分割到多个文件中,便于维护。

# 方式 1:导入整个模块
import math
print(math.sqrt(16))

# 方式 2:导入特定对象 (推荐,命名空间更清晰)
from math import sqrt, pi
print(sqrt(16))

# 方式 3:导入并重命名 (避免冲突)
from numpy import array as np_array

4.2 __name__ == "__main__" 守卫

这是 Python 脚本的入口点标识。

# script.py
def main() -> None:
    print("程序正在运行...")

# 只有直接运行该文件时才会执行,被 import 时不会执行
if __name__ == "__main__":
    main()

为什么需要它?

  1. 防止副作用:当你的模块被其他文件 import 时,不会自动执行测试代码或启动服务。

  2. 单元测试友好:测试框架导入模块时不会触发主逻辑。

  3. 代码复用:该文件既可以是脚本,也可以是库。

4.3 包结构示例

my_project/
├── pyproject.toml      # 项目配置与依赖
├── src/
│   └── my_package/
│       ├── __init__.py # 标记这是一个包
│       ├── main.py     # 入口文件
│       └── utils.py    # 工具函数
└── tests/              # 测试文件

5. 现代包管理 (Package Management)

5.1 为什么需要虚拟环境?

不同项目可能需要不同版本的库。虚拟环境可以隔离依赖,避免冲突。

5.2 工具选择:uv vs pip

  • pip: Python 官方默认工具,稳定但速度较慢。

  • uv: 基于 Rust 编写的现代工具,极速,兼容 pip 接口,正在成为新标准。

5.3 常用命令 (推荐使用 uv)

操作

传统 pip 命令

现代 uv 命令 (推荐)

创建虚拟环境

python -m venv .venv

uv venv

安装包

pip install requests

uv add requests

安装开发依赖

pip install pytest

uv add --dev pytest

运行脚本

python script.py

uv run script.py

导出依赖

pip freeze > requirements.txt

uv export > requirements.txt

💡 建议:新项目直接使用 uv。它会自动管理虚拟环境,无需手动激活。

# 初始化新项目
uv init my_project
cd my_project

# 添加依赖
uv add requests

# 运行代码
uv run src/main.py

6. 最佳实践与避坑指南

6.1 函数设计原则

  1. 单一职责:一个函数只做一件事。

  2. 短小精悍:函数长度尽量控制在 50 行以内。

  3. 命名清晰:动词 + 名词 (如 get_user, calculate_total)。

  4. 避免副作用:尽量使用纯函数 (输入相同,输出始终相同,不修改外部状态)。

6.2 可变默认参数陷阱 (经典面试题)

❌ 错误写法:默认参数是可变对象 (列表、字典)。

def add_item(item: str, items: list[str] = []) -> list[str]:
    items.append(item)
    return items

# 第一次调用
print(add_item("apple"))  # ['apple']
# 第二次调用 (意外!列表保留了上次的状态)
print(add_item("banana")) # ['apple', 'banana']

✅ 正确写法:使用 None 作为默认值。

def add_item(item: str, items: list[str] | None = None) -> list[str]:
    if items is None:
        items = []
    items.append(item)
    return items

6.3 文档字符串 (Docstring)

使用三引号编写函数文档,遵循 Google 或 NumPy 风格。

def connect(host: str, port: int) -> bool:
    """连接到服务器
    
    Args:
        host: 服务器地址
        port: 端口号
    
    Returns:
        连接是否成功
    
    Raises:
        ConnectionError: 当连接失败时
    """
    ...

7. 本章实战练习

练习 1:带类型检查的计算器

编写一个函数 calculate,接收两个数字和一个运算符 (+, -, *, /)。

  • 要求:添加完整的类型提示。

  • 要求:处理除零错误。

  • 要求:使用字典映射运算符,避免冗长的 if-elif

练习 2:模块化重构

将练习 1 的代码拆分:

  • calculator.py: 包含 calculate 函数。

  • main.py: 包含 if __name__ == "__main__" 块,调用 calculator 并打印结果。

练习 3:配置管理器

编写一个函数 load_config,使用 **kwargs 接收配置项。

  • 要求:返回一个字典。

  • 要求:如果传入了 debug 参数,打印所有配置。

  • 要求:timeout 参数必须是关键字参数。


📝 本章总结

知识点

核心要点

函数定义

使用 def,必须加类型提示

参数传递

推荐关键字参数,注意可变默认参数陷阱

高级参数

*args (元组), **kwargs (字典), * (强制关键字)

作用域

遵循 LEGB,慎用 global

模块

使用 __name__ == "__main__" 保护入口

包管理

推荐使用 uv 管理依赖和虚拟环境

🔑 核心心智

"函数是代码的积木。好的积木接口清晰 (类型提示)、功能单一 (单一职责)、且不会意外污染周围环境 (无副作用)。"

🚀 下一步

在下一章 高级编程特性 中,我们将探索 Python 更强大的一面:

  1. 装饰器 (Decorator):如何在不修改原代码的情况下增强函数功能?

  2. 生成器 (Generator):如何用 yield 处理海量数据而不内存溢出?

  3. 上下文管理器:如何用 with 优雅地管理资源?


💡 学习建议

  1. 从今天开始,所有新函数都必须写类型提示

  2. 尝试使用 uv 初始化你的下一个练习项目。

  3. 遇到“可变默认参数”问题,立刻条件反射使用 None 替代。

优雅、明确、简单——这才是 Python 之道。

0.Python 编程核心英语词汇表 (初学者必备) 2026-03-10

评论区