主题
📦 函数与模块
数据有了,流程有了,但代码一长就像一锅粥。函数和模块就是把粥分碗装——各司其职,互不打扰。
函数定义与调用
用 def 关键字定义函数:
python
def greet(name):
"""向某人打招呼(这是文档字符串)"""
return f"你好,{name}!"
# 调用
print(greet("小雅")) # 你好,小雅!文档字符串
函数体内第一行用 """...""" 写的字符串就是文档,之后用 help(greet) 或 greet.__doc__ 可以查看。养成写文档的好习惯。
参数类型
Python 的参数系统非常灵活:
python
# 1. 位置参数 — 按顺序传
def add(a, b):
return a + b
print(add(3, 5)) # 8
# 2. 默认参数 — 不传就用默认值
def greet(name, greeting="你好"):
return f"{greeting},{name}!"
print(greet("小雅")) # 你好,小雅!
print(greet("Tom", "Hello")) # Hello,Tom!
# 3. 关键字参数 — 按名字传,顺序无所谓
print(greet(greeting="Hi", name="Jack")) # Hi,Jack!
# 4. 可变参数 *args — 接收任意多个位置参数(打包成元组)
def sum_all(*nums):
return sum(nums)
print(sum_all(1, 2, 3, 4, 5)) # 15
# 5. 关键字可变参数 **kwargs — 接收任意多个关键字参数(打包成字典)
def print_info(**info):
for key, value in info.items():
print(f"{key}: {value}")
print_info(name="小雅", age=25, city="杭州")
# name: 小雅
# age: 25
# city: 杭州组合使用时的顺序:**位置参数 → *args → 关键字参数 → kwargs。
返回值
python
# 无 return → 返回 None
def do_nothing():
pass
print(do_nothing()) # None
# 返回多个值 → 其实是返回元组
def get_min_max(nums):
return min(nums), max(nums)
a, b = get_min_max([3, 1, 4, 1, 5]) # a=1, b=5作用域 — LEGB 规则
Python 找变量按 LEGB 顺序:Local → Enclosing → Global → Built-in。
python
x = "global" # 全局变量
def outer():
x = "enclosing" # 闭包变量
def inner():
x = "local" # 局部变量
print(x) # 先找局部 → "local"
inner()
outer()global:在函数内声明要修改全局变量nonlocal:在内层函数声明要修改外层(闭包)变量
python
count = 0
def increment():
global count
count += 1
def make_counter():
n = 0
def counter():
nonlocal n
n += 1
return n
return counterlambda — 匿名函数
短小的一次性函数,一条表达式搞定:
python
# 传统写法
def square(x):
return x**2
# lambda 写法
square = lambda x: x**2
# 常用于排序、过滤、map
students = [("小明", 85), ("小红", 92), ("小刚", 78)]
students.sort(key=lambda s: s[1]) # 按分数排序
nums = [1, 2, 3, 4, 5]
evens = list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
doubled = list(map(lambda x: x * 2, nums)) # [2, 4, 6, 8, 10]模块 — 代码分文件
创建模块
一个 .py 文件就是一个模块。创建 utils.py:
python
# utils.py
def compress_file(path):
"""压缩文件"""
import zipfile
with zipfile.ZipFile(path + ".zip", "w") as zf:
zf.write(path)
print(f"{path} 已压缩")
def send_email(to, subject, body):
"""发送邮件(示例)"""
print(f"→ 发送邮件到 {to}")
print(f" 主题: {subject}")导入模块
python
import utils # 导入整个模块
utils.compress_file("data.txt")
from utils import send_email # 导入特定函数
send_email("test@qq.com", "Hello", "正文")
from utils import compress_file as cf # 起别名
cf("report.csv")
from utils import * # ❌ 不推荐:导入所有,污染命名空间__name__ == "__main__"
python
# utils.py
if __name__ == "__main__":
print("直接运行 utils.py 时执行这里")
# 可以写测试代码直接 python utils.py 时会执行;被 import 时不会执行。
实战:工具函数库
python
# toolkit.py — 可复用的工具函数
import logging
from datetime import datetime
def setup_logger(name="app"):
"""快速创建日志器"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
return logging.getLogger(name)
def timeit(func):
"""装饰器:打印函数执行时间"""
def wrapper(*args, **kwargs):
start = datetime.now()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时: {datetime.now() - start}")
return result
return wrapper
@timeit
def slow_task():
total = sum(range(10_000_000))
return total
# 直接运行测试
if __name__ == "__main__":
logger = setup_logger()
logger.info("工具库加载成功")
slow_task()常见坑
坑1:默认参数用可变对象
默认参数在函数定义时只计算一次,共享同一个对象:
python
# ❌ 错误写法
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 意外!列表被共享了
# ✅ 正确写法
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items坑2:循环导入
A 导 B,B 导 A → 死循环。解法:把共同的依赖抽到第三个模块,或把 import 放到函数内部。
坑3:from module import *
会导入模块中所有公开名字,容易覆盖已有变量。永远用显式导入。
练习题
- 写一个
calculator(a, b, op)函数,op可以是"+"、"-"、"*"、"/",默认"+" - 用
*args写一个函数,返回所有参数的乘积 - 写一个装饰器,把函数返回值翻倍
- 把练习题 1~3 放到一个
myutils.py模块里,在另一个文件中导入使用 - 用
__name__ == "__main__"给你的模块写一段自测代码
📦 函数与模块让代码井井有条。下一步学 面向对象编程,用类的思维建模现实世界。
🎯 本章要点
- 默认参数在函数定义时计算一次。
- *args 收集所有位置参数到一个元组,**kwargs 收集所有关键字参数到字典。
- lambda 适合一行搞定的简单表达式
加载练习题中...