第 5 章 函数与模块

814 阅读15分钟

在 Python 中,函数与模块是实现代码复用与工程化组织的核心工具。函数将重复逻辑封装为可调用单元,模块则将相关函数、类等聚合为独立文件,二者共同解决代码冗余、维护困难等问题。从简单工具函数到复杂项目架构,函数与模块贯穿程序设计的全流程。本章系统解析函数的定义与参数机制、命名空间与作用域规则、模块与包的组织方式,结合工程实践提炼代码复用技巧,帮助读者构建模块化思维,为开发可维护、可扩展的程序奠定基础。

5.1 函数基础

函数是代码复用的基本单元,通过封装特定逻辑实现 “一次定义,多次调用”。掌握函数的定义规范、参数传递与返回值处理,是编写简洁代码的关键。

5.1.1 函数的定义与调用

Python 中使用def关键字定义函数,基本语法如下:

def 函数名(参数列表):
    """函数文档字符串(描述功能、参数、返回值)"""
    # 函数体逻辑
    return 返回值  # 可选,默认返回None

示例:定义并调用问候函数

# 场景:用户问候功能
def greet_user(username):
    """向指定用户显示问候语(用户名首字母大写)"""
    print(f"Hello, {username.title()}!")

# 调用函数:传入实参'alice'
greet_user('alice')  # 输出:Hello, Alice!

📌 重点提示:函数名需遵循 “小写字母 + 下划线” 规范(如greet_user),直观反映功能;文档字符串(三引号包裹)是良好实践,通过help(函数名)可查看,例如help(greet_user)会显示函数功能说明。

5.1.2 函数的参数类型

函数参数是外部与函数交互的接口,Python 支持多种参数形式,适应不同传递需求:

参数类型含义示例结果说明
位置参数按顺序匹配形参describe_pet('dog', 'willie')描述狗 willie实参与形参顺序必须一致
关键字参数按 “参数名 = 值” 传递describe_pet(animal_type='cat', pet_name='mimi')描述猫 mimi无需关注顺序,可读性更强
默认参数定义时指定默认值def describe_pet(pet_name, animal_type='dog')未传类型时默认为狗需放在参数列表末尾
*args接收任意位置参数make_pizza(12, '蘑菇', '青椒')制作 12 寸双配料披萨打包为元组,适合不定数量参数
**kwargs接收任意关键字参数build_profile('albert', 'einstein', location='princeton')构建含 location 的档案打包为字典,适合扩展信息

示例 1:位置参数与关键字参数

# 场景:宠物信息描述
def describe_pet(animal_type, pet_name):
    """描述宠物的类型和名字"""
    print(f"I have a {animal_type}. Its name is {pet_name.title()}.")

# 位置参数:按顺序传递
describe_pet('dog', 'willie')  # 输出:I have a dog. Its name is Willie.

# 关键字参数:明确参数名
describe_pet(pet_name='mimi', animal_type='cat')  # 输出:I have a cat. Its name is Mimi.

示例 2:默认参数

# 场景:简化常见调用(默认宠物类型为狗)
def describe_pet(pet_name, animal_type='dog'):
    """描述宠物,默认类型为狗"""
    print(f"I have a {animal_type}. Its name is {pet_name.title()}.")

describe_pet('willie')  # 未传类型,使用默认值 → I have a dog. Its name is Willie.
describe_pet('mimi', 'cat')  # 传递类型,覆盖默认值 → I have a cat. Its name is Mimi.

示例 3:可变参数(*args**kwargs

# 场景1:处理不定数量的披萨配料
def make_pizza(size, *toppings):
    """制作指定尺寸和配料的披萨"""
    print(f"Making a {size}-inch pizza with:")
    for topping in toppings:
        print(f"- {topping}")

make_pizza(12, '蘑菇', '青椒', '芝士')  # 1个固定参数+3个可变参数


# 场景2:构建含扩展信息的用户档案
def build_profile(first, last, **user_info):
    """构建包含基本信息和扩展信息的用户档案"""
    profile = {'first': first, 'last': last}
    profile.update(user_info)  # 添加扩展信息
    return profile

user = build_profile('albert', 'einstein', location='princeton', field='physics')
print(user)  # 输出:{'first': 'albert', 'last': 'einstein', 'location': 'princeton', 'field': 'physics'}

5.1.3 函数的返回值

函数通过return语句返回结果,支持单个值、多个值或复杂数据结构,满足不同场景的数据传递需求。

示例 1:返回单个值

# 场景:格式化姓名
def get_full_name(first, last):
    """返回首字母大写的全名"""
    return f"{first.title()} {last.title()}"

name = get_full_name('jimi', 'hendrix')
print(name)  # 输出:Jimi Hendrix

示例 2:返回多个值(本质为元组)

# 场景:返回姓名及长度
def get_name_info(first, last):
    """返回全名和名字长度"""
    full_name = f"{first} {last}"
    return full_name.title(), len(full_name)  # 隐式返回元组

name, length = get_name_info('jimi', 'hendrix')
print(f"Name: {name}, Length: {length}")  # 输出:Name: Jimi Hendrix, Length: 12

示例 3:返回复杂结构(字典 / 列表)

# 场景:生成学生成绩字典
def get_student_scores():
    """返回包含3名学生成绩的字典"""
    return {
        'alice': 95,
        'bob': 88,
        'charlie': 92
    }

scores = get_student_scores()
for name, score in scores.items():
    print(f"{name.title()}: {score}")  # 依次输出学生成绩

5.1.4 函数文档字符串

文档字符串(Docstring)是函数的 “说明书”,位于函数定义首行,用三引号包裹,用于描述功能、参数、返回值及异常,提升代码可读性。

示例:规范的函数文档

def calculate_area(radius):
    """计算圆的面积
    
    参数:
        radius (float): 圆的半径(非负数)
    
    返回:
        float: 圆的面积(公式:π × radius²)
    
    异常:
        ValueError: 若radius为负数,抛出值错误
    """
    import math
    if radius < 0:
        raise ValueError("半径不能为负数")
    return math.pi * (radius **2)

# 查看文档
help(calculate_area)  # 输出上述文档内容
print(calculate_area(5))  # 输出:78.53981633974483

5.1.5 匿名函数与高阶函数

除常规函数外,Python 支持匿名函数(lambda)与高阶函数,为简洁处理迭代逻辑提供灵活性。

匿名函数(lambda

匿名函数是 “一行式” 函数,用lambda定义,仅含一个表达式,适合简单逻辑。语法:lambda 参数: 表达式

示例:匿名函数的应用

# 场景1:简单计算
add = lambda x, y: x + y
print(add(3, 5))  # 输出:8

# 场景2:列表排序依据
students = [('Alice', 22), ('Bob', 19), ('Charlie', 25)]
students.sort(key=lambda x: x[1])  # 按年龄排序
print(students)  # 输出:[('Bob', 19), ('Alice', 22), ('Charlie', 25)]

# 场景3:筛选数据
numbers = [1, 2, 3, 4, 5]
odds = list(filter(lambda x: x % 2 != 0, numbers))  # 筛选奇数
print(odds)  # 输出:[1, 3, 5]

📌 重点提示lambda仅适用于简单逻辑,复杂逻辑(如多分支、循环)需用def定义常规函数;匿名函数无文档,可读性较低,避免在核心逻辑中过度使用。

高阶函数

高阶函数指 “接收函数作为参数” 或 “返回函数” 的函数,Python 内置mapfilterreduce等,简化迭代处理。

函数功能示例结果
map(func, iterable)对可迭代对象每个元素应用funcmap(str, [1,2,3])['1', '2', '3']
filter(func, iterable)保留func返回True的元素filter(lambda x: x>3, [1,2,3,4])[4]
reduce(func, iterable)累积计算(需从functools导入)reduce(lambda x,y: x+y, [1,2,3])6

示例:高阶函数的应用

# 场景1:批量转换数据类型
numbers = [1, 2, 3, 4]
str_numbers = list(map(str, numbers))  # 转为字符串列表
print(str_numbers)  # 输出:['1', '2', '3', '4']


# 场景2:筛选符合条件的元素
words = ['apple', 'cat', 'banana', 'dog']
long_words = list(filter(lambda s: len(s) > 3, words))  # 筛选长单词
print(long_words)  # 输出:['apple', 'banana']


# 场景3:累积计算
from functools import reduce
total = reduce(lambda x, y: x + y, [1, 2, 3, 4])  # 求和
print(total)  # 输出:10

5.2 命名空间与作用域

变量的可访问范围由 “作用域” 决定,而 “命名空间” 是变量名与值的映射集合。理解二者规则,可避免变量冲突,清晰管理变量访问范围。

5.2.1 命名空间概述

命名空间是 “变量名→值” 的字典,用于区分不同位置的变量,避免命名冲突。Python 主要有 3 类命名空间:

命名空间含义生命周期示例
内置命名空间Python 自带的变量 / 函数程序启动→退出printlenmath
全局命名空间模块顶层定义的变量 / 函数模块导入→解释器退出脚本中定义的a=10def demo()
局部命名空间函数 / 类内部定义的变量函数调用→执行结束函数内的b=20

示例:命名空间的区分

# 全局命名空间:变量a、函数demo
a = 10

def demo():
    # 局部命名空间:变量b
    b = 20
    print(f"局部b: {b}")  # 可访问局部变量
    print(f"全局a: {a}")  # 可访问全局变量

demo()
print(f"全局a: {a}")
# print(b)  # 报错:NameError(b不在全局命名空间)

5.2.2 作用域类型

作用域是命名空间的 “可见范围”,变量查找遵循 “LEGB” 规则(从内到外):

作用域含义查找优先级
L(Local)函数 / 类内部最高
E(Enclosing)嵌套函数的外层函数次之
G(Global)模块顶层再次之
B(Built-in)Python 内置最低

示例:作用域的查找顺序

x = 100  # 全局作用域(G)

def outer():
    x = 200  # 嵌套作用域(E)
    def inner():
        # 局部作用域(L)无x,向上查找E
        print(f"inner中的x: {x}")  # 输出:200
    inner()

outer()
print(f"全局x: {x}")  # 输出:100

5.2.3 作用域关键字(globalnonlocal

默认情况下,函数内部无法修改全局或嵌套外层变量,需用关键字声明:

关键字作用适用场景
global声明变量为全局变量函数内修改全局变量
nonlocal声明变量为嵌套外层变量嵌套函数内修改外层变量

示例 1:global修改全局变量

x = 10

def modify_global():
    global x  # 声明x为全局变量
    x = 20  # 修改全局变量

modify_global()
print(x)  # 输出:20(全局变量已被修改)

示例 2:nonlocal修改嵌套外层变量

def outer():
    x = 10  # 嵌套外层变量
    def inner():
        nonlocal x  # 声明x为嵌套外层变量
        x = 20  # 修改外层变量
    inner()
    print(x)  # 输出:20(外层变量已被修改)

outer()

📌 重点提示:滥用global会增加代码耦合度,建议优先通过 “参数传递 + 返回值” 修改数据;nonlocal仅适用于嵌套函数,且变量必须在外层已定义。

5.3 模块与包

当代码量增长时,需用模块(单个.py 文件)与包(含多个模块的文件夹)组织代码,实现模块化拆分与复用。

5.3.1 模块基础与导入方式

模块是包含 Python 代码的.py 文件,通过import导入后可使用其内容。常用导入方式如下:

导入方式语法特点
导入整个模块import 模块名使用时需加模块名.前缀
导入特定内容from 模块名 import 内容可直接使用名称,无需前缀
导入并指定别名import 模块名 as 别名简化长模块名调用
导入所有内容(不推荐)from 模块名 import *易引发名称冲突

示例:模块导入方式

# 方式1:导入整个模块
import math
print(math.pi)  # 输出:3.141592653589793

# 方式2:导入特定内容
from math import pi, sqrt
print(pi)  # 输出:3.141592653589793
print(sqrt(16))  # 输出:4.0

# 方式3:指定别名
import numpy as np
print(np.array([1, 2, 3]))  # 输出:[1 2 3]

模块缓存机制

Python 会将已导入的模块缓存到sys.modules中,同一模块仅导入一次,避免重复执行初始化代码。

示例:验证模块缓存

import sys
import string_utils  # 首次导入,执行模块代码

# 查看缓存
print('string_utils' in sys.modules)  # 输出:True

import string_utils  # 二次导入,使用缓存,不执行代码

📌 提示:调试时需重新加载模块,可使用importlib.reload(模块名),但生产环境应避免频繁使用。

导入自定义模块

步骤 1:创建模块string_utils.py

# string_utils.py
def reverse_string(s):
    """反转字符串"""
    return s[::-1]

def count_vowels(s):
    """统计元音字母数量"""
    vowels = {'a', 'e', 'i', 'o', 'u'}
    return sum(1 for c in s.lower() if c in vowels)

步骤 2:导入并使用

import string_utils as su

s = "Hello Python"
print(su.reverse_string(s))  # 输出:nohtyP olleH
print(su.count_vowels(s))    # 输出:4

5.3.2 包的结构与使用

包是含__init__.py的文件夹,用于组织多个相关模块。__init__.py可控制包的导入行为(如通过__all__指定公开模块)。

包结构示例

my_project/
├── main.py          # 主程序
└── data/            # 数据处理包
    ├── __init__.py  # 包初始化文件
    ├── clean.py     # 数据清洗模块
    └── analyze.py   # 数据分析模块

配置__init__.py

# data/__init__.py
# 指定“from data import *”时导入的模块
__all__ = ['clean', 'analyze']

# 提前导入子模块,简化调用
from . import clean, analyze

导入包内模块

# 方式1:导入子模块并使用
from data import clean, analyze

data = [1, None, 3, 5]
cleaned = clean.remove_null(data)  # 调用clean模块函数
avg = analyze.calculate_average(cleaned)  # 调用analyze模块函数
print(avg)  # 输出:3.0


# 方式2:直接导入函数
from data.clean import remove_null
from data.analyze import calculate_average

cleaned = remove_null([1, None, 3])
print(calculate_average(cleaned))  # 输出:2.0

5.3.3 常用标准库应用

Python 标准库是内置模块集合,涵盖文件操作、数学计算等功能,无需安装即可使用。

1. os:操作系统交互

用于文件路径处理、目录操作等跨平台功能。

import os

# 获取当前目录
print(os.getcwd())  # 输出:当前工作目录路径

# 拼接路径(跨平台兼容)
path = os.path.join("data", "test.txt")
print(path)  # 输出:data/test.txt(Linux/macOS)或 data\test.txt(Windows)

# 创建多级目录
os.makedirs("temp/logs", exist_ok=True)  # exist_ok=True避免目录已存在时报错

2. datetime:日期时间处理

用于创建、计算、格式化日期时间。

from datetime import datetime, timedelta

# 获取当前时间并格式化
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # 输出:2024-10-04 15:30:00

# 计算3天后的时间
future = now + timedelta(days=3)
print(future.strftime("%Y-%m-%d"))  # 输出:2024-10-07

3. random:随机数生成

用于生成随机数、随机选择等场景。

import random

# 生成1-10的随机整数
print(random.randint(1, 10))  # 输出:3(示例)

# 从列表随机选择元素
fruits = ['apple', 'banana', 'orange']
print(random.choice(fruits))  # 输出:banana(示例)

4. logging:日志记录

用于程序运行日志的分级记录(比print更强大)。

import logging

# 配置日志:输出到控制台和文件
logging.basicConfig(
    level=logging.WARNING,  # 仅记录WARNING及以上
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('app.log'),  # 写入文件
        logging.StreamHandler()          # 输出到控制台
    ]
)

logging.warning("这是警告日志")  # 会被记录
logging.info("这是信息日志")    # 不会被记录(级别低于WARNING)

5.3.4 第三方模块安装与使用

第三方模块需通过pip安装,如requests(HTTP 请求)、pandas(数据处理)等。

安装模块

pip install requests pandas  # 安装最新版本
pip install requests==2.31.0  # 安装指定版本

使用示例

示例 1:requests发送 HTTP 请求

import requests

response = requests.get("https://api.github.com")
if response.status_code == 200:
    data = response.json()
    print(data['message'])  # 输出:Hello Octocat!

示例 2:pandas处理 CSV 数据

import pandas as pd

# 读取CSV文件
df = pd.read_csv("data.csv")
# 查看前5行
print(df.head())
# 筛选分数>90的行
high_scores = df[df['score'] > 90]
# 保存结果
high_scores.to_csv("high_scores.csv", index=False)

5.4 函数与模块综合应用及工程实践

结合工程实践,合理设计函数与模块结构,可提升代码的可复用性与可维护性。

5.4.1 函数装饰器进阶

装饰器用于 “增强函数功能而不修改其代码”,支持带参数、多装饰器叠加等高级用法。

带参数的装饰器

import time
from functools import wraps

def log_execution(file_path='log.txt'):
    """带参数的装饰器:记录函数执行时间到指定文件"""
    def decorator(func):
        @wraps(func)  # 保留原函数元信息
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            # 写入日志
            with open(file_path, 'a') as f:
                f.write(f"{func.__name__} 执行时间:{end-start:.2f}秒\n")
            return result
        return wrapper
    return decorator

@log_execution('func_log.txt')  # 指定日志文件
def slow_task(n):
    time.sleep(n)

slow_task(1)  # 执行后,func_log.txt会记录执行时间

多个装饰器叠加

# 装饰器1:计时
def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时:{time.time()-start:.2f}秒")
        return result
    return wrapper

# 装饰器2:日志
def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数:{args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@timer  # 外层装饰器,后执行
@log    # 内层装饰器,先执行
def add(a, b):
    return a + b

add(3, 5)  # 输出:调用 add,参数:(3, 5), {} → add 耗时:0.00秒

5.4.2 工程实践:函数模块化设计

模块化设计遵循 “单一职责” 原则:一个函数只做一件事,一个模块只包含相关功能。

示例:数据处理项目结构

data_processing/
├── main.py          # 主程序入口
├── data_clean.py    # 数据清洗函数
├── data_analyze.py  # 数据分析函数
└── utils.py         # 通用工具函数

data_clean.py(数据清洗)

def remove_null(data, fill_value=None):
    """移除或填充空值"""
    if fill_value is None:
        return [x for x in data if x is not None]
    return [x if x is not None else fill_value for x in data]

def remove_duplicates(data):
    """移除重复元素,保留顺序"""
    seen = set()
    return [x for x in data if x not in seen and not seen.add(x)]

data_analyze.py(数据分析)

def calculate_mean(data):
    """计算均值"""
    if not data:
        raise ValueError("数据不能为空")
    return sum(data) / len(data)

def calculate_variance(data):
    """计算方差"""
    mean = calculate_mean(data)
    return sum((x - mean)** 2 for x in data) / len(data)

main.py(主程序)

from data_clean import remove_null, remove_duplicates
from data_analyze import calculate_mean, calculate_variance

# 数据处理流程
raw_data = [1, None, 3, 5, 3, None, 7]
cleaned_data = remove_duplicates(remove_null(raw_data))
print(f"清洗后数据:{cleaned_data}")
print(f"均值:{calculate_mean(cleaned_data)}")
print(f"方差:{calculate_variance(cleaned_data)}")

5.4.3 最佳实践总结

  1. 函数设计:单一职责,参数明确,文档完整,避免过长函数(建议不超过 50 行)。
  2. 模块组织:按功能划分模块,避免过大模块(建议不超过 300 行),核心逻辑与辅助逻辑分离。
  3. 命名规范:函数 / 模块名使用小写 + 下划线,直观反映功能(如calculate_mean而非func1)。
  4. 依赖管理:第三方模块版本固定(如通过requirements.txt),避免版本兼容问题。
  5. 可测试性:函数应易于单元测试(输入明确,输出可预期),避免依赖全局状态。

通过遵循这些实践,可构建出逻辑清晰、易于维护的 Python 程序,为后续扩展与协作奠定基础。