深入浅出:Python函数与模块——构建可复用、可维护代码的基石
🔎大家好,我是ZTLJQ,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
📝个人主页-ZTLJQ的主页
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝📣系列果你对这个系列感兴趣的话
专栏 - Python从零到企业级应用:短时间成为市场抢手的程序员
✔说明⇢本人讲解主要包括Python爬虫、JS逆向、Python的企业级应用
如果你对这个系列感兴趣的话,可以关注订阅哟👋
在编程的世界里,重复是效率的天敌,混乱是维护的噩梦。而Python,以其优雅和简洁著称,为我们提供了两个强大的武器来对抗这些问题:函数 (Functions) 和 模块 (Modules)。它们是将复杂程序分解为更小、更易管理单元的核心思想,是实现代码复用、提高可读性和可维护性的不二法门。今天,我们将进行一次深度探索,从基础语法到高级技巧,再到实际应用,彻底掌握这两大基石。
第一部分:函数——代码的“乐高积木”
想象一下,你有一个需要反复执行的特定任务,比如计算一个数的平方根,或者格式化一段用户信息。如果每次都需要重写这段逻辑,不仅费时费力,还容易出错。函数就是为此而生的。它是一个命名的代码块,用于执行一个特定的任务。你可以向它传递数据(参数),它处理后返回结果(返回值)。
1. 函数的定义与调用
-
语法:
def function_name(parameters): """函数文档字符串 (Docstring) - 描述函数的功能""" # 函数体 (Function Body) # 执行具体操作 return result # 可选,用于返回值 -
关键要素:
def: 定义函数的关键字。function_name: 函数名称,应遵循snake_case命名规范,并清晰表达其功能。parameters: 参数列表,位于括号内,多个参数用逗号分隔。参数是函数接收外部数据的占位符。:: 冒号表示函数头结束,函数体开始。- 缩进的代码块:所有属于该函数的代码都必须有相同的缩进(通常是4个空格)。
return: 关键字,用于退出函数并可选择性地返回一个或多个值给调用者。如果没有return语句,函数默认返回None。
2. 参数的奥秘:从简单到灵活
参数是函数灵活性的来源。Python支持多种参数类型:
-
位置参数 (Positional Arguments):
最基础的形式。实参(调用时传入的值)按照形参(定义时声明的变量)的顺序一一对应。def greet(name, age): print(f"Hello, {name}! You are {age} years old.") # 调用 greet("Alice", 30) # 输出: Hello, Alice! You are 30 years old. # 注意顺序: "Alice" 对应 name, 30 对应 age -
关键字参数 (Keyword Arguments):
调用函数时,通过参数名=值的方式传递参数。这使得参数顺序不再重要,代码也更具可读性。 1# 使用关键字参数调用 2greet(age=25, name="Bob") # 输出: Hello, Bob! You are 25 years old. 3# 顺序可以颠倒,但意义明确 -
默认参数 (Default Arguments):
在定义函数时为参数指定一个默认值。如果调用时没有提供该参数,则使用默认值。默认参数必须放在非默认参数之后。 1def create_profile(name, city="Unknown", country="China"): 2 print(f"{name} is from {city}, {country}.") 3 4# 调用 5create_profile("Charlie") # 输出: Charlie is from Unknown, China. 6create_profile("David", "Beijing") # 输出: David is from Beijing, China. (country用默认值) 7create_profile("Eve", "Shanghai", "Japan") # 输出: Eve is from Shanghai, Japan. -
可变位置参数 (
*args):
当你不确定函数需要接收多少个位置参数时,可以使用*args。它会将所有额外的位置参数收集到一个元组(tuple)中。def sum_all(*numbers): """计算任意数量数字的总和""" total = 0 for num in numbers: total += num return total # 调用 print(sum_all(1, 2)) # 输出: 3 print(sum_all(1, 2, 3, 4, 5)) # 输出: 15 print(sum_all()) # 输出: 0 (空元组) # 解包 (Unpacking) 的应用 my_numbers = [10, 20, 30] print(sum_all(*my_numbers)) # 输出: 60 (* 将列表解包成独立的参数) -
可变关键字参数 (
**kwargs):
用于接收任意数量的关键字参数。它会将所有额外的关键字参数收集到一个字典(dict)中。def build_user_profile(name, **user_info): """构建用户资料,接受姓名和任意其他信息""" profile = {"name": name} profile.update(user_info) # 将 user_info 字典合并到 profile 中 return profile # 调用 user1 = build_user_profile("Frank", age=35, job="Engineer") print(user1) # 输出: {'name': 'Frank', 'age': 35, 'job': 'Engineer'} user2 = build_user_profile("Grace", hobby="Reading", city="Hangzhou") print(user2) # 输出: {'name': 'Grace', 'hobby': 'Reading', 'city': 'Hangzhou'} # 解包 (Unpacking) 的应用 extra_info = {"level": "Senior", "team": "AI"} user3 = build_user_profile("Henry", **extra_info) # ** 将字典解包成 key=value 形式 print(user3) # 输出: {'name': 'Henry', 'level': 'Senior', 'team': 'AI'} -
组合使用 (
*args和**kwargs):
这是最灵活的函数签名,可以接收几乎任何类型的参数组合。def flexible_function(required_param, *args, default_param="default", **kwargs): print(f"必需参数: {required_param}") print(f"额外位置参数: {args}") print(f"默认参数: {default_param}") print(f"额外关键字参数: {kwargs}") # 调用示例 flexible_function("must_have", 1, 2, 3, optional_kw="value", another="test") # 输出: # 必需参数: must_have # 额外位置参数: (1, 2, 3) # 默认参数: default # 额外关键字参数: {'optional_kw': 'value', 'another': 'test'} flexible_function("must_have", 4, 5, default_param="custom", flag=True) # 输出: # 必需参数: must_have # 额外位置参数: (4, 5) # 默认参数: custom # 额外关键字参数: {'flag': True}
3. 作用域 (Scope):变量的领地
理解变量的作用域至关重要,它决定了变量在何处可以被访问。
- 局部作用域 (Local Scope): 在函数内部定义的变量。它只能在该函数内部被访问。
- 全局作用域 (Global Scope): 在函数外部(如模块顶层)定义的变量。它可以在整个模块的任何地方被访问(包括函数内部,但修改时需要特殊处理)。
- 内置作用域 (Built-in Scope): Python自带的内置函数和异常,如
print,len,ValueError等。
LEGB 规则: 当Python查找一个变量名时,会按照以下顺序搜索:
- Local (局部)
- Enclosing (嵌套函数的外层函数作用域)
- Global (全局)
- Built-in (内置)
x = "global x" # 全局变量
def outer_function():
y = "outer y" # 外层函数的局部变量 (对 inner_function 来说是 Enclosing scope)
def inner_function():
z = "inner z" # 局部变量
print(z) # -> "inner z" (Local)
print(y) # -> "outer y" (Enclosing)
print(x) # -> "global x" (Global)
# print(len) # -> <built-in function len> (Built-in)
inner_function()
print(f"Inside outer, y={y}") # -> Inside outer, y=outer y
# print(z) # 错误!z 是 inner_function 的局部变量,在此无法访问
outer_function()
print(f"Global x={x}") # -> Global x=global x
# print(y) # 错误!y 是 outer_function 的局部变量,在此无法访问
-
修改全局变量:
global关键字
如果你想在函数内部修改一个全局变量,必须使用global关键字声明。counter = 0 # 全局计数器 def increment(): global counter # 声明要使用全局的 counter counter += 1 def bad_increment(): # counter += 1 # 错误!Python 会认为你在创建一个新的局部变量 counter, # 但在赋值前就试图读取它(UnboundLocalError) pass print(counter) # -> 0 increment() print(counter) # -> 1 increment() print(counter) # -> 2 -
修改外层变量:
nonlocal关键字
在嵌套函数中,如果你想修改外层函数的变量,需要使用nonlocal关键字。def outer(): count = 0 # 外层函数的变量 def inner(): nonlocal count # 声明要使用外层函数的 count count += 1 return count return inner # 返回 inner 函数本身 counter = outer() # counter 现在是一个闭包 (closure) print(counter()) # -> 1 print(counter()) # -> 2 print(counter()) # -> 3 # 每次调用 counter() 都能记住并修改外层的 count 变量
4. 函数是一等公民 (First-Class Citizens)
在Python中,函数是“一等公民”,这意味着它们可以像其他任何对象(如整数、字符串、列表)一样被对待。
-
将函数赋值给变量:
def say_hello(): print("Hello!") greet = say_hello # 将函数对象赋值给变量 greet greet() # 调用 -> Hello! (等同于 say_hello()) print(greet) # -> <function say_hello at 0x...> -
将函数作为参数传递给另一个函数 (高阶函数):
def apply_operation(func, value): """接收一个函数 func 和一个值 value,对 value 应用 func""" return func(value) def square(x): return x * x def double(x): return x * 2 result1 = apply_operation(square, 4) # -> 16 result2 = apply_operation(double, 4) # -> 8 print(result1, result2) -
将函数作为另一个函数的返回值:
def get_multiplier(factor): """返回一个能将输入乘以 factor 的函数""" def multiplier(number): return number * factor return multiplier double_func = get_multiplier(2) triple_func = get_multiplier(3) print(double_func(5)) # -> 10 print(triple_func(5)) # -> 15 -
将函数存储在数据结构中:
operations = [square, double, lambda x: x + 1] # 列表中存储函数 values = [1, 2, 3] for val in values: print(f"Operations on {val}:") for op in operations: print(f" {op.__name__ if hasattr(op, '__name__') else 'lambda'}({val}) = {op(val)}") # 输出类似: # Operations on 1: # square(1) = 1 # double(1) = 2 # lambda(1) = 2 # ...
5. Lambda 表达式:匿名函数
Lambda表达式是一种创建小型、匿名函数的简洁方式。它适用于简单的、单行的函数逻辑。
- 语法:
lambda arguments: expression - 特点: 只能包含一个表达式,不能有复杂的语句(如
if-elif-else块,但三元运算符if-else可以)。自动返回表达式的结果。
# 等价于之前的 square 函数
square_lambda = lambda x: x * x
print(square_lambda(4)) # -> 16
# 排序时作为 key 参数
students = [("Alice", 85), ("Bob", 90), ("Charlie", 78)]
# 按成绩 (第二个元素) 升序排序
sorted_by_grade = sorted(students, key=lambda student: student[1])
print(sorted_by_grade) # -> [('Charlie', 78), ('Alice', 85), ('Bob', 90)]
# 结合 map 和 filter
numbers = [1, 2, 3, 4, 5]
# 使用 map 将每个数平方
squared = list(map(lambda x: x**2, numbers))
print(squared) # -> [1, 4, 9, 16, 25]
# 使用 filter 筛选出偶数
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # -> [2, 4]
6. 装饰器 (Decorators):为函数“化妆”
装饰器是Python中一个非常强大和优雅的特性。它允许你在不修改原函数代码的情况下,动态地增加其功能(如计时、日志记录、权限检查等)。
- 本质: 装饰器是一个接收函数作为参数,并返回一个新函数的高阶函数。
- 语法糖:
@decorator_name用于简化装饰器的应用。
import time
# 一个简单的计时装饰器
def timer_decorator(func):
"""装饰器:测量并打印函数执行时间"""
def wrapper(*args, **kwargs): # 包装函数,接收原函数的所有参数
start_time = time.time()
result = func(*args, **kwargs) # 调用原函数
end_time = time.time()
print(f"Function '{func.__name__}' took {end_time - start_time:.4f} seconds to execute.")
return result # 返回原函数的结果
return wrapper # 返回包装后的函数
# 使用 @timer_decorator 装饰 slow_function
@timer_decorator
def slow_function(n):
"""一个模拟耗时操作的函数"""
total = 0
for i in range(n):
total += i
return total
# 使用 @timer_decorator 装饰 calculate_square
@timer_decorator
def calculate_square(x):
time.sleep(0.1) # 模拟一点延迟
return x * x
# 调用被装饰的函数
result1 = slow_function(1000000) # 会打印执行时间
result2 = calculate_square(10) # 会打印执行时间
print(f"Result of slow_function: {result1}")
print(f"Result of calculate_square: {result2}")
输出示例:
Function 'slow_function' took 0.0452 seconds to execute.
Result of slow_function: 499999500000
Function 'calculate_square' took 0.1001 seconds to execute.
Result of calculate_square: 100
解析:
@timer_decorator写在slow_function定义之前。- Python解释器会将其转换为:
slow_function = timer_decorator(slow_function)。 timer_decorator函数被调用,传入slow_function作为参数。timer_decorator内部定义了wrapper函数,并返回这个wrapper函数。- 因此,
slow_function这个名字现在指向了wrapper函数。 - 当我们调用
slow_function(1000000)时,实际上是调用了wrapper(1000000)。 wrapper函数负责记录时间、调用真正的slow_function、记录时间并打印,最后返回结果。
第二部分:模块——代码的“文件夹”
随着项目规模的增长,将所有代码塞进一个.py文件里会变得难以管理。模块就是解决这个问题的答案。一个模块就是一个包含Python定义和语句的.py文件。模块的文件名就是模块名(不带.py后缀)。
1. 创建和导入模块
-
创建模块:
假设我们创建一个名为math_utils.py的文件,其中包含一些数学相关的函数。# math_utils.py """一个包含实用数学函数的模块。""" PI = 3.141592653589793 def add(a, b): """返回 a 和 b 的和。""" return a + b def multiply(a, b): """返回 a 和 b 的积。""" return a * b def circle_area(radius): """根据半径计算圆的面积。""" return PI * radius ** 2 def _private_helper(x): # 以下划线开头,约定为“私有”函数 """一个仅供模块内部使用的辅助函数。""" return x * 2 -
导入模块:
在另一个Python脚本(例如main.py)中,我们可以导入并使用math_utils模块。# main.py # 方法1: 导入整个模块 import math_utils print(math_utils.PI) # 访问模块中的变量 print(math_utils.add(5, 3)) # 调用模块中的函数 area = math_utils.circle_area(4) print(f"Area: {area}") # 方法2: 从模块中导入特定的名称 from math_utils import add, multiply, PI print(add(10, 20)) # 直接使用,无需模块前缀 print(multiply(3, 7)) print(PI) # 方法3: 从模块中导入所有名称 (谨慎使用!) from math_utils import * # 现在可以直接使用 add, multiply, PI, circle_area # 但是也导入了 _private_helper,这通常不是好习惯,可能导致命名冲突 # 方法4: 导入并重命名 (as) import math_utils as mu print(mu.multiply(2, 8)) # 使用别名 mu from math_utils import circle_area as calc_area print(calc_area(5))
2. __name__ 和 __main__:模块的“身份证”
每个Python模块都有一个内置的属性 __name__。它的值取决于模块是如何被使用的。
- 当一个模块被直接运行时(例如
python math_utils.py),它的__name__属性会被设置为"__main__"。 - 当一个模块被导入到另一个模块中时,它的
__name__属性就是它自己的文件名(如"math_utils")。
这个特性被广泛用于编写既可以作为脚本独立运行,又可以作为模块被导入的代码。
# 在 math_utils.py 文件末尾添加
if __name__ == "__main__":
# 这段代码只有在直接运行 math_utils.py 时才会执行
# 通常用于测试模块中的函数
print("Running tests for math_utils module:")
print(f"add(2, 3) = {add(2, 3)}")
print(f"circle_area(1) = {circle_area(1):.4f}")
print("All tests passed!")
- 效果:
- 如果你执行
python math_utils.py,你会看到测试输出。 - 如果你在
main.py中import math_utils,则不会看到测试输出,只导入了函数和变量。
- 如果你执行
3. 包 (Packages):模块的“文件夹”
当你的项目变得更大,拥有几十甚至上百个模块时,你需要一个更高层次的组织结构——包 (Package)。一个包就是一个包含多个模块的目录,并且这个目录下必须有一个特殊的文件 __init__.py。
-
创建包:
my_project/ ├── __init__.py # 标记 my_project 是一个包 ├── calculator.py # 包内的一个模块 └── utilities/ # 子包 ├── __init__.py # 标记 utilities 是一个子包 ├── string_utils.py └── file_utils.py -
__init__.py的作用:- 让Python将该目录识别为一个包。
- 可以包含包初始化代码(当包被首次导入时执行)。
- 可以控制
from package import *时导入哪些模块(通过__all__变量)。
# my_project/__init__.py # 可以为空,也可以包含初始化代码 print("Initializing my_project package...") # 控制 from my_project import * 的行为 __all__ = ['calculator'] # 只有 calculator 模块会被 * 导入 -
从包中导入:
# main.py (假设在 my_project 目录外) # 导入包内的模块 import my_project.calculator result = my_project.calculator.add(1, 2) # 从子包导入 from my_project.utilities import string_utils cleaned_text = string_utils.clean_whitespace(" hello world ") # 从子包的模块导入特定函数 from my_project.utilities.file_utils import read_file, write_file # 使用相对导入 (通常在包内部的模块间使用) # 在 my_project/calculator.py 中 # from .utilities.string_utils import clean_whitespace # . 表示当前包 # from ..other_package import helper_module # .. 表示上一级
4. Python路径 (sys.path) 与模块搜索
当你导入一个模块时,Python解释器会按照一个特定的顺序去查找它。这个顺序由 sys.path 这个列表决定。
import sys
print(sys.path)
sys.path 通常包含:
- 脚本所在的目录(或当前工作目录)。
- PYTHONPATH 环境变量所包含的目录。
- Python标准库的安装目录。
- 第三方库的安装目录(如 site-packages)。
如果你的自定义模块不在这些路径下,你需要手动将其添加到 sys.path 中,或者使用虚拟环境和包管理工具(如 pip)来正确安装。
5. 标准库模块简介
Python的强大之处在于其丰富的“电池已包含”(batteries included)的标准库。这里列举几个常用模块:
os: 提供与操作系统交互的函数(文件、目录、环境变量)。sys: 提供对Python解释器和系统相关的访问。math: 数学运算(三角函数、对数、常数等)。datetime: 日期和时间处理。json: JSON数据的编码和解码。random: 生成随机数。collections: 提供额外的数据结构(如namedtuple,deque,Counter)。itertools: 提供用于创建高效迭代器的函数。functools: 提供高阶函数和可调用对象的操作(如lru_cache,partial)。
import datetime
now = datetime.datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))
import json
data = {"name": "Alice", "age": 30}
json_string = json.dumps(data)
print(json_string)
parsed_data = json.loads(json_string)
print(parsed_data["name"])
综合案例:构建一个简易的“任务管理器”
让我们结合函数和模块,构建一个简单的命令行任务管理器。
项目结构:
task_manager/
├── __init__.py
├── task_manager.py # 主程序逻辑
├── storage.py # 数据持久化模块
└── utils.py # 工具函数模块
# storage.py
"""处理任务数据的保存和加载。"""
import json
import os
TASKS_FILE = "tasks.json"
def load_tasks():
"""从文件加载任务列表。如果文件不存在,则返回空列表。"""
if os.path.exists(TASKS_FILE):
try:
with open(TASKS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"加载任务失败: {e}")
return []
return []
def save_tasks(tasks):
"""将任务列表保存到文件。"""
try:
with open(TASKS_FILE, 'w', encoding='utf-8') as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
print("任务已保存。")
except IOError as e:
print(f"保存任务失败: {e}")
# utils.py
"""通用工具函数。"""
from datetime import datetime
def get_current_time_str():
"""获取格式化的当前时间字符串。"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def validate_task_input(description):
"""验证任务描述是否有效。"""
if not description or description.strip() == "":
return False, "任务描述不能为空。"
if len(description.strip()) > 100:
return False, "任务描述不能超过100个字符。"
return True, ""
# task_manager.py
"""任务管理器主程序。"""
from storage import load_tasks, save_tasks
from utils import get_current_time_str, validate_task_input
def display_menu():
"""显示菜单选项。"""
print("\n=== 任务管理器 ===")
print("1. 查看所有任务")
print("2. 添加新任务")
print("3. 标记任务完成")
print("4. 删除任务")
print("5. 退出")
print("=" * 20)
def show_tasks(tasks):
"""展示所有任务。"""
if not tasks:
print("暂无任务。")
return
print("\n当前任务列表:")
for idx, task in enumerate(tasks, start=1):
status = "✓" if task['completed'] else "○"
print(f"{idx}. [{status}] {task['description']} "
f"(创建于: {task['created_at']})")
def add_task(tasks):
"""添加一个新任务。"""
description = input("请输入任务描述: ").strip()
is_valid, message = validate_task_input(description)
if not is_valid:
print(f"输入无效: {message}")
return
new_task = {
"description": description,
"completed": False,
"created_at": get_current_time_str()
}
tasks.append(new_task)
print("任务添加成功!")
def complete_task(tasks):
"""标记一个任务为完成。"""
show_tasks(tasks)
if not tasks:
return
try:
choice = int(input("请输入要完成的任务编号: ")) - 1
if 0 <= choice < len(tasks):
if tasks[choice]['completed']:
print("该任务已完成。")
else:
tasks[choice]['completed'] = True
tasks[choice]['completed_at'] = get_current_time_str()
print("任务已完成!")
else:
print("无效的编号!")
except ValueError:
print("请输入一个有效的数字!")
def delete_task(tasks):
"""删除一个任务。"""
show_tasks(tasks)
if not tasks:
return
try:
choice = int(input("请输入要删除的任务编号: ")) - 1
if 0 <= choice < len(tasks):
removed_task = tasks.pop(choice)
print(f"已删除任务: {removed_task['description']}")
else:
print("无效的编号!")
except ValueError:
print("请输入一个有效的数字!")
def main():
"""主函数。"""
tasks = load_tasks() # 启动时加载任务
while True:
display_menu()
choice = input("请选择 (1-5): ").strip()
if choice == '1':
show_tasks(tasks)
elif choice == '2':
add_task(tasks)
elif choice == '3':
complete_task(tasks)
elif choice == '4':
delete_task(tasks)
elif choice == '5':
save_tasks(tasks) # 退出前保存任务
print("再见!")
break
else:
print("无效选择,请重新输入。")
if __name__ == "__main__":
main()
- 函数是将代码逻辑封装起来的最小单元,它让代码可复用、可测试。
- 模块是将相关函数、类和变量组织在一个文件中的单元,它让代码可组织、可分发。
- 包是将多个模块组织在目录结构中的单元,它让大型项目结构清晰。
- 熟练掌握参数、作用域、装饰器、
*args/**kwargs等高级特性,能让你写出更灵活、更强大的函数。 - 理解
__name__ == "__main__"模式,是编写健壮模块的基础。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)