从零到接单 05:函数与模块——把代码组织成可复用的零件
系列目录:本文是「从零到接单:Python 自动化与爬虫实战」系列的第 5 篇。前面的代码都是从头写到尾,但真实项目可能有几百上千行代码,全靠顺序执行会乱成一团。这篇我们学习函数和模块——把大任务拆成小零件,这是写大程序的基本功。
一、什么是函数?为什么要用?
你已经用过很多内置函数了:print()、input()、len()、type()……它们帮你完成了特定任务。
现在你要学会自己写函数。函数的作用:
- 避免重复:同样的逻辑不用写多次
- 让代码清晰:给一段代码起个有意义的名字
- 方便维护:改一处,所有调用都生效
- 别人也能用:函数可以分享给其他人
二、定义和调用函数
基本语法
def 函数名(参数1, 参数2, ...):
"""文档字符串:说明这个函数做什么(可选但推荐)"""
函数体
return 返回值 # 可选
第一个函数
def greet(name):
"""向指定用户打招呼"""
return f"你好,{name}!欢迎回来。"
# 调用
result = greet("小明")
print(result) # 你好,小明!欢迎回来。
print(greet("小红")) # 你好,小红!欢迎回来。
无返回值的函数
def print_separator(char="-", length=40):
"""打印分隔线"""
print(char * length)
print_separator() # ----------------------------------------
print_separator("=", 20) # ====================
print("程序结束")
print_separator("~") # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
实际返回了 None(Python 的空值)。
三、参数的类型
位置参数
def introduce(name, age, city):
print(f"我叫{name},{age}岁,来自{city}")
introduce("小明", 25, "北京") # 按位置传参
默认参数(最常用!)
def introduce(name, age, city="北京"):
print(f"我叫{name},{age}岁,来自{city}")
introduce("小明", 25) # 我叫小明,25岁,来自北京
introduce("小红", 22, "上海") # 我叫小红,22岁,来自上海
⚠️ 有默认值的参数必须放在没有默认值的参数后面。
关键字参数
def order_product(name, price, quantity=1):
total = price * quantity
print(f"商品:{name},单价:{price},数量:{quantity},总价:{total}")
# 用关键字可以不按顺序
order_product(quantity=3, name="Python书", price=59)
# 商品:Python书,单价:59,数量:3,总价:177
不定长参数:*args 和 **kwargs
# *args:接收任意数量的位置参数(打包成元组)
def sum_all(*numbers):
"""求和任意数量的数"""
return sum(numbers)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# **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: 北京
四、返回值
返回单个值
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
返回多个值(实际是返回元组)
def calculate(a, b):
return a + b, a - b, a * b, a / b
add_result, sub_result, mul_result, div_result = calculate(10, 5)
print(f"加:{add_result},减:{sub_result},乘:{mul_result},除:{div_result}")
# 加:15,减:5,乘:50,除:2.0
提前返回
def divide(a, b):
if b == 0:
print("错误:除数不能为 0")
return # 提前结束函数
return a / b
print(divide(10, 0)) # None
print(divide(10, 2)) # 5.0
五、作用域:变量在哪里可见
x = "全局变量"
def test():
y = "局部变量"
print(x) # 可以访问全局变量
print(y) # 可以访问局部变量
test()
print(x) # ✅ 可以
# print(y) # ❌ NameError: y 不存在于全局作用域
global 关键字(谨慎使用)
count = 0
def increment():
global count # 声明要修改全局变量
count += 1
increment()
print(count) # 1
更好的做法是用返回值,避免全局变量:
def increment(count):
return count + 1
count = 0
count = increment(count)
六、lambda 表达式:匿名函数
一行就能搞定的简单函数,不需要用 def 正式定义:
# 普通写法
def add(a, b):
return a + b
# lambda 写法
add = lambda a, b: a + b
print(add(3, 5)) # 8
# lambda 常见用法:作为 sorted/key 参数
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 78},
]
# 按分数排序
sorted_students = sorted(students, key=lambda s: s["score"], reverse=True)
for s in sorted_students:
print(f"{s['name']}: {s['score']}")
# 李四: 92
# 张三: 85
# 王五: 78
七、模块(Module):把函数组织到不同文件
当代码越来越多,需要分文件管理。
创建模块
新建 utils.py:
# utils.py
def read_csv(filepath):
"""读取 CSV 文件"""
with open(filepath, "r", encoding="utf-8") as f:
lines = f.readlines()
headers = lines[0].strip().split(",")
data = []
for line in lines[1:]:
values = line.strip().split(",")
data.append(dict(zip(headers, values)))
return data
def write_csv(filepath, data):
"""写入 CSV 文件"""
if not data:
return
headers = list(data[0].keys())
with open(filepath, "w", encoding="utf-8") as f:
f.write(",".join(headers) + "\n")
for row in data:
f.write(",".join(str(row[h]) for h in headers) + "\n")
VERSION = "1.0.0"
AUTHOR = "小明"
导入模块
# main.py
# 方式一:import 模块名
import utils
data = utils.read_csv("data.csv")
# 方式二:from 模块 import 特定内容
from utils import read_csv, write_csv
data = read_csv("data.csv")
# 方式三:from 模块 import *(不推荐,会污染命名空间)
# from utils import *
# 方式四:起别名
import utils as u
data = u.read_csv("data.csv")
常用标准库模块预览
import os # 操作系统接口(文件路径、环境变量)
import sys # 系统参数(命令行参数)
import json # JSON 处理
import csv # CSV 文件处理
import re # 正则表达式
import datetime # 日期时间
import random # 随机数
import time # 时间相关
import math # 数学函数
if name == "main"
# utils.py 底部加这个
if __name__ == "__main__":
# 这段代码只在直接运行 utils.py 时执行
# 被其他文件 import 时不会执行
print("测试 utils 模块...")
test_data = [{"name": "测试", "age": "25"}]
write_csv("test.csv", test_data)
print(read_csv("test.csv"))
八、实战练习:爬虫工具模块
提前写一个后面会用到的小工具模块:
# scraper_utils.py —— 爬虫常用工具函数
import time
import random
def clean_text(text):
"""清洗文本:去掉首尾空格和多余空白"""
if not text:
return ""
return " ".join(text.split())
def extract_number(text):
"""从文本中提取数字"""
import re
numbers = re.findall(r"\d+\.?\d*", text)
return float(numbers[0]) if numbers else None
def safe_get(data, *keys, default=None):
"""安全地从嵌套字典中取值"""
for key in keys:
if isinstance(data, dict):
data = data.get(key, default)
else:
return default
return data
def random_delay(min_seconds=0.5, max_seconds=2.0):
"""随机延时,避免被封(爬虫必备)"""
delay = random.uniform(min_seconds, max_seconds)
time.sleep(delay)
def retry(max_attempts=3, delay=1):
"""装饰器:失败自动重试"""
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise e
print(f"第 {attempt + 1} 次失败,{delay} 秒后重试...")
time.sleep(delay)
return wrapper
return decorator
if __name__ == "__main__":
# 测试
print(clean_text(" 你好 世界 ")) # 你好 世界
print(extract_number("价格:99.9元")) # 99.9
data = {"a": {"b": {"c": "hello"}}}
print(safe_get(data, "a", "b", "c")) # hello
print(safe_get(data, "a", "x", default="无")) # 无
print("所有工具函数测试通过 ✅")
总结
| 概念 | 写法 | 要点 |
|------|------|------|
| 定义函数 | def func(params): | 名字清晰、参数合理 |
| 返回值 | return value | 可返回多个值(元组) |
| 默认参数 | def f(a, b=0) | 有默认值的往后放 |
| *args | def f(*args) | 任意数量位置参数 |
| **kwargs | def f(**kwargs) | 任意数量关键字参数 |
| lambda | lambda x: x+1 | 一行函数 |
| 导入模块 | import module | 用 . 访问 |
| 条件执行 | if __name__ == "__main__" | 只在直接运行时执行 |
下一篇,我们将学习文件读写与异常处理——程序终于可以和外部世界交互了!
练习:写一个
password_tool.py模块,包含以下函数:
generate_password(length=12)—— 生成随机密码(含大小写字母+数字+符号)check_strength(password)—— 检查密码强度(弱/中/强)- 在
if __name__ == "__main__"中测试这两个函数
Comments
Sign in to leave a comment.