6-python模块
约 17210 字大约 57 分钟
2026-05-15
使用工具箱:Python模块
在前面的学习中,已经讲授了Python的基本语法、条件判断、循环结构和函数等知识,能够编写一些简单的程序来解决实际问题。但是,当我们需要处理更复杂的任务时,比如生成随机数、进行数学计算、处理日期时间等,如果每次都要从头编写代码,不仅费时费力,还容易出错。Python的模块机制就是为了解决这个问题而设计的,它让我们可以方便地使用别人已经写好、测试过的代码,就像使用一个现成的工具箱一样。
本章将带领大家认识Python模块的概念,学习如何导入和使用模块,并详细介绍几个常用的内置模块:random(随机数)、math(数学计算)、datetime(日期时间)、time(时间处理)、calendar(日历)、os(操作系统)、shutil(高级文件操作)和re(正则表达式)。通过本章的学习,你将能够灵活运用这些"工具箱",大大提高编程效率。
【学习目标】
知识目标:理解模块概念与分类;掌握import导入语法;熟悉8大常用内置模块功能;掌握自定义模块与__name__机制。
能力目标:熟练导入模块;能运用random、math、datetime/time、os/shutil、calendar、re解决随机化、计算、时间处理、文件管理与文本匹配等实际问题;能综合多模块与数据结构开发自定义模块。
素养目标:养成模块化编程与代码复用习惯;践行精益求精的工匠精神;厚植科技报国情怀与民族自豪感。
什么是模块
在日常生活中,我们经常会使用各种工具箱。比如,一个电工的工具箱里有螺丝刀、钳子、电笔等工具;一个医生的急救箱里有绷带、消毒液、听诊器等物品。这些工具箱把相关的工具整理在一起,方便携带和使用。Python中的模块就像这样的工具箱,它把相关的函数、类和变量组织在一起,方便我们调用。
模块就是工具箱
模块(Module)是Python中组织代码的基本单位。简单来说,一个模块就是一个包含Python代码的文件,文件名就是模块名加上.py后缀。模块中可以包含函数、类、变量以及可执行的语句。当我们需要使用模块中的功能时,只需要导入这个模块,就可以调用其中定义的函数和使用其中的变量。
打个比方,模块就像是一个专门的工具箱。比如,random模块就像一个"随机数工具箱",里面装满了各种生成随机数的工具(函数);math模块就像一个"数学计算工具箱",里面有计算平方根、正弦、余弦等数学运算的工具。当我们需要生成随机数时,不需要自己写复杂的算法,只需要打开random这个工具箱,拿出相应的工具使用就可以了。
Python中的模块分为三类:第一类是内置模块,这些模块是Python自带的,安装Python后就可以直接使用,比如random、math、datetime等;第二类是第三方模块,这些模块由其他开发者编写并共享,需要单独安装后才能使用,比如用于数据处理的pandas、用于绘图的matplotlib等;第三类是自定义模块,我们可以把自己编写的代码保存为.py文件,作为模块在其他程序中使用。本章主要介绍Python的内置模块和自定义模块。
为什么要用模块
使用模块有三大好处:省时间、更可靠、功能多。
省时间
使用模块最大的好处就是节省开发时间。假设你需要编写一个程序来计算圆的面积,如果不用math模块,你需要自己定义圆周率π的值,而且精度可能不够高。使用math模块,只需要一行代码math.pi就能获得高精度的圆周率。再比如,要生成一个随机数,如果不用random模块,你需要自己实现复杂的随机算法,而使用random模块,只需要调用random.random()就能得到一个随机数。这些现成的模块让我们可以把精力集中在解决实际问题上,而不是重复造轮子。
更可靠
Python的内置模块和知名的第三方模块都经过了严格的测试和大量用户的验证,代码质量很高,bug很少。这些模块中的函数通常考虑了各种边界情况和异常处理,比我们自己临时编写的代码更加健壮可靠。例如,math模块中的数学函数都经过精确计算,能够处理各种特殊情况;datetime模块能够正确处理闰年、时区等复杂问题。使用这些经过验证的模块,可以大大减少程序出错的可能性。
功能多
Python拥有丰富的模块生态系统,几乎涵盖了所有常见的编程需求。无论是数学计算、字符串处理、文件操作、网络通信,还是图形界面、数据库访问、机器学习,都有相应的模块可以使用。这些模块提供了大量的函数和类,功能非常强大。例如,random模块不仅能生成随机数,还能实现随机抽样、打乱列表顺序等功能;os模块可以让我们在程序中操作文件和目录。掌握这些模块的使用,能够大大扩展我们的编程能力。
导入模块
要使用模块中的功能,首先需要导入模块。Python提供了几种不同的导入方式,每种方式都有其特点和适用场景。下面我们详细介绍这些导入方法。
使用import语句导入整个模块
最常用的导入方式是使用import语句导入整个模块。语法格式为:import 模块名。导入后,使用模块中的函数时需要加上模块名作为前缀,格式为:模块名.函数名()。这种方式的优点是代码清晰,能够明确知道函数来自哪个模块,避免命名冲突。
代码 6-1:使用import导入模块
# 导入random模块
import random
# 使用random模块中的random()函数生成随机数
num = random.random()
print(num) # 输出一个0到1之间的随机小数
# 使用randint()函数生成指定范围的随机整数
n = random.randint(1, 100)
print(n) # 输出1到100之间的随机整数
在上面的例子中,我们首先使用import random导入了random模块,然后通过random.random()和random.randint()调用了模块中的函数。这种写法清晰地表明了这些函数来自random模块。
使用from...import语句导入特定函数
如果只需要使用模块中的某几个函数,可以使用from...import语句只导入需要的函数。语法格式为:from 模块名 import 函数名1, 函数名2, ...。导入后,可以直接使用函数名调用,不需要加模块名前缀。
代码 6-2:使用from...import导入特定函数
# 从random模块中只导入randint和choice函数
from random import randint, choice
# 直接使用函数名调用,不需要模块名前缀
n = randint(1, 10)
print(n) # 输出1到10之间的随机整数
fruits = ["苹果", "香蕉", "橙子"]
f = choice(fruits)
print(f) # 随机输出列表中的一个水果
这种导入方式的优点是代码简洁,不需要反复写模块名。缺点是如果导入的函数名与程序中已有的变量或函数名相同,会产生命名冲突。因此,建议在确定不会产生命名冲突的情况下使用这种方式。
使用from...import *导入所有函数
还可以使用from 模块名 import *来导入模块中的所有函数和变量。这样导入后,可以直接使用模块中的所有内容,不需要加模块名前缀。但是这种方式不推荐使用,因为可能会导致命名冲突,而且代码可读性较差,不容易知道某个函数来自哪个模块。
代码 6-3:使用from...import *导入所有内容
# 导入math模块中的所有内容(不推荐)
from math import *
# 直接使用函数名
print(pi) # 输出圆周率
print(sqrt(16)) # 输出4.0(16的平方根)
使用as给模块或函数起别名
有时候模块名或函数名比较长,为了简化代码,可以使用as关键字给它们起一个简短的别名。这在实际开发中非常常见,特别是对于一些常用的模块,大家往往有约定俗成的简写方式。
代码 6-4:使用as起别名
# 给模块起别名
import random as r
# 使用别名调用函数
num = r.random()
print(num)
# 给函数起别名
from random import randint as ri
# 使用别名调用函数
n = ri(1, 100)
print(n)
在实际开发中,给模块起别名是一种常见的做法。例如,numpy模块通常简写为np,pandas模块通常简写为pd,这些已经成为程序员编码的惯例。
常用内置模块
Python提供了丰富的内置模块,这些模块无需安装,导入后即可使用。本节将详细介绍八个常用的内置模块:random(随机数)、math(数学计算)、datetime(日期时间)、time(时间处理)、calendar(日历)、os(操作系统)、shutil(高级文件操作)和re(正则表达式)。每个模块都像是一个专业的工具箱,提供了特定领域的实用功能。
random模块
random模块是Python中用于生成随机数的内置模块。在实际应用中,随机数有着广泛的用途,比如开发猜数字游戏、模拟掷骰子、随机抽取幸运观众、生成验证码等。random模块提供了多种生成随机数的函数,可以满足不同的需求。
random()函数------生成0到1之间的随机小数
random()函数是random模块中最基础的函数,用于生成一个0到1之间的随机小数(包含0,不包含1)。每次调用都会返回一个不同的随机值。
代码 6-5:使用random()生成随机小数
import random
# 生成随机小数
print(random.random()) # 例如:0.7153492876214012
print(random.random()) # 例如:0.3894571628394726
randint()函数------生成指定范围的随机整数
randint(a, b)函数用于生成一个a到b之间的随机整数(包含a和b)。这是最常用的随机数生成函数之一,特别适合用于需要整数随机数的场景。
代码 6-6:使用randint()生成随机整数
import random
# 生成1到6之间的随机整数(模拟掷骰子)
dice = random.randint(1, 6)
print(f"掷出的点数是:{dice}")
# 生成0到100之间的随机整数(模拟考试分数)
score = random.randint(0, 100)
print(f"随机分数:{score}")
randrange()函数------生成指定步长的随机整数
randrange(start, stop, step)函数类似于range()函数,用于在指定范围内(包含start,不包含stop)按指定步长生成随机整数。例如,要生成一个随机的偶数,可以设置步长为2。
代码 6-7:使用randrange()生成指定步长的随机数
import random
# 生成0到100之间的随机偶数
even = random.randrange(0, 101, 2)
print(f"随机偶数:{even}")
# 生成1到10之间的随机奇数
odd = random.randrange(1, 11, 2)
print(f"随机奇数:{odd}")
choice()函数------从序列中随机选择一个元素
choice()函数可以从一个非空序列(如列表、元组、字符串)中随机选择一个元素。这在需要从多个选项中随机抽取一个的场景中非常有用。
代码 6-8:使用choice()随机选择元素
import random
# 从列表中随机选择一个水果
fruits = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"]
selected = random.choice(fruits)
print(f"今天吃:{selected}")
# 从字符串中随机选择一个字符
chars = "ABCDEFGH"
char = random.choice(chars)
print(f"随机字符:{char}")
sample()函数------从序列中随机抽取多个元素
sample(population, k)函数可以从序列中随机抽取k个不重复的元素,返回一个列表。这在需要随机抽样或抽奖的场景中非常实用。
代码 6-9:使用sample()随机抽取多个元素
import random
# 从数字列表中随机抽取3个号码(彩票选号)
numbers = list(range(1, 34)) # 1到33的数字
lottery = random.sample(numbers, 6)
print(f"彩票号码:{sorted(lottery)}")
# 从班级名单中随机抽取3名幸运同学
students = ["张三", "李四", "王五", "赵六", "钱七", "孙八"]
lucky = random.sample(students, 3)
print(f"幸运同学:{lucky}")
shuffle()函数------打乱序列顺序
shuffle()函数可以将序列中的元素随机打乱顺序。注意,这个函数会直接修改原序列,不返回新序列。这在需要随机排列的场景中很有用,比如洗牌、随机出题顺序等。
代码 6-10:使用shuffle()打乱序列顺序
import random
# 洗牌示例
cards = ["红桃A", "红桃K", "黑桃A", "黑桃K", "方块A"]
print(f"洗牌前:{cards}")
random.shuffle(cards)
print(f"洗牌后:{cards}")
【例题 6-1】猜数字游戏
编写一个猜数字游戏:程序随机生成一个1到100之间的整数,玩家输入猜测的数字,程序提示"大了"或"小了",直到猜对为止,最后显示猜测次数。
代码 6-11:猜数字游戏完整代码
import random
# 生成随机答案
answer = random.randint(1, 100)
count = 0
print("=== 猜数字游戏 ===")
print("我已经想好了一个1到100之间的整数,请猜猜是多少?")
while True:
guess = int(input("请输入你的猜测:"))
count += 1
if guess < answer:
print("小了,再大一点!")
elif guess > answer:
print("大了,再小一点!")
else:
print(f"恭喜你,猜对了!答案就是{answer}")
print(f"你一共猜了{count}次")
break
这个例子综合运用了random.randint()生成随机数、while循环持续接收输入、条件判断给出提示等知识,是一个很好的综合练习。运行程序后,玩家可以不断输入猜测,程序会给出相应的提示,直到猜对为止。
math模块
math模块提供了丰富的数学函数和常量,包括三角函数、对数函数、幂函数、取整函数等。对于需要进行数学计算的程序,math模块是必不可少的工具。
数学常量
math模块提供了两个常用的数学常量:math.pi表示圆周率π(约3.14159...),math.e表示自然对数的底e(约2.71828...)。这些常量具有很高的精度,在科学计算中非常有用。
代码 6-12:math模块的数学常量
import math
print(f"圆周率π = {math.pi}")
print(f"自然常数e = {math.e}")
幂函数和平方根
math.pow(x, y)用于计算x的y次幂,math.sqrt(x)用于计算x的平方根。此外,Python内置的**运算符也可以进行幂运算。
代码 6-13:幂函数和平方根
import math
# 幂运算
print(math.pow(2, 3)) # 输出:8.0(2的3次方)
print(math.pow(4, 0.5)) # 输出:2.0(4的0.5次方,即平方根)
# 平方根
print(math.sqrt(16)) # 输出:4.0
print(math.sqrt(2)) # 输出:1.4142135623730951
# 使用**运算符
print(2 ** 3) # 输出:8
print(16 ** 0.5) # 输出:4.0
取整函数
math模块提供了多种取整函数:math.ceil(x)向上取整(返回大于等于x的最小整数),math.floor(x)向下取整(返回小于等于x的最大整数),math.trunc(x)截断取整(直接去掉小数部分)。
代码 6-14:取整函数
import math
num = 3.7
print(f"原始值:{num}")
print(f"向上取整:{math.ceil(num)}") # 输出:4
print(f"向下取整:{math.floor(num)}") # 输出:3
print(f"截断取整:{math.trunc(num)}") # 输出:3
# 对于负数
num2 = -3.7
print(f"\n原始值:{num2}")
print(f"向上取整:{math.ceil(num2)}") # 输出:-3
print(f"向下取整:{math.floor(num2)}") # 输出:-4
print(f"截断取整:{math.trunc(num2)}") # 输出:-3
绝对值和符号函数
math.fabs(x)返回x的绝对值(浮点数),math.copysign(x, y)返回x的绝对值带上y的符号。
代码 6-15:绝对值和符号函数
import math
print(math.fabs(-5)) # 输出:5.0
print(math.fabs(-3.14)) # 输出:3.14
print(math.copysign(5, -1)) # 输出:-5.0
print(math.copysign(-3, 1)) # 输出:3.0
三角函数
math模块提供了完整的三角函数,包括sin()、cos()、tan()及其反函数asin()、acos()、atan()。注意,这些函数的参数是弧度而非角度,如果需要使用角度,需要进行转换。
代码 6-16:三角函数
import math
# 角度转弧度:弧度 = 角度 * π / 180
angle = 45 # 45度
radian = math.radians(angle)
print(f"{angle}度 = {radian}弧度")
print(f"sin({angle}°) = {math.sin(radian):.4f}")
print(f"cos({angle}°) = {math.cos(radian):.4f}")
print(f"tan({angle}°) = {math.tan(radian):.4f}")
对数函数
math.log(x)计算x的自然对数(以e为底),math.log10(x)计算以10为底的对数,math.log2(x)计算以2为底的对数。
代码 6-17:对数函数
import math
print(f"ln(e) = {math.log(math.e)}") # 输出:1.0
print(f"ln(10) = {math.log(10):.4f}") # 输出:2.3026
print(f"lg(100) = {math.log10(100)}") # 输出:2.0
print(f"lg(8) = {math.log2(8)}") # 输出:3.0
【例题 6-2】计算圆的面积和周长
编写程序,输入圆的半径,计算并输出圆的面积和周长。
代码 6-18:计算圆的面积和周长
import math
# 输入半径
radius = float(input("请输入圆的半径:"))
# 计算面积和周长
area = math.pi * math.pow(radius, 2)
circumference = 2 * math.pi * radius
# 输出结果
print(f"圆的面积:{area:.2f}")
print(f"圆的周长:{circumference:.2f}")
这个例子展示了math模块在实际计算中的应用。使用math.pi可以获得高精度的圆周率,使用math.pow()进行幂运算,使代码更加清晰专业。
datetime模块
datetime模块是Python中处理日期和时间的核心模块。它提供了date(日期)、time(时间)、datetime(日期时间)、timedelta(时间差)等类,可以方便地进行日期时间的创建、计算和格式化。
获取当前日期时间
使用datetime.datetime.now()可以获取当前的日期和时间,datetime.date.today()可以获取当前的日期。
代码 6-19:获取当前日期时间
from datetime import datetime, date
# 获取当前日期时间
now = datetime.now()
print(f"当前日期时间:{now}")
print(f"年份:{now.year}")
print(f"月份:{now.month}")
print(f"日期:{now.day}")
print(f"小时:{now.hour}")
print(f"分钟:{now.minute}")
print(f"秒:{now.second}")
# 获取当前日期
today = date.today()
print(f"今天日期:{today}")
创建指定日期时间
可以使用datetime()或date()构造函数创建指定的日期时间对象。
代码 6-20:创建指定日期时间
from datetime import datetime, date, time
# 创建指定日期
birthday = date(2000, 1, 1)
print(f"生日:{birthday}")
# 创建指定时间
meeting_time = time(14, 30, 0)
print(f"会议时间:{meeting_time}")
# 创建指定日期时间
appointment = datetime(2024, 12, 25, 10, 0, 0)
print(f"预约时间:{appointment}")
日期时间格式化
使用strftime()方法可以将日期时间格式化为指定的字符串格式。常用的格式化符号包括:%Y(四位年份)、%m(两位月份)、%d(两位日期)、%H(24小时制小时)、%M(分钟)、%S(秒)、%A(星期名称)等。
代码 6-21:日期时间格式化
from datetime import datetime
now = datetime.now()
# 格式化输出
print(now.strftime("%Y-%m-%d")) # 2024-01-15
print(now.strftime("%Y年%m月%d日")) # 2024年01月15日
print(now.strftime("%H:%M:%S")) # 14:30:25
print(now.strftime("%A")) # Monday
print(now.strftime("%Y年%m月%d日 %H:%M:%S")) # 完整格式
表 6-1 常用日期时间格式化符号
格式符号 含义 示例
%Y 四位年份 2024
%y 两位年份 24
%m 两位月份 01-12
%d 两位日期 01-31
%H 24小时制小时 00-23
%I 12小时制小时 01-12
%M 分钟 00-59
%S 秒 00-59
%A 星期名称 Monday
%a 星期缩写 Mon
%B 月份名称 January
%b 月份缩写 Jan
日期时间计算
使用timedelta可以方便地进行日期时间的加减运算,计算时间差。
代码 6-22:日期时间计算
from datetime import datetime, timedelta
now = datetime.now()
# 计算7天后的日期
future = now + timedelta(days=7)
print(f"7天后:{future.strftime('%Y-%m-%d')}")
# 计算30天前的日期
past = now - timedelta(days=30)
print(f"30天前:{past.strftime('%Y-%m-%d')}")
# 计算1小时后的时间
later = now + timedelta(hours=1)
print(f"1小时后:{later.strftime('%H:%M:%S')}")
# 计算两个日期相差多少天
date1 = datetime(2024, 1, 1)
date2 = datetime(2024, 12, 31)
diff = date2 - date1
print(f"相差{diff.days}天")
【例题 6-3】计算年龄
编写程序,输入出生日期,计算并输出年龄(周岁)和距离下次生日还有多少天。
代码 6-23:计算年龄
from datetime import date, timedelta
# 输入出生日期
year = int(input("请输入出生年份:"))
month = int(input("请输入出生月份:"))
day = int(input("请输入出生日期:"))
birthday = date(year, month, day)
today = date.today()
# 计算年龄
age = today.year - birthday.year
# 如果今年生日还没过,年龄减1
if (today.month, today.day) < (birthday.month, birthday.day):
age -= 1
# 计算下次生日
next_birthday = date(today.year, birthday.month, birthday.day)
if next_birthday < today:
next_birthday = date(today.year + 1, birthday.month, birthday.day)
days_to_birthday = (next_birthday - today).days
print(f"你的年龄是:{age}岁")
print(f"距离下次生日还有{days_to_birthday}天")
os 模块
os模块提供了与操作系统交互的功能,包括文件和目录的操作、环境变量的访问、进程管理等。在处理文件和目录时,os模块是非常实用的工具。
获取和切换当前目录
os.getcwd()用于获取当前工作目录,os.chdir(path)用于切换工作目录。
代码 6-24:获取和切换目录
import os
# 获取当前工作目录
current_dir = os.getcwd()
print(f"当前目录:{current_dir}")
# 切换目录
# os.chdir("/home/user")
# print(f"切换后目录:{os.getcwd()}")
创建和删除目录
os.mkdir(path)创建单个目录,os.makedirs(path)可以创建多级目录,os.rmdir(path)删除空目录。
代码 6-25:创建和删除目录
import os
# 创建目录
# os.mkdir("test") # 创建test目录
# 创建多级目录
# os.makedirs("test/sub1/sub2") # 创建多级目录
# 删除空目录
# os.rmdir("test") # 只能删除空目录
列出目录内容
os.listdir(path)返回指定目录下的所有文件和目录名称列表。
代码 6-26:列出目录内容
import os
# 列出当前目录的内容
items = os.listdir(".")
print("当前目录内容:")
for item in items:
print(f" {item}")
判断文件和目录
os.path.isfile(path)判断是否为文件,os.path.isdir(path)判断是否为目录,os.path.exists(path)判断路径是否存在。
代码 6-27:判断文件和目录
import os
path = "." # 当前目录
print(f"是否存在:{os.path.exists(path)}")
print(f"是否为文件:{os.path.isfile(path)}")
print(f"是否为目录:{os.path.isdir(path)}")
路径操作
os.path模块提供了丰富的路径操作函数,如获取文件名、目录名、文件扩展名、拼接路径等。
代码 6-28:路径操作
import os
filepath = "/home/user/documents/report.txt"
print(f"文件名:{os.path.basename(filepath)}") # report.txt
print(f"目录名:{os.path.dirname(filepath)}") # /home/user/documents
print(f"扩展名:{os.path.splitext(filepath)[1]}") # .txt
# 路径拼接
new_path = os.path.join("home", "user", "documents")
print(f"拼接路径:{new_path}")
【例题 6-4】文件统计工具
编写程序,统计指定目录下的文件数量和目录数量。
代码 6-29:文件统计工具
import os
# 指定目录
directory = input("请输入目录路径:")
if not os.path.exists(directory):
print("目录不存在!")
else:
file_count = 0
dir_count = 0
for item in os.listdir(directory):
item_path = os.path.join(directory, item)
if os.path.isfile(item_path):
file_count += 1
elif os.path.isdir(item_path):
dir_count += 1
print(f"文件数量:{file_count}")
print(f"目录数量:{dir_count}")
time 模块
time模块提供了时间访问和转换的功能。与datetime模块不同,time模块更侧重于底层的时间戳操作和程序计时功能。
时间戳
时间戳是从1970年1月1日00:00:00(称为Unix纪元)到现在的秒数。time.time()返回当前时间戳,这是一个浮点数。
代码 6-30:时间戳
import time
# 获取当前时间戳
timestamp = time.time()
print(f"当前时间戳:{timestamp}")
# 时间戳转换为本地时间
local_time = time.localtime(timestamp)
print(f"本地时间:{local_time}")
时间格式化
time.strftime()可以将时间格式化为字符串,time.strptime()可以将字符串解析为时间。
代码 6-31:时间格式化
import time
# 获取当前时间
current = time.localtime()
# 格式化输出
formatted = time.strftime("%Y-%m-%d %H:%M:%S", current)
print(f"格式化时间:{formatted}")
# 字符串解析为时间
time_str = "2024-01-15 14:30:00"
parsed = time.strptime(time_str, "%Y-%m-%d %H:%M:%S")
print(f"解析结果:{parsed}")
程序计时
time模块常用于测量程序执行时间。time.sleep(seconds)可以让程序暂停指定的秒数,这在需要延时或控制程序节奏时非常有用。
代码 6-32:程序计时
import time
print("程序开始")
start = time.time()
# 模拟耗时操作
time.sleep(2) # 暂停2秒
end = time.time()
elapsed = end - start
print("程序结束")
print(f"耗时:{elapsed:.2f}秒")
进度条示例
结合time.sleep()和循环,可以实现简单的进度条效果。
代码 6-33:进度条示例
import time
print("下载进度:")
for i in range(11):
percent = i * 10
bar = "█" * i + " " * (10 - i)
print(f"\r[{bar}] {percent}%", end="")
time.sleep(0.3)
print("\n下载完成!")
【例题 6-5】倒计时程序
编写一个倒计时程序,输入秒数后开始倒计时,每秒显示剩余时间,倒计时结束时发出提示。
代码 6-34:倒计时程序
import time
# 输入倒计时秒数
seconds = int(input("请输入倒计时秒数:"))
print(f"倒计时{seconds}秒开始!")
while seconds >= 0:
mins = seconds // 60
secs = seconds % 60
print(f"\r剩余时间:{mins:02d}:{secs:02d}", end="")
time.sleep(1)
seconds -= 1
print("\n时间到!")
calendar模块
calendar模块是Python中处理日历相关功能的内置模块。它提供了丰富的日历生成和日期计算功能,可以方便地输出日历、判断闰年、计算某月天数等。在开发日程管理、考勤系统、日历应用等场景中,calendar模块是非常实用的工具。
输出日历------calendar()函数
calendar.calendar(year)函数可以输出指定年份的完整日历,返回一个多行字符串。这个函数非常适合用于生成年度日历视图,可以一目了然地查看全年的日期分布情况。输出的日历按照月份排列,每周以周一开头,格式整齐美观。
代码 6-36:输出年度日历
import calendar
# 输出2024年的完整日历
year_cal = calendar.calendar(2024)
print(year_cal)
运行上述代码后,会在控制台输出2024年全年的日历,每个月份的日历整齐排列,方便查看,如图6-1 2024年日历。这种格式特别适合需要打印年度计划表或制作纸质日历的场景。
图6-1 2024年日历
{width="2.973611111111111in" height="3.384027777777778in"}
输出月历------month()函数
calendar.month(year, month)函数用于输出指定年份和月份的日历,返回一个多行字符串。相比输出全年日历,month()函数更加灵活,可以只显示我们关注的某个月份。这在需要查看特定月份日期安排时非常方便。
代码 6-37:输出月度日历
import calendar
# 输出2024年1月的日历
month_cal = calendar.month(2024, 1)
print(month_cal)
# 输出2024年12月的日历
print(calendar.month(2024, 12))
月历输出会显示该月的所有日期,并标注对应的星期几。第一行是月份和年份标题,第二行是星期标题,后面是具体的日期排列。这种格式直观清晰,适合用于月度计划安排。
判断闰年------isleap()函数
calendar.isleap(year)函数用于判断指定年份是否为闰年,返回布尔值True或False。闰年的判断规则是:能被4整除但不能被100整除,或者能被400整除的年份是闰年。这个函数封装了复杂的判断逻辑,使用起来非常简单。
代码 6-38:判断闰年
import calendar
# 判断是否为闰年
print(f"2024年是否为闰年:{calendar.isleap(2024)}") # True
print(f"2023年是否为闰年:{calendar.isleap(2023)}") # False
print(f"2000年是否为闰年:{calendar.isleap(2000)}") # True
print(f"1900年是否为闰年:{calendar.isleap(1900)}") # False
从上面的例子可以看出,2024年是闰年(能被4整除),2023年不是闰年。特别需要注意的是2000年和1900年:2000年能被400整除,所以是闰年;1900年虽然能被4整除,但也能被100整除且不能被400整除,所以不是闰年。
获取月份天数------monthrange()函数
calendar.monthrange(year, month)函数返回一个元组,包含两个元素:第一个是该月第一天是星期几(0表示周一,6表示周日),第二个是该月的天数。这个函数在需要知道某月有多少天或者该月从星期几开始时非常有用。
代码 6-39:获取月份天数
import calendar
# 获取2024年2月的信息
first_day, days = calendar.monthrange(2024, 2)
print(f"2024年2月第一天是星期{first_day}") # 3表示周四
print(f"2024年2月共有{days}天") # 闰年29天
# 获取2023年2月的信息
first_day2, days2 = calendar.monthrange(2023, 2)
print(f"2023年2月共有{days2}天") # 平年28天
# 获取2024年各月的天数
for month in range(1, 13):
_, days = calendar.monthrange(2024, month)
print(f"{month}月:{days}天")
这个函数特别适合用于日程管理系统,比如需要计算某月有多少个工作日,或者需要知道某月第一天是星期几来安排月度计划。注意返回的星期值中,0代表周一,这与datetime模块中0代表周一是一致的。
获取星期信息------weekday()函数
calendar.weekday(year, month, day)函数返回指定日期是星期几,返回值为0到6的整数(0表示周一,6表示周日)。这个函数可以快速查询任意日期对应的星期,在安排日程、计算工作日等场景中非常实用。
代码 6-40:获取星期信息
import calendar
# 查询特定日期是星期几
weekday = calendar.weekday(2024, 1, 1)
print(f"2024年1月1日是星期{weekday}") # 0表示周一
# 将数字转换为星期名称
day_names = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
print(f"2024年10月1日是{day_names[calendar.weekday(2024, 10, 1)]}")
# 查询生日是星期几
birthday_weekday = calendar.weekday(2000, 6, 15)
print(f"生日是{day_names[birthday_weekday]}")
获取月份日历矩阵------monthcalendar()函数
calendar.monthcalendar(year, month)函数返回一个嵌套列表,表示该月的日历矩阵。外层列表的每个元素代表一周,内层列表的每个元素代表一天(0表示该位置不属于本月)。这种格式便于程序化处理日历数据,比如在GUI界面中绘制日历控件。
代码 6-41:获取月份日历矩阵
import calendar
# 获取2024年1月的日历矩阵
cal_matrix = calendar.monthcalendar(2024, 1)
print("2024年1月日历矩阵:")
for week in cal_matrix:
print(week)
# 找出该月所有的周一
mondays = []
for week in cal_matrix:
if week[0] != 0: # 周一在索引0位置
mondays.append(week[0])
print(f"1月所有的周一:{mondays}")
【例题 6-6】制作年度工作日统计工具
编写程序,统计指定年份每个月的工作日数量(假设周一到周五为工作日,排除周末)。
代码 6-42:年度工作日统计工具
import calendar
def count_workdays(year, month):
"""计算指定月份的工作日数量"""
workdays = 0
# 获取该月的日历矩阵
cal_matrix = calendar.monthcalendar(year, month)
for week in cal_matrix:
for day_idx, day in enumerate(week):
# day_idx: 0-4是周一到周五,5-6是周末
if day != 0 and day_idx < 5:
workdays += 1
return workdays
# 统计2024年各月工作日
year = 2024
print(f"{year}年各月工作日统计:")
print("-" * 25)
total_workdays = 0
for month in range(1, 13):
workdays = count_workdays(year, month)
total_workdays += workdays
print(f"{month:2d}月:{workdays}个工作日")
print("-" * 25)
print(f"全年共{total_workdays}个工作日")
这个例题综合运用了calendar模块的多个功能:monthcalendar()获取日历矩阵,然后遍历矩阵统计工作日。通过这个程序,可以快速了解一年中每个月的工作日分布情况,对于人力资源规划、项目排期等都有参考价值。
shutil 模块
shutil模块是Python中用于高级文件操作的内置模块,其名称是"shell utility"的缩写。与os模块相比,shutil模块提供了更高级、更便捷的文件操作功能,如复制文件、复制目录、移动文件、删除目录树等。在需要进行文件批量处理、备份、迁移等操作时,shutil模块是首选工具。
复制文件------copy()函数
shutil.copy(src, dst)函数用于复制文件。src是源文件路径,dst是目标路径(可以是目录或文件名)。该函数会复制文件内容和权限,但不会复制元数据(如创建时间、修改时间)。如果目标文件已存在,会被覆盖。
代码 6-43:复制文件
import shutil
import os
# 创建测试文件
with open("source.txt", "w") as f:
f.write("这是源文件的内容")
# 复制文件到当前目录(新文件名)
shutil.copy("source.txt", "destination.txt")
print("文件复制成功!")
# 复制文件到指定目录
if not os.path.exists("backup"):
os.mkdir("backup")
shutil.copy("source.txt", "backup/")
print("文件已复制到backup目录")
# 清理测试文件
# os.remove("source.txt")
# os.remove("destination.txt")
# shutil.rmtree("backup")
在实际应用中,copy()函数常用于创建文件备份。需要注意的是,如果目标路径是目录,文件会以原文件名复制到该目录下;如果目标路径包含文件名,则会以新文件名保存。
复制文件(保留元数据)------copy2()函数
shutil.copy2(src, dst)函数与copy()类似,但会同时复制文件的元数据(如最后访问时间、最后修改时间等)。在需要完整复制文件信息的场景中,应该使用copy2()函数。
代码 6-44:复制文件保留元数据
import shutil
import os
import time
# 创建测试文件
with open("original.txt", "w") as f:
f.write("原始文件内容")
time.sleep(1) # 等待1秒
# 使用copy()复制
shutil.copy("original.txt", "copy1.txt")
# 使用copy2()复制(保留元数据)
shutil.copy2("original.txt", "copy2.txt")
# 比较修改时间
stat1 = os.stat("copy1.txt")
stat2 = os.stat("copy2.txt")
print(f"copy1修改时间:{stat1.st_mtime}")
print(f"copy2修改时间:{stat2.st_mtime}")
print("copy2保留了原始文件的修改时间")
复制整个目录------copytree()函数
shutil.copytree(src, dst)函数用于递归复制整个目录树,包括目录下的所有文件和子目录。这是一个非常强大的功能,可以一次性复制整个项目文件夹。注意目标目录必须不存在,否则会报错。
代码 6-45:复制整个目录
import shutil
import os
# 创建测试目录结构
os.makedirs("project/src", exist_ok=True)
os.makedirs("project/docs", exist_ok=True)
with open("project/main.py", "w") as f:
f.write("print('Hello')")
with open("project/src/utils.py", "w") as f:
f.write("# 工具函数")
with open("project/docs/readme.txt", "w") as f:
f.write("项目说明")
# 复制整个目录
shutil.copytree("project", "project_backup")
print("目录复制成功!")
# 验证复制结果
for root, dirs, files in os.walk("project_backup"):
for file in files:
filepath = os.path.join(root, file)
print(f"已复制:{filepath}")
移动文件或目录------move()函数
shutil.move(src, dst)函数用于移动文件或目录。如果源和目标在同一文件系统上,实际上是重命名操作,速度很快;如果在不同文件系统上,则会先复制后删除。这个函数也可以用于重命名文件。
代码 6-46:移动文件或目录
import shutil
import os
# 创建测试文件
with open("old_name.txt", "w") as f:
f.write("测试内容")
# 移动并重命名文件
shutil.move("old_name.txt", "new_name.txt")
print("文件已重命名")
# 创建目录并移动文件
if not os.path.exists("archive"):
os.mkdir("archive")
shutil.move("new_name.txt", "archive/")
print("文件已移动到archive目录")
# 移动整个目录
shutil.move("archive", "old_archive")
print("目录已重命名")
删除目录树------rmtree()函数
shutil.rmtree(path)函数用于递归删除整个目录树,包括目录下的所有文件和子目录。这是一个危险的操作,因为删除后无法恢复,使用时要特别小心。建议在删除前先确认目录内容。
代码 6-47:删除目录树
import shutil
import os
# 创建测试目录
os.makedirs("test_dir/subdir", exist_ok=True)
with open("test_dir/file1.txt", "w") as f:
f.write("文件1")
with open("test_dir/subdir/file2.txt", "w") as f:
f.write("文件2")
print("删除前目录内容:")
print(os.listdir("test_dir"))
# 删除整个目录树
shutil.rmtree("test_dir")
print("目录已删除")
# 验证删除结果
if not os.path.exists("test_dir"):
print("确认:目录已不存在")
获取磁盘使用情况------disk_usage()函数
shutil.disk_usage(path)函数返回指定路径所在磁盘的使用情况,包括总容量、已使用空间和剩余空间。返回值是一个命名元组,包含total、used、free三个属性,单位是字节。
代码 6-48:获取磁盘使用情况
import shutil
# 获取当前磁盘使用情况
usage = shutil.disk_usage(".")
# 转换为GB单位
def bytes_to_gb(bytes_val):
return bytes_val / (1024 ** 3)
print("磁盘使用情况:")
print(f"总容量:{bytes_to_gb(usage.total):.2f} GB")
print(f"已使用:{bytes_to_gb(usage.used):.2f} GB")
print(f"剩余空间:{bytes_to_gb(usage.free):.2f} GB")
print(f"使用率:{usage.used/usage.total*100:.1f}%")
【例题 6-7】文件备份工具
编写一个文件备份工具,将指定目录下的所有文件备份到另一个目录,并在备份目录名中添加日期时间戳。
代码 6-49:文件备份工具
import shutil
import os
from datetime import datetime
def backup_directory(source_dir):
"""备份指定目录"""
# 检查源目录是否存在
if not os.path.exists(source_dir):
print(f"错误:目录 {source_dir} 不存在")
return
# 生成带时间戳的备份目录名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"{os.path.basename(source_dir)}_backup_{timestamp}"
backup_path = os.path.join(os.path.dirname(source_dir), backup_name)
# 检查磁盘空间
source_size = sum(
os.path.getsize(os.path.join(root, file))
for root, dirs, files in os.walk(source_dir)
for file in files
)
disk_usage = shutil.disk_usage(source_dir)
if source_size > disk_usage.free:
print("错误:磁盘空间不足")
return
# 执行备份
print(f"正在备份 {source_dir} ...")
shutil.copytree(source_dir, backup_path)
# 统计备份结果
file_count = sum(len(files) for _, _, files in os.walk(backup_path))
print(f"备份完成!")
print(f"备份位置:{backup_path}")
print(f"备份文件数:{file_count}")
print(f"备份大小:{source_size/1024/1024:.2f} MB")
# 使用示例
# backup_directory("my_project")
这个例题综合运用了shutil模块的copytree()和disk_usage()函数,以及os模块的路径操作功能。程序会先检查源目录是否存在、磁盘空间是否充足,然后执行备份并统计结果。这是一个实用的文件管理工具。
re 模块
re模块是Python中处理正则表达式的内置模块。正则表达式(Regular Expression)是一种强大的文本匹配模式,可以用来检查字符串是否符合某种模式、提取符合模式的子串、替换字符串中的特定内容等。在数据验证、文本处理、爬虫开发等场景中,正则表达式是不可或缺的工具。
正则表达式基础语法
在学习re模块之前,需要先了解正则表达式的基本语法。正则表达式使用特殊字符来表示匹配规则,常用的元字符包括:点号(.)匹配任意单个字符、星号(*)匹配前一个字符零次或多次、加号(+)匹配前一个字符一次或多次、问号(?)匹配前一个字符零次或一次。
表 6-2 常用正则表达式元字符
元字符 含义 示例
. 匹配任意单个字符 a.c 匹配 abc、adc
* 匹配前一个字符0次或多次 ab* 匹配 a、ab、abb
+ 匹配前一个字符1次或多次 ab+ 匹配 ab、abb
? 匹配前一个字符0次或1次 ab? 匹配 a、ab
^ 匹配字符串开头 ^Hello 匹配开头的Hello
$ 匹配字符串结尾 world$ 匹配结尾的world
\d 匹配数字字符 \d+ 匹配一个或多个数字
\w 匹配字母数字下划线 \w+ 匹配单词
\s 匹配空白字符 \s+ 匹配空格、制表符
[] 字符集合 [abc] 匹配a或b或c
() 分组捕获 (ab)+ 匹配ab、abab
匹配字符串------match()函数
re.match(pattern, string)函数从字符串开头开始匹配正则表达式。如果匹配成功,返回一个匹配对象;如果匹配失败,返回None。match()只从字符串开头匹配,不会搜索字符串中间的位置。
代码 6-50:使用match()匹配字符串
import re
# 从开头匹配
result = re.match(r"Hello", "Hello World")
if result:
print(f"匹配成功:{result.group()}")
else:
print("匹配失败")
# 匹配失败示例(不在开头)
result2 = re.match(r"World", "Hello World")
print(f"匹配结果:{result2}") # None
# 使用分组提取内容
pattern = r"(\d{4})-(\d{2})-(\d{2})"
date_str = "2024-01-15 今天是星期一"
match = re.match(pattern, date_str)
if match:
print(f"年份:{match.group(1)}")
print(f"月份:{match.group(2)}")
print(f"日期:{match.group(3)}")
搜索字符串------search()函数
re.search(pattern, string)函数在整个字符串中搜索第一个匹配的位置。与match()不同,search()会扫描整个字符串,找到第一个匹配的位置。这是最常用的匹配函数之一。
代码 6-51:使用search()搜索字符串
import re
# 在字符串中搜索
text = "我的电话是13812345678,邮箱是test@example.com"
# 搜索手机号
phone_pattern = r"1[3-9]\d{9}"
phone_match = re.search(phone_pattern, text)
if phone_match:
print(f"手机号:{phone_match.group()}")
# 搜索邮箱
email_pattern = r"\w+@\w+\.\w+"
email_match = re.search(email_pattern, text)
if email_match:
print(f"邮箱:{email_match.group()}")
# 获取匹配位置
print(f"手机号位置:{phone_match.start()}-{phone_match.end()}")
查找所有匹配------findall()函数
re.findall(pattern, string)函数返回字符串中所有匹配的子串列表。如果正则表达式中包含分组,则返回分组内容的列表。这个函数非常适合提取文本中的所有符合条件的内容。
代码 6-52:使用findall()查找所有匹配
import re
text = "价格:苹果5元,香蕉3元,橙子4元,葡萄8元"
# 提取所有数字
numbers = re.findall(r"\d+", text)
print(f"所有数字:{numbers}")
# 提取所有水果和价格
fruits_prices = re.findall(r"(\w+)(\d+)元", text)
print("水果价格:")
for fruit, price in fruits_prices:
print(f" {fruit}:{price}元")
# 提取网页中的所有链接
html = '<a href="page1.html">链接1</a><a href="page2.html">链接2</a>'
links = re.findall(r'href="([^"]+)"', html)
print(f"链接地址:{links}")
替换字符串------sub()函数
re.sub(pattern, repl, string)函数用于替换字符串中匹配正则表达式的部分。pattern是正则表达式,repl是替换内容(可以是字符串或函数),string是原字符串。这个函数常用于文本清洗和格式化。
代码 6-53:使用sub()替换字符串
import re
# 替换手机号为星号
text = "联系方式:13812345678,备用:13987654321"
hidden = re.sub(r"1[3-9]\d{9}", "***********", text)
print(f"隐藏后:{hidden}")
# 替换多余空格
text2 = "这 是 有 多余空格的 文本"
cleaned = re.sub(r"\s+", " ", text2)
print(f"清理后:{cleaned}")
# 使用函数进行替换
def double_price(match):
price = int(match.group())
return str(price * 2)
text3 = "原价:100元,200元,300元"
new_text = re.sub(r"\d+", double_price, text3)
print(f"翻倍后:{new_text}")
分割字符串------split()函数
re.split(pattern, string)函数根据正则表达式分割字符串,返回分割后的列表。与字符串的split()方法相比,re.split()可以使用更复杂的分隔符模式。
代码 6-54:使用split()分割字符串
import re
# 按多种分隔符分割
text = "苹果,香蕉;橙子|葡萄 西瓜"
fruits = re.split(r"[,;|\s]+", text)
print(f"水果列表:{fruits}")
# 分割并保留分隔符
text2 = "2024-01-15"
parts = re.split(r"(-)", text2)
print(f"分割结果:{parts}")
# 分割句子
sentence = "Hello! How are you? I am fine."
words = re.split(r"[!?\s.]+", sentence)
words = [w for w in words if w] # 过滤空字符串
print(f"单词:{words}")
编译正则表达式------compile()函数
re.compile(pattern)函数将正则表达式编译成模式对象,可以提高重复使用时的效率。如果一个正则表达式需要多次使用,建议先编译再使用。编译后的对象可以直接调用match()、search()等方法。
代码 6-55:使用compile()编译正则表达式
import re
# 编译手机号正则表达式
phone_pattern = re.compile(r"1[3-9]\d{9}")
# 多次使用编译后的模式
texts = [
"联系电话:13812345678",
"手机:15987654321",
"电话号码:18811112222"
]
for text in texts:
match = phone_pattern.search(text)
if match:
print(f"找到手机号:{match.group()}")
# 编译时添加标志
# re.IGNORECASE 忽略大小写
pattern = re.compile(r"hello", re.IGNORECASE)
print(pattern.findall("Hello HELLO hello"))
【例题 6-8】数据验证工具
编写一个数据验证工具,可以验证用户输入的手机号、邮箱、身份证号是否符合规范。
代码 6-56:数据验证工具
import re
def validate_phone(phone):
"""验证手机号"""
pattern = r"^1[3-9]\d{9}$"
return bool(re.match(pattern, phone))
def validate_email(email):
"""验证邮箱"""
pattern = r"^[\w.-]+@[\w.-]+\.\w+$"
return bool(re.match(pattern, email))
def validate_id_card(id_card):
"""验证身份证号(18位)"""
pattern = r"^\d{17}[\dXx]$"
return bool(re.match(pattern, id_card))
# 测试验证
print("=== 数据验证工具 ===")
# 验证手机号
phones = ["13812345678", "12345678901", "15987654321"]
print("\n手机号验证:")
for phone in phones:
result = "有效" if validate_phone(phone) else "无效"
print(f" {phone}:{result}")
# 验证邮箱
emails = ["test@example.com", "invalid-email", "user@domain.cn"]
print("\n邮箱验证:")
for email in emails:
result = "有效" if validate_email(email) else "无效"
print(f" {email}:{result}")
# 验证身份证
ids = ["110101199001011234", "123456789012345678", "11010119900101123X"]
print("\n身份证验证:")
for id_card in ids:
result = "有效" if validate_id_card(id_card) else "无效"
print(f" {id_card}:{result}")
这个例题展示了正则表达式在实际开发中的重要应用------数据验证。通过定义不同的正则表达式模式,可以验证各种格式的数据。在实际项目中,这些验证函数可以用于表单验证、数据清洗等场景,确保用户输入的数据符合预期格式。
自定义模块
在前面的学习中,我们已经学会了使用Python自带的内置模块,比如random、math、datetime等。这些模块就像是一个个现成的"工具箱",我们只需要导入就可以直接使用里面的工具。但是,在实际开发中,Python自带的模块并不能满足所有的需求。有时候,我们需要根据自己的需求,打造属于自己的"工具箱",这就是自定义模块。
自定义模块其实就是我们自己编写的Python文件。还记得我们在第5章学过函数吗?当时我们编写了很多实用的函数,比如计算平均分的函数、判断成绩等级的函数等。如果这些函数在多个程序中都要用到,难道每次都要重新写一遍吗?当然不需要!我们只需要把这些函数保存到一个.py文件中,这个文件就成了一个自定义模块,在其他程序中导入后就可以直接使用了。
本节将带领大家学习如何创建自定义模块、如何在其他程序中导入和使用自定义模块,以及如何利用列表、字典、元组等数据结构来组织模块中的数据,让我们的模块功能更加强大、使用更加灵活。
创建自己的模块
什么是自定义模块?
自定义模块,简单来说,就是一个包含Python代码的.py文件。这个文件中可以定义函数、变量、类等。当我们在其他程序中需要使用这些函数或变量时,只需要通过import语句导入这个文件,就可以直接调用了。这就好比我们自己动手打造了一个专属的工具箱,里面装满了我们常用的工具,需要用的时候打开工具箱拿就行了。
创建自定义模块非常简单,只需要以下三个步骤:第一步,新建一个.py文件,文件名就是模块名。比如我们创建一个名为mytools.py的文件,那么模块名就是mytools。第二步,在这个文件中编写函数、变量等代码。第三步,在其他程序中通过import mytools导入这个模块,就可以使用其中的函数和变量了。
需要注意的是,模块的文件名必须符合Python的命名规则:只能包含字母、数字和下划线,而且不能以数字开头。另外,模块名尽量做到"见名知意",比如math_operations.py一看就知道是数学运算相关的模块,student_manager.py一看就知道是学生管理相关的模块。
创建第一个自定义模块
让我们从一个简单的例子开始。假设我们在日常编程中经常需要用到一些成绩处理的功能,比如计算平均分、找出最高分、判断成绩等级等。每次都重新写这些函数太麻烦了,不如把它们集中到一个模块中,方便以后反复使用。下面我们来创建一个名为score_utils.py的成绩处理模块。
代码 6-57:创建成绩处理模块 score_utils.py
# score_utils.py ------ 成绩处理工具模块
"""
成绩处理工具模块
提供成绩计算、统计、等级判断等功能
"""
# ---------- 函数1:计算平均分 ----------
def calc_average(scores):
"""
计算成绩列表的平均分
参数scores:存储成绩的列表,如[85, 92, 78, 96]
返回值:平均分(保留1位小数)
"""
if len(scores) == 0:
return 0
total = 0
for s in scores:
total = total + s
average = total / len(scores)
return round(average, 1)
# ---------- 函数2:找出最高分 ----------
def find_max(scores):
"""
找出成绩列表中的最高分
参数scores:存储成绩的列表
返回值:最高分
"""
if len(scores) == 0:
return 0
max_score = scores[0]
for s in scores:
if s > max_score:
max_score = s
return max_score
# ---------- 函数3:找出最低分 ----------
def find_min(scores):
"""
找出成绩列表中的最低分
参数scores:存储成绩的列表
返回值:最低分
"""
if len(scores) == 0:
return 0
min_score = scores[0]
for s in scores:
if s < min_score:
min_score = s
return min_score
# ---------- 函数4:判断成绩等级 ----------
def get_grade(score):
"""
根据分数判断成绩等级
参数score:单个分数
返回值:等级字符串
"""
if score >= 90:
return "优秀"
elif score >= 80:
return "良好"
elif score >= 70:
return "中等"
elif score >= 60:
return "及格"
else:
return "不及格"
# ---------- 函数5:统计各等级人数 ----------
def count_grades(scores):
"""
统计各等级的人数,返回字典
参数scores:存储成绩的列表
返回值:字典,如{"优秀":2, "良好":3, ...}
"""
grade_count = {"优秀": 0, "良好": 0, "中等": 0, "及格": 0, "不及格": 0}
for s in scores:
grade = get_grade(s)
grade_count[grade] = grade_count[grade] + 1
return grade_count
上面的代码创建了一个名为score_utils.py的模块文件。在这个模块中,我们定义了5个函数,每个函数都有明确的功能和详细的注释说明。其中,calc_average()函数接收一个成绩列表作为参数,通过for循环遍历列表中的每个成绩,累加求和后计算平均值;find_max()和find_min()函数分别用于找出列表中的最高分和最低分;get_grade()函数根据单个分数判断对应的等级;count_grades()函数则综合运用了列表遍历和字典操作,统计各等级的人数并返回一个字典。
这里需要特别注意的是,模块文件中的函数使用了列表作为参数,函数内部通过for循环遍历列表元素进行计算。同时,count_grades()函数的返回值是一个字典,它将等级名称作为键、人数作为值,通过字典的键值对来组织统计数据。这种用列表传递批量数据、用字典组织统计结果的方式,在实际开发中非常常见,同学们一定要熟练掌握。
使用自定义模块
导入自定义模块
创建好自定义模块之后,在其他程序中使用它的方法和使用内置模块完全一样,都是通过import语句来导入。假设我们把score_utils.py文件保存在当前工作目录下,那么在同一个目录下的其他.py文件中就可以直接导入并使用它。
导入自定义模块有几种常见的方式:第一种是使用import 模块名的方式导入整个模块,然后通过"模块名.函数名()"的方式调用函数,比如import score_utils,然后调用score_utils.calc_average(scores)。第二种是使用from 模块名 import 函数名的方式导入指定的函数,导入后可以直接使用函数名调用,比如from score_utils import calc_average,然后直接调用calc_average(scores)。第三种是使用from 模块名 import *的方式导入模块中的所有内容,这种方式虽然方便,但不推荐使用,因为可能会造成命名冲突。
代码 6-58:导入并使用 score_utils 模块
# main.py ------ 使用成绩处理模块
import score_utils
# 定义一个成绩列表
scores = [85, 92, 78, 96, 63, 71, 88, 55, 90, 82]
print("=== 班级成绩分析报告 ===")
print()
# 1. 计算平均分
avg = score_utils.calc_average(scores)
print("全班平均分:", avg)
# 2. 找出最高分和最低分
max_score = score_utils.find_max(scores)
min_score = score_utils.find_min(scores)
print("最高分:", max_score)
print("最低分:", min_score)
# 3. 统计各等级人数
grade_dict = score_utils.count_grades(scores)
print()
print("各等级人数统计:")
for grade, count in grade_dict.items():
print(" " + grade + ":" + str(count) + "人")
# 4. 逐个判断每位同学的等级
print()
print("每位同学的成绩等级:")
i = 1
for s in scores:
grade = score_utils.get_grade(s)
print(" 第" + str(i) + "位同学:" + str(s) + "分(" + grade + ")")
i = i + 1
运行上面的程序,输出结果如下所示:
=== 班级成绩分析报告 ===
全班平均分:80.0
最高分:96
最低分:55
各等级人数统计:
优秀:2人
良好:3人
中等:2人
及格:2人
不及格:1人
每位同学的成绩等级:
第1位同学:85分(良好)
第2位同学:92分(优秀)
第3位同学:78分(中等)
...(略)
在代码6-58中,我们首先用import score_utils导入了自定义模块,然后定义了一个成绩列表scores,接着依次调用了模块中的各个函数。特别值得注意的是,count_grades()函数返回的是一个字典,我们通过for grade, count in grade_dict.items()来遍历字典中的每一个键值对,分别获取等级名称和对应的人数。这种"列表传数据、字典存结果、循环遍历输出"的模式,是Python中处理批量数据时非常经典的编程思路。
使用from...import导入指定函数
如果只需要使用模块中的某几个函数,可以使用from...import语句只导入需要的函数,这样代码写起来更简洁,不需要每次都加模块名前缀。下面的例子演示了如何只导入calc_average和get_grade两个函数:
代码 6-59:使用from...import导入指定函数
# 只导入需要的函数
from score_utils import calc_average, get_grade
# 直接使用函数名,不需要加模块名前缀
scores = [88, 75, 93, 61, 82]
avg = calc_average(scores)
print("平均分:", avg)
for s in scores:
print(str(s) + "分 -> " + get_grade(s))
使用from...import方式导入后,可以直接使用函数名来调用,代码更加简洁。但是要注意,如果同时从多个模块导入了同名函数,后导入的会覆盖先导入的,因此在使用这种方式时要避免函数名冲突。在实际开发中,如果只用到模块中的一两个函数,推荐使用from...import方式;如果要用到模块中的多个函数,推荐使用import方式,这样代码的可读性更好,一眼就能看出函数来自哪个模块。
在模块中使用列表、字典和元组
用列表组织批量数据
在自定义模块中,列表是最常用的数据结构之一。我们可以用列表来存储一批同类型的数据,比如一组成绩、一组学生姓名、一组商品价格等。然后通过函数对这些列表数据进行统一的处理,比如计算总和、求平均值、排序、筛选等。列表的灵活性使得它成为模块中传递批量数据的首选方式。
下面我们创建一个更实用的模块------学生信息管理模块。这个模块将使用列表来存储学生信息,使用字典来表示每个学生的详细资料,使用元组来存储不可变的数据(如科目名称)。通过这个综合案例,同学们可以深入理解列表、字典和元组在模块中的实际应用。
代码 6-60:创建学生信息管理模块 student_manager.py
# student_manager.py ------ 学生信息管理模块
"""
学生信息管理模块
提供学生信息的增删改查和统计功能
"""
# ---------- 模块级别的常量数据 ----------
# 用元组存储科目名称(不可变数据适合用元组)
SUBJECTS = ("语文", "数学", "英语", "Python")
# 用字典存储各科满分
FULL_SCORES = {"语文": 100, "数学": 100, "英语": 100, "Python": 100}
# ---------- 函数1:创建学生信息字典 ----------
def create_student(name, chinese, math, english, python):
"""
创建一个学生信息字典
参数:姓名和四科成绩
返回值:学生信息字典
"""
scores_list = [chinese, math, english, python]
total = chinese + math + english + python
average = round(total / 4, 1)
student = {
"name": name,
"scores": scores_list,
"total": total,
"average": average
}
return student
# ---------- 函数2:添加学生到列表 ----------
def add_student(student_list, student):
"""
将一个学生字典添加到学生列表中
参数student_list:学生列表
参数student:学生字典
"""
student_list.append(student)
print("已添加学生:" + student["name"])
# ---------- 函数3:按姓名查找学生 ----------
def find_by_name(student_list, name):
"""
在学生列表中按姓名查找学生
返回值:找到的学生字典,未找到返回None
"""
for student in student_list:
if student["name"] == name:
return student
return None
# ---------- 函数4:计算班级各科平均分 ----------
def class_subject_avg(student_list):
"""
计算班级各科平均分,返回字典
返回值:如{"语文":85.2, "数学":78.6, ...}
"""
subject_totals = {"语文": 0, "数学": 0, "英语": 0, "Python": 0}
count = len(student_list)
if count == 0:
return subject_totals
for student in student_list:
scores = student["scores"]
subject_totals["语文"] = subject_totals["语文"] + scores[0]
subject_totals["数学"] = subject_totals["数学"] + scores[1]
subject_totals["英语"] = subject_totals["英语"] + scores[2]
subject_totals["Python"] = subject_totals["Python"] + scores[3]
# 计算平均分
result = {}
for subject in SUBJECTS:
avg = round(subject_totals[subject] / count, 1)
result[subject] = avg
return result
# ---------- 函数5:找出总分最高分的学生 ----------
def find_top_student(student_list):
"""
找出总分最高的学生
返回值:学生字典
"""
if len(student_list) == 0:
return None
top = student_list[0]
for student in student_list:
if student["total"] > top["total"]:
top = student
return top
# ---------- 函数6:生成成绩排名 ----------
def get_ranking(student_list):
"""
按总分从高到低排序,返回排名后的列表
"""
# 复制列表,避免修改原列表
ranked = student_list.copy()
# 冒泡排序(从高到低)
n = len(ranked)
for i in range(n):
for j in range(0, n - 1 - i):
if ranked[j]["total"] < ranked[j + 1]["total"]:
temp = ranked[j]
ranked[j] = ranked[j + 1]
ranked[j + 1] = temp
return ranked
# ---------- 函数7:显示学生详细信息 ----------
def show_student(student, subjects):
"""
格式化显示一个学生的详细信息
"""
print("姓名:" + student["name"])
scores = student["scores"]
for i in range(len(subjects)):
print(" " + subjects[i] + ":" + str(scores[i]) + "分")
print(" 总分:" + str(student["total"]) + "分")
print(" 平均分:" + str(student["average"]) + "分")
代码6-60创建了一个功能丰富的学生信息管理模块。在这个模块中,我们综合运用了三种重要的数据结构:第一,元组SUBJECTS用来存储科目名称,因为科目名称是固定不变的,使用元组可以防止被意外修改,体现了数据的安全性;第二,字典FULL_SCORES用来存储各科满分,通过科目名称(键)快速查找对应的满分(值);第三,列表student_list用来存储所有学生的信息,每个元素都是一个字典,字典中又包含了列表(各科成绩)和数值(总分、平均分)等不同类型的数据。
这种"列表套字典、字典套列表"的数据组织方式在实际开发中非常普遍。比如,create_student()函数创建一个字典来表示单个学生的信息,其中"scores"键对应的值是一个列表,存储了四科成绩;add_student()函数将学生字典添加到学生列表中;class_subject_avg()函数遍历学生列表,累加各科成绩后计算平均分,结果以字典的形式返回;get_ranking()函数则对列表中的学生按总分进行排序。理解并掌握这种数据嵌套的结构,对于编写实用的程序至关重要。
使用学生信息管理模块
模块创建好之后,我们就可以在主程序中导入并使用它了。下面的例子演示了如何使用student_manager模块来完成班级学生信息的录入、查询、统计和排名等功能。
代码 6-61:使用 student_manager 模块
# main.py ------ 使用学生信息管理模块
import student_manager
# 获取模块中定义的常量
subjects = student_manager.SUBJECTS
print("本学期科目:", subjects)
print("各科满分:", student_manager.FULL_SCORES)
print()
# 创建学生列表
students = []
# 添加学生信息
print("=== 录入学生信息 ===")
s1 = student_manager.create_student("张三", 85, 92, 78, 96)
student_manager.add_student(students, s1)
s2 = student_manager.create_student("李四", 72, 88, 91, 83)
student_manager.add_student(students, s2)
s3 = student_manager.create_student("王五", 90, 65, 84, 77)
student_manager.add_student(students, s3)
s4 = student_manager.create_student("赵六", 68, 95, 73, 89)
student_manager.add_student(students, s4)
s5 = student_manager.create_student("钱七", 93, 81, 87, 92)
student_manager.add_student(students, s5)
print()
# 按姓名查找学生
print("=== 查找学生 ===")
result = student_manager.find_by_name(students, "王五")
if result:
student_manager.show_student(result, subjects)
else:
print("未找到该学生")
print()
# 统计班级各科平均分
print("=== 班级各科平均分 ===")
subject_avg = student_manager.class_subject_avg(students)
for subject, avg in subject_avg.items():
print(" " + subject + ":" + str(avg) + "分")
print()
# 找出总分最高的学生
print("=== 总分最高分 ===")
top = student_manager.find_top_student(students)
if top:
student_manager.show_student(top, subjects)
print()
# 生成并显示排名
print("=== 成绩排名 ===")
ranking = student_manager.get_ranking(students)
for i in range(len(ranking)):
student = ranking[i]
print(" 第" + str(i + 1) + "名:" + student["name"]
+ " 总分:" + str(student["total"]) + "分")
运行上面的程序,输出结果如下所示:
本学期科目:("语文", "数学", "英语", "Python")
各科满分:{"语文": 100, "数学": 100, "英语": 100, "Python": 100}
=== 录入学生信息 ===
已添加学生:张三
已添加学生:李四
已添加学生:王五
已添加学生:赵六
已添加学生:钱七
=== 查找学生 ===
姓名:王五
语文:90分
数学:65分
英语:84分
Python:77分
总分:316分
平均分:79.0分
=== 班级各科平均分 ===
语文:81.6分
数学:84.2分
英语:82.6分
Python:87.4分
=== 总分最高分 ===
姓名:钱七
语文:93分
数学:81分
英语:87分
Python:92分
总分:353分
平均分:88.3分
=== 成绩排名 ===
第1名:钱七 总分:353分
第2名:张三 总分:351分
第3名:赵六 总分:325分
第4名:李四 总分:334分
第5名:王五 总分:316分
通过代码6-60和代码6-61可以看到,自定义模块的使用方式与内置模块完全一致。在主程序中,我们通过import student_manager导入模块后,就可以调用模块中定义的函数和访问模块中定义的常量了。整个程序的逻辑非常清晰:先创建学生列表,然后逐个添加学生信息,接着进行查找、统计、排名等操作。每个功能都由模块中的一个函数负责,主程序只需要按顺序调用这些函数即可。这种"模块负责功能实现,主程序负责流程控制"的分工方式,是软件工程中非常重要的设计思想。
模块的搜索路径
Python如何找到模块?
当我们在程序中写import score_utils的时候,Python是怎么找到score_utils.py这个文件的呢?Python会按照一定的顺序在几个目录中搜索这个文件。理解模块的搜索路径,有助于我们正确地组织和管理自己的模块文件,避免出现"ModuleNotFoundError"(找不到模块)的错误。
Python查找模块的顺序如下:第一,当前目录。也就是运行Python程序的目录,这是Python最先查找的位置。如果模块文件和主程序文件在同一个目录下,Python就能直接找到它。第二,标准库目录。Python安装时自带的标准库模块都存放在这个目录中,比如random、math、datetime等。第三,第三方库目录。通过pip安装的第三方模块存放在这个目录中。第四,环境变量PYTHONPATH指定的目录。我们可以通过设置这个环境变量来告诉Python额外的模块搜索路径。
在实际开发中,最简单的方式就是把自定义模块文件和主程序文件放在同一个目录下。如果模块文件比较多,可以在当前目录下创建一个子文件夹来存放模块文件,但这时需要在该子文件夹中创建一个名为__init__.py的空文件(这个文件告诉Python这个文件夹是一个"包"),然后通过"from 文件夹名 import 模块名"的方式来导入。
查看模块搜索路径
如果我们想知道Python的模块搜索路径有哪些,可以使用sys模块来查看。sys模块是Python的内置模块之一,它提供了一个名为path的变量,这个变量是一个列表,里面存储了所有的模块搜索路径。
代码 6-62:查看模块搜索路径
import sys
# 查看模块搜索路径
print("Python模块搜索路径:")
for i in range(len(sys.path)):
path = sys.path[i]
print(" 路径" + str(i + 1) + ":" + path)
运行上面的代码,你会看到Python的模块搜索路径列表。列表中的第一个元素通常是空字符串(表示当前目录),后面的元素是Python安装目录下的标准库路径和第三方库路径等。了解了这些路径,当出现"找不到模块"的错误时,我们就可以检查模块文件是否放在了这些路径所对应的目录中。
__name__变量与模块测试
什么是__name__变量?
在Python中,每个模块都有一个内置的特殊变量叫做__name__。这个变量的值取决于模块是如何被使用的。当一个模块被直接运行时(比如在命令行中执行python score_utils.py),__name__的值就是字符串"__main__";当一个模块被其他程序导入时(比如在其他文件中写import score_utils),__name__的值就是模块名本身,也就是"score_utils"。
这个特性非常有用。我们在编写模块时,通常会在模块文件的末尾加上一段测试代码,用来验证模块中的函数是否正常工作。但是,如果这段测试代码在模块被导入时也执行,就会在主程序中输出一些不需要的内容。通过判断__name__变量的值,我们就可以让测试代码只在模块被直接运行时执行,而在被导入时不执行。这是一种非常优雅的做法,也是Python开发者必须掌握的编程习惯。
在模块中添加测试代码
下面的例子演示了如何在score_utils.py模块中添加测试代码。我们在模块末尾加上if __name__ == "__main__":这段代码,其中的测试代码只会在直接运行模块时执行,而不会在被导入时执行。
代码 6-63:在模块中添加测试代码
# score_utils.py ------ 在模块末尾添加测试代码
"""
成绩处理工具模块
提供成绩计算、统计、等级判断等功能
"""
# ...(前面的函数定义保持不变)...
# ========== 模块测试代码 ==========
# 下面这段代码只在直接运行本文件时执行
# 当其他程序import本模块时不会执行
if __name__ == "__main__":
print("=== score_utils 模块测试 ===")
print()
# 测试数据
test_scores = [85, 92, 78, 96, 63, 71, 88, 55, 90, 82]
print("测试数据:", test_scores)
print()
# 测试 calc_average
avg = calc_average(test_scores)
print("calc_average 测试:")
print(" 平均分 = " + str(avg))
print()
# 测试 find_max 和 find_min
print("find_max / find_min 测试:")
print(" 最高分 = " + str(find_max(test_scores)))
print(" 最低分 = " + str(find_min(test_scores)))
print()
# 测试 get_grade
print("get_grade 测试:")
test_score = 85
print(" " + str(test_score) + "分 -> " + get_grade(test_score))
print()
# 测试 count_grades
print("count_grades 测试:")
grades = count_grades(test_scores)
for grade, count in grades.items():
print(" " + grade + ":" + str(count) + "人")
print()
print("=== 测试完成 ===")
当我们直接运行python score_utils.py时,__name__的值为"__main__",if条件成立,测试代码会被执行,输出各个函数的测试结果。而当其他程序通过import score_utils导入这个模块时,__name__的值为"score_utils",if条件不成立,测试代码不会被执行,因此不会影响主程序的正常运行。这种写法是Python模块开发中的标准做法,在编写自定义模块时,都应该养成在模块末尾添加if __name__ == "__main__":测试代码的习惯。
