第十九章 标准内置库详解 1、时间处理模块详解 在 Python 开发中,处理日期和时间是常见的需求,无论是记录日志、数据打点、任务调度还是处理用户输入,都离不开时间模块。Python 标准库主要提供了 time
和 datetime
两个模块来处理时间
time
模块time
模块提供了对C语言库中时间相关函数的底层访问接口,主要用于获取时间戳、进行时间格式转换以及控制程序执行流程(如暂停)。它处理的时间通常与操作系统的本地时间或UTC(协调世界时)相关。
time
模块常用函数下表列出了 time
模块中一些核心且常用的函数:
函数 描述 time.time()
返回当前时间的时间戳(自1970年1月1日00:00:00 UTC到现在的浮点秒数)。 time.localtime([secs])
将一个时间戳(默认为 time.time()
的返回值)转换为本地时间的 struct_time
对象。如果未提供 secs
,则使用当前时间。 time.gmtime([secs])
将一个时间戳(默认为 time.time()
的返回值)转换为 UTC(格林威治标准时间)的 struct_time
对象。如果未提供 secs
,则使用当前时间。 time.mktime(t)
time.localtime()
的反函数。接收一个 struct_time
对象(本地时间)作为参数,并返回其对应的时间戳(浮点秒数)。time.asctime([t])
接收一个 struct_time
对象(默认为 time.localtime()
的返回值)并返回一个24个字符的可读字符串,例如:'Mon May 12 11:18:35 2025'
。不推荐在新代码中使用。 time.strftime(format[, t])
将 struct_time
对象(默认为 time.localtime()
的返回值)按照指定的 format
字符串格式化为时间字符串。这是最常用的时间格式化函数。 time.strptime(string, format)
将时间字符串 string
按照指定的 format
解析为 struct_time
对象。 time.sleep(secs)
暂停当前程序的执行指定的秒数 secs
(可以是浮点数,表示更精确的时间)。 time.perf_counter()
返回一个具有最高可用精度的性能计数器的值(以秒为单位),用于测量短时间间隔。它的参考点是未定义的,因此只有连续调用的结果之间的差异才是有效的。 time.process_time()
返回当前进程的CPU执行时间(用户时间 + 系统时间)的总和(以秒为单位)。不包括睡眠时间。
批注:核心记忆函数
time.time()
: 获取时间戳,是很多时间操作的基础。time.localtime()
: 将时间戳转为本地时间元组,便于访问时间的各个部分。time.strftime()
: 强烈推荐记忆 ,用于将时间元RSA组格式化为任意字符串,非常灵活和常用。time.strptime()
: 用于将特定格式的字符串解析回时间元组。time.sleep()
: 控制程序执行节奏,非常实用。time.mktime()
: 在需要将本地时间元组转换回时间戳时使用。struct_time
对象详解time.localtime()
和 time.gmtime()
函数返回的是 struct_time
对象,它是一个包含九个整数的元组(实际上是一个具有命名属性的元组,因此可以通过索引和属性名访问)。
索引 属性名 值范围 含义 0 tm_year
例如, 2025 年份 1 tm_mon
1 到 12 月份 2 tm_mday
1 到 31 日期 3 tm_hour
0 到 23 小时 4 tm_min
0 到 59 分钟 5 tm_sec
0 到 61 (闰秒) 秒 6 tm_wday
0 到 6 (周一为0) 星期几 7 tm_yday
1 到 366 一年中的第几天 8 tm_isdst
0, 1, 或 -1 是否为夏令时
strftime
和 strptime
常用格式化符号strftime
用于将时间元组格式化为字符串,而 strptime
则相反,用于将字符串解析为时间元组。它们使用相同的格式化代码。
格式符 含义 示例 (基于 2025-05-12 14:30:45 周一) %Y
四位数的年份 2025
%y
两位数的年份 (00-99) 25
%m
两位数的月份 (01-12) 05
%d
两位数的日期 (01-31) 12
%H
24小时制的小时 (00-23) 14
%I
12小时制的小时 (01-12) 02
%M
两位数的分钟 (00-59) 30
%S
两位数的秒数 (00-61) 45
%A
完整的星期几名称 (本地化) Monday
(英文环境)%a
简写的星期几名称 (本地化) Mon
(英文环境)%B
完整的月份名称 (本地化) May
(英文环境)%b
简写的月份名称 (本地化) May
(英文环境)%w
星期几 (0-6,周日为0,周一为1,…周六为6) 1
(如果周日是0)%j
一年中的第几天 (001-366) 132
%p
AM/PM (本地化) PM
%c
本地化的日期和时间表示 Mon May 12 14:30:45 2025
%x
本地化的日期表示 05/12/25
(美式)%X
本地化的时间表示 14:30:45
%%
一个字面的 ‘%’ 字符 %
注意 : %w
的行为(周日是0还是周一是0)可能因平台而异。struct_time
中的 tm_wday
始终是周一为0。
time
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import timecurrent_timestamp = time.time() print (f"当前时间戳: {current_timestamp} " ) local_time_struct = time.localtime(current_timestamp) print (f"本地时间元组 (struct_time): {local_time_struct} " )print (f" 年份 (tm_year): {local_time_struct.tm_year} " )print (f" 月份 (tm_mon): {local_time_struct.tm_mon} " )print (f" 日期 (tm_mday): {local_time_struct.tm_mday} " )print (f" 小时 (tm_hour): {local_time_struct.tm_hour} " )print (f" 分钟 (tm_min): {local_time_struct.tm_min} " )print (f" 秒数 (tm_sec): {local_time_struct.tm_sec} " )print (f" 星期几 (tm_wday, 周一=0): {local_time_struct.tm_wday} " )print (f" 一年中的第几天 (tm_yday): {local_time_struct.tm_yday} " )print (f" 夏令时 (tm_isdst): {local_time_struct.tm_isdst} " )utc_time_struct = time.gmtime(current_timestamp) print (f"UTC时间元组 (struct_time): {utc_time_struct} " )print (f" UTC 年份: {utc_time_struct.tm_year} , UTC 小时: {utc_time_struct.tm_hour} " )timestamp_from_local_struct = time.mktime(local_time_struct) print (f"从本地时间元组转换回的时间戳: {timestamp_from_local_struct} " ) readable_time_asctime = time.asctime(local_time_struct) print (f"asctime() 格式的时间: {readable_time_asctime} " ) formatted_time_custom = time.strftime("%Y-%m-%d %H:%M:%S %A" , local_time_struct) print (f"strftime() 自定义格式化时间: {formatted_time_custom} " ) time_string = "2024-07-20 10:30:00" format_string = "%Y-%m-%d %H:%M:%S" parsed_time_struct = time.strptime(time_string, format_string) print (f"strptime() 解析 '{time_string} ' 结果: {parsed_time_struct} " )print (f" 解析得到的年份: {parsed_time_struct.tm_year} " )print ("程序开始执行..." )time.sleep(1.5 ) print ("程序暂停1.5秒后继续执行。" )start_perf = time.perf_counter() for _ in range (100000 ): pass end_perf = time.perf_counter() duration_perf = end_perf - start_perf print (f"高性能计数器测量的操作耗时: {duration_perf:.6 f} 秒" )start_process = time.process_time() total = 0 for i in range (1000000 ): total += i end_process = time.process_time() duration_process = end_process - start_process print (f"CPU密集型操作的进程耗时: {duration_process:.6 f} 秒" )
time
模块开箱即用函数/场景a. 获取特定格式的当前时间字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import timedef get_current_time_str (format_str="%Y-%m-%d %H:%M:%S" ): """ 获取当前本地时间,并按指定格式返回字符串。 参数: format_str (str): strftime 使用的时间格式化字符串。 返回: str: 格式化后的当前时间字符串。 """ return time.strftime(format_str, time.localtime()) print (f"当前日期时间: {get_current_time_str()} " ) print (f"当前日期 (YYYYMMDD): {get_current_time_str('%Y%m%d' )} " ) print (f"当前时间 (HH-MM-SS): {get_current_time_str('%H-%M-%S' )} " )
b. 测量函数执行时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import timedef measure_execution_time (func, *args, **kwargs ): """ 测量并打印指定函数执行所需的时间。 参数: func (callable): 需要测量执行时间的函数。 *args: 传递给 func 的位置参数。 **kwargs: 传递给 func 的关键字参数。 返回: any: func 的返回值。 """ start_time = time.perf_counter() result = func(*args, **kwargs) end_time = time.perf_counter() print (f"函数 '{func.__name__} ' 执行耗时: {end_time - start_time:.6 f} 秒" ) return result def example_function (n ): time.sleep(n) return f"操作完成,参数为 {n} " measure_execution_time(example_function, 0.5 ) measure_execution_time(sum , range (1000000 ))
c. 时间戳与格式化字符串之间的转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import timedef timestamp_to_str (ts, format_str="%Y-%m-%d %H:%M:%S" ): """将时间戳转换为指定格式的本地时间字符串。""" return time.strftime(format_str, time.localtime(ts)) def str_to_timestamp (time_str, format_str="%Y-%m-%d %H:%M:%S" ): """将指定格式的时间字符串转换为本地时间戳。""" struct_t = time.strptime(time_str, format_str) return time.mktime(struct_t) ts_now = time.time() print (f"当前时间戳: {ts_now} " )formatted_str = timestamp_to_str(ts_now) print (f"时间戳 -> 字符串: {formatted_str} " )back_to_ts = str_to_timestamp(formatted_str) print (f"字符串 -> 时间戳: {back_to_ts} " )
datetime
模块datetime
模块是 Python 中处理日期和时间的更高级、更面向对象的接口。它定义了几种类,用于处理日期、时间、日期时间、时间差以及时区信息。相比 time
模块,datetime
模块的功能更强大,使用更直观,尤其适合进行复杂的日期时间运算和时区处理。
datetime
模块核心类/方法类/方法 描述 datetime.date(year, month, day)
表示一个理想化的日期对象,具有 year
, month
, day
属性。 date.today()
(类方法) 返回当前的本地日期。 date.fromtimestamp(timestamp)
(类方法) 根据给定的时间戳返回本地日期。 datetime.time(hour, minute, second, ...)
表示一个理想化的时间对象,具有 hour
, minute
, second
, microsecond
, tzinfo
属性。 datetime.datetime(year, month, day, ...)
表示日期和时间的组合,包含 date
和 time
对象的所有信息。 datetime.now([tz])
(类方法) 返回当前的本地日期和时间。如果提供了可选参数 tz
(一个 tzinfo
对象),则返回对应时区的日期时间。 datetime.utcnow()
(类方法) 返回当前的UTC日期和时间,但返回的是一个 naive datetime 对象 (无时区信息)。不推荐使用,请用 datetime.now(timezone.utc)
替代。 datetime.fromtimestamp(timestamp, [tz])
(类方法) 根据时间戳返回本地日期和时间。如果提供了 tz
,则返回对应时区的日期时间。 datetime.strptime(date_string, format)
(类方法) 将符合 format
格式的日期字符串 date_string
解析为 datetime
对象。 obj.strftime(format)
(实例方法) 将 date
, time
或 datetime
对象 obj
格式化为字符串。 datetime.timedelta(days, seconds, ...)
表示两个日期或时间之间的时间差,可以用于日期时间的加减运算。 datetime.timezone(offset, [name])
tzinfo
的一个具体子类,表示一个固定的UTC偏移量。例如 timezone.utc
代表UTC时区。
批注:核心记忆类与方法
datetime.datetime
: 最核心的类 ,用于表示精确的日期和时间。datetime.date
: 当只需要日期部分时使用。datetime.time
: 当只需要时间部分时使用。datetime.timedelta
: 非常重要 ,用于进行日期时间的加减运算。datetime.now()
: 获取当前日期时间。datetime.strptime()
: 常用 ,将字符串解析为 datetime
对象。obj.strftime()
: 常用 ,将 datetime
对象格式化为字符串。datetime.timezone.utc
: 用于创建UTC时区感知的 datetime
对象。datetime
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 from datetime import *from print_utils import *print_header("1. 获取当前日期时间" ) dt_now = datetime.now() print_info(f"当前日期时间 (datetime.now()): {dt_now} " ) print_info(f" 年份: {dt_now.year} , 月份: {dt_now.month} , 日期: {dt_now.day} " ) print_info(f" 小时: {dt_now.hour} , 分钟: {dt_now.minute} , 秒数: {dt_now.second} , 微秒: {dt_now.microsecond} " ) print_header("2. 获取当前UTC日期时间" ) utc_now = datetime.now(timezone.utc) print_info(f"当前UTC日期时间 (datetime.now(timezone.utc)): {utc_now} " ) print_header("3. 创建特定的日期时间对象" ) special_dt = datetime(2025 , 1 , 1 , 12 , 0 , 0 ) print_info(f"特定日期时间对象: {special_dt} " ) print_header("4. 创建 date 对象 和 time 对象" ) specific_date = date(2025 , 10 , 20 ) print_info(f"特定日期 (date): {specific_date} " ) print_info(f" 年份: {specific_date.year} , 月份: {specific_date.month} , 日期: {specific_date.day} " ) print_info(f" 星期几 (weekday(), 周一=0): {specific_date.weekday()} " ) specific_time = time(12 , 0 , 0 ) print_info(f"特定时间 (time): {specific_time} " ) print_info(f" 小时: {specific_time.hour} , 分钟: {specific_time.minute} , 秒数: {specific_time.second} " ) print_header("5. 日期时间格式化 (strftime)" ) formatted_dt = dt_now.strftime("%Y年%m月%d日 %H时%M分%S秒 星期%A" ) print_info(f"格式化后的日期时间: {formatted_dt} " ) print_header("6. 日期时间解析 (strptime)" ) parsed_dt = datetime.strptime("2025-10-20 12:00:00" , "%Y-%m-%d %H:%M:%S" ) print_info(f"解析后的日期时间: {parsed_dt} " ) print_header("7. 时间差 (timedelta)" ) delta1 = timedelta(days=5 , hours=3 , minutes=30 ) print_info(f"时间差: {delta1} " ) print_header("8. 日期时间运算" ) future_dt = dt_now + delta1 print_info(f"未来日期时间: {future_dt} " ) past_dt = dt_now - timedelta(weeks=2 ) print_info(f"当前时间减去2周后: {past_dt} " ) specific_dt = datetime(2025 , 10 , 20 , 12 , 0 , 0 ) diff_dt = specific_dt - dt_now print_info(f"两个datetime对象之间的差: {diff_dt} " ) print_info(f" 相差天数: {diff_dt.days} " ) print_info(f" 相差总秒数: {diff_dt.total_seconds()} " ) print_header("9. datetime 与时间戳转换" ) current_timestamp = dt_now.timestamp() print_info(f"当前datetime对象对应的时间戳: {current_timestamp} " ) dt_from_ts_local = datetime.fromtimestamp(current_timestamp) print_info(f"从时间戳转换回的本地datetime: {dt_from_ts_local} " ) dt_from_ts_utc = datetime.fromtimestamp(current_timestamp, tz=timezone.utc) print_info(f"从时间戳转换回的UTC datetime: {dt_from_ts_utc} " ) print_header("10. 替换日期时间的某些部分" ) replaced_dt = dt_now.replace(year=2030 , microsecond=0 ) print_info(f"替换年份和微秒后的datetime: {replaced_dt} " ) print_header("11. 时区处理" ) beijing_tz = timezone(timedelta(hours=8 ), name='Asia/Shanghai' ) beijing_now = datetime.now(beijing_tz) print_info(f"当前北京时间: {beijing_now} " ) print_info(f"北京时间的时区信息: {beijing_now.tzinfo} " ) naive_dt = datetime(2025 , 1 , 1 , 12 , 0 , 0 ) aware_dt_beijing = naive_dt.replace(tzinfo=beijing_tz) print_info(f"Naive datetime 赋予北京时区: {aware_dt_beijing} " ) utc_dt_example = aware_dt_beijing.astimezone(timezone.utc) print_info(f"北京时间转换为UTC时间: {utc_dt_example} " )
datetime
模块开箱即用函数/场景a. 获取不同精度的当前时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from datetime import datetime, timezonedef get_formatted_current_datetime (fmt="%Y-%m-%d %H:%M:%S.%f" , use_utc=False ): """ 获取格式化的当前日期时间字符串。 参数: fmt (str): strftime 使用的时间格式化字符串。 '%f' 可以用于表示微秒。 use_utc (bool): 如果为True,则返回UTC时间,否则返回本地时间。 返回: str: 格式化后的当前日期时间字符串。 """ if use_utc: now_dt = datetime.now(timezone.utc) else : now_dt = datetime.now() return now_dt.strftime(fmt) print (f"本地当前精确时间: {get_formatted_current_datetime()} " )print (f"UTC当前日期 (YYYY-MM-DD): {get_formatted_current_datetime(use_utc=True )} " )
b. 计算未来或过去的日期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from datetime import datetime, timedeltadef calculate_future_past_date (days_offset=0 , weeks_offset=0 , hours_offset=0 , base_date=None ): """ 根据偏移量计算未来或过去的日期时间。 参数: days_offset (int): 天数偏移,正数表示未来,负数表示过去。 weeks_offset (int): 周数偏移。 hours_offset (int): 小时数偏移。 base_date (datetime, optional): 基准日期时间,默认为当前本地时间。 返回: datetime: 计算后的日期时间对象。 """ if base_date is None : base_date = datetime.now() offset = timedelta(days=days_offset, weeks=weeks_offset, hours=hours_offset) return base_date + offset one_week_later = calculate_future_past_date(weeks_offset=1 ) print (f"一周后的日期时间: {one_week_later.strftime('%Y-%m-%d %H:%M' )} " )three_days_ago_noon = calculate_future_past_date(days_offset=-3 , base_date=datetime(2025 ,5 ,15 ,12 ,0 ,0 )) print (f"三天前的中午12点: {three_days_ago_noon.strftime('%Y-%m-%d %H:%M' )} " )
c. 安全地解析日期字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from datetime import datetimedef parse_date_string_safely (date_str, date_format="%Y-%m-%d" ): """ 尝试按指定格式解析日期字符串,如果失败则返回None。 参数: date_str (str): 要解析的日期字符串。 date_format (str): datetime.strptime 使用的日期格式。 返回: datetime.date or None: 解析成功则返回date对象,否则返回None。 """ try : dt_object = datetime.strptime(date_str, date_format) return dt_object.date() except ValueError: print (f"错误: 字符串 '{date_str} ' 无法匹配格式 '{date_format} '" ) return None valid_date_str = "2024-12-25" invalid_date_str = "25/12/2024" parsed_date1 = parse_date_string_safely(valid_date_str) if parsed_date1: print (f"成功解析 '{valid_date_str} ': {parsed_date1} " ) parsed_date2 = parse_date_string_safely(invalid_date_str) if parsed_date2 is None : print (f"解析 '{invalid_date_str} ' 失败。" ) parsed_date3 = parse_date_string_safely("31.01.2023" , date_format="%d.%m.%Y" ) if parsed_date3: print (f"成功解析 '31.01.2023': {parsed_date3} " )
d. 计算两个日期之间的天数/小时数/分钟数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from datetime import datetimedef get_difference_between_datetimes (dt1_str, dt2_str, fmt="%Y-%m-%d %H:%M:%S" ): """ 计算两个日期时间字符串之间的差异。 参数: dt1_str (str): 第一个日期时间字符串。 dt2_str (str): 第二个日期时间字符串。 fmt (str): 日期时间字符串的格式。 返回: dict or None: 包含天数、总小时数、总分钟数、总秒数的字典,如果解析失败返回None。 """ try : dt1 = datetime.strptime(dt1_str, fmt) dt2 = datetime.strptime(dt2_str, fmt) except ValueError: print ("日期时间字符串格式错误或无效。" ) return None if dt1 > dt2: delta = dt1 - dt2 sign = -1 else : delta = dt2 - dt1 sign = 1 return { "days" : sign * delta.days, "total_hours" : sign * (delta.total_seconds() / 3600 ), "total_minutes" : sign * (delta.total_seconds() / 60 ), "total_seconds" : sign * delta.total_seconds(), "timedelta_object" : delta if sign == 1 else -delta } start_time = "2025-05-10 09:00:00" end_time = "2025-05-12 11:30:45" diff = get_difference_between_datetimes(start_time, end_time) if diff: print (f"从 '{start_time} ' 到 '{end_time} ':" ) print (f" 相差天数: {diff['days' ]} " ) print (f" 总小时数: {diff['total_hours' ]:.2 f} " ) print (f" 总分钟数: {diff['total_minutes' ]:.2 f} " ) print (f" 总秒数: {diff['total_seconds' ]:.0 f} " ) print (f" Timedelta对象: {diff['timedelta_object' ]} " ) diff_reverse = get_difference_between_datetimes(end_time, start_time) if diff_reverse: print (f"\n从 '{end_time} ' 到 '{start_time} ':" ) print (f" 相差天数: {diff_reverse['days' ]} " )
time
与 datetime
模块的选择与注意事项精度 :time.time()
返回的时间戳通常具有微秒级精度(取决于操作系统)。datetime
对象也支持微秒。易用性 :对于复杂的日期时间运算、时区处理以及更清晰的API,datetime
模块通常是更好的选择。底层访问 :如果你需要与C库或其他需要时间戳(秒数)的系统交互,time.time()
很直接。时区处理 :time
模块对时区的支持有限,time.localtime()
依赖于系统本地时区,time.gmtime()
提供UTC时间。datetime
模块通过 tzinfo
抽象基类及其子类(如 timezone
)提供了更健壮的时区处理能力。处理跨时区应用时,强烈建议使用 datetime
并明确指定时区(通常将所有时间转换为UTC存储和处理,仅在显示时转换为本地时区)。字符串转换 :strftime
和 strptime
是这两个模块共有的重要功能,但它们作用的对象类型不同 (struct_time
vs datetime
对象)。datetime
对象的 strftime
方法通常更易用。不可变性 :date
, time
, datetime
对象都是不可变的,这使得它们在用作字典键或在多线程环境中更安全。ISO 8601 格式 :在进行数据交换时,推荐使用 ISO 8601 标准格式(例如 YYYY-MM-DDTHH:MM:SS.ffffff
或 YYYY-MM-DDTHH:MM:SSZ
表示UTC)。datetime
对象有 isoformat()
方法可以直接生成此格式。在现代 Python 开发中,除非有特定需求要使用 time
模块的底层功能或性能计数器,否则**datetime
模块通常是进行日期和时间操作的首选**
2、随机数模块详解 在编程中,随机数扮演着重要的角色,可用于模拟、游戏、数据抽样、生成测试数据等多种场景。Python 的标准库通过 random
模块提供了生成伪随机数的工具。
random
模块介绍random
模块实现了用于各种分布的伪随机数生成器。这些是伪随机数 (Pseudo-random numbers) ,意味着它们是通过一个确定性的算法生成的,但其序列在统计上看起来是随机的。如果不显式设置“种子”(seed),序列的起点通常是不可预测的。
random
模块常用函数下表列出了 random
模块中一些核心且常用的函数:
函数 描述 random.seed(a=None, version=2)
初始化随机数生成器,让随机序列可以被复现。 random.getstate()
获取随机数生成器当前的内部状态。 random.setstate(state)
恢复随机数生成器到之前保存的状态。 random.random()
返回一个 $0.0$ 到 $1.0$ 之间 (不包括 $1.0$) 的随机小数。 random.uniform(a, b)
返回一个在 $a$ 和 $b$ 之间的随机小数。 random.randint(a, b)
返回一个在 $a$ 和 $b$ 之间 (包括 $a$ 和 $b$) 的随机整数。 random.randrange(start, stop[, step])
从一个按步长生成的整数序列中随机选一个数 (不包括 stop
)。 random.choice(seq)
从一个列表、元组或字符串等序列中随机选择一个元素。 random.choices(population, ..., k=1)
从总体中进行 k
次可重复的随机抽样,可以设置权重。 random.sample(population, k)
从总体中随机选择 k
个不重复的元素。 random.shuffle(x[, random])
将一个列表中的元素顺序随机打乱 (直接修改原列表)。 random.gauss(mu, sigma)
生成一个符合正态分布 (高斯分布) 的随机数,mu
是平均值,sigma
是标准差。 random.expovariate(lambd)
生成一个符合指数分布的随机数。
批注:核心记忆函数
random.seed()
: 需要结果可复现时使用。random.random()
: 生成基础随机小数。random.randint(a, b)
: 非常常用 ,获取范围内随机整数。random.choice(seq)
: 非常常用 ,从序列中随机挑一个。random.sample(population, k)
: 选取多个不重复项。random.shuffle(x)
: 打乱列表顺序。random.choices(population, weights=..., k=...)
: 带权重或可重复抽样时使用。random
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import randomfrom print_utils import *print_header("随机数种子设置" ) print_info(f"使用种子101生成的第一个随机数: {random.random()} " ) print_info(f"使用种子101生成的第二个随机数: {random.random()} " ) print_info(f"重置种子101后生成的第一个随机数: {random.random()} " ) print_header("生成随机浮点数" ) print_info(f"生成的随机浮点数: {random.random()} " ) print_header("生成指定范围内的随机浮点数" ) rand_float_range = random.uniform(5.5 , 15.5 ) print_info(f"生成的随机浮点数 (5~15.5): {rand_float_range} " ) print_header("生成随机范围的随机整数" ) rand_int_range = random.randint(10 , 20 ) print_info(f"生成的随机整数 (10 到 20): {rand_int_range} " ) print_header("生成指定步长的随机整数" ) rand_int_step = random.randrange(0 , 100 , 5 ) print_info(f"生成的随机整数 (0 到 100, 步长5): {rand_int_step} " ) print_header("从序列中随机选择一个元素" ) my_colors = ['红' , '橙' , '黄' , '绿' , '蓝' , '紫' ] random_color = random.choice(my_colors) print (f"颜色列表: {my_colors} " )print (f"随机选择的颜色 (choice): {random_color} " )print_header("从序列中随机选择k个元素 (可重复)" ) elements_pool = ['石头' , '剪刀' , '布' ] element_weights = [0.8 , 0.1 , 0.1 ] chosen_one_weighted = random.choices(elements_pool, weights=element_weights) print_info(f"带权重选择1个元素 (choices): {chosen_one_weighted} " ) chosen_five_weighted = random.choices(elements_pool, weights=element_weights, k=5 ) print_info(f"带权重选择5个可重复元素 (choices): {chosen_five_weighted} " ) print_header("随机打乱新序列元素的顺序" ) game_players = ['玩家A' , '玩家B' , '玩家C' , '玩家D' ] print (f"原始玩家顺序: {game_players} " )random.shuffle(game_players) print (f"打乱后的玩家顺序 (shuffle): {game_players} " )
random
模块开箱即用函数/场景a. 生成指定长度的随机字符串(用于非加密场景) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import randomimport stringdef generate_simple_random_string (length=10 , use_letters=True , use_digits=True , use_punctuation=False ): """ 生成一个指定长度的简单随机字符串。 参数: length (int): 生成字符串的长度。 use_letters (bool): 是否包含大小写字母。 use_digits (bool): 是否包含数字。 use_punctuation (bool): 是否包含标点符号。 返回: str: 生成的随机字符串。 """ char_pool = "" if use_letters: char_pool += string.ascii_letters if use_digits: char_pool += string.digits if use_punctuation: char_pool += string.punctuation if not char_pool: char_pool = string.ascii_lowercase return '' .join(random.choice(char_pool) for _ in range (length)) simple_id = generate_simple_random_string(8 ) print (f"生成的8位随机ID (字母数字): {simple_id} " )complex_code = generate_simple_random_string(12 , use_punctuation=True ) print (f"生成的12位复杂随机码: {complex_code} " )
安全提示 :此函数生成的随机字符串不应用于密码、会话令牌等安全敏感的场景。对于这些场景,请务必使用 Python 的 secrets
模块,它能提供密码学强度的随机性。
b. 模拟多次掷骰子并统计结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import randomfrom collections import Counterdef simulate_dice_rolls (num_rolls=100 , num_sides=6 ): """ 模拟多次掷骰子并返回每次的结果及统计。 参数: num_rolls (int): 掷骰子的次数。 num_sides (int): 骰子的面数。 返回: tuple: (包含所有掷骰结果的列表, 结果计数的Counter对象) """ rolls = [random.randint(1 , num_sides) for _ in range (num_rolls)] counts = Counter(rolls) return rolls, counts rolls_100, counts_100 = simulate_dice_rolls(100 ) print (f"\n模拟掷100次6面骰子:" )print (f" 结果统计: {counts_100} " )print (f" 最常出现的点数: {counts_100.most_common(1 )} " )
c. 从加权列表中随机抽取奖励 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import randomdef get_weighted_reward (rewards_with_weights ): """ 根据权重从奖励列表中随机抽取一个奖励。 参数: rewards_with_weights (dict): 一个字典,键是奖励,值是对应的权重 (整数或浮点数)。 例如: {'金币': 50, '装备': 30, '药水': 20} 返回: any: 抽中的奖励。 """ if not rewards_with_weights: return None population = list (rewards_with_weights.keys()) weights = list (rewards_with_weights.values()) return random.choices(population, weights=weights, k=1 )[0 ] daily_rewards = { '100 金币' : 60 , '小体力药剂' : 25 , '随机材料包' : 10 , '稀有装备碎片' : 5 } print ("\n模拟每日签到奖励抽取5次:" )for day in range (1 , 6 ): reward = get_weighted_reward(daily_rewards) print (f" 第 {day} 天签到获得: {reward} " )
d. 生成一个范围内的随机日期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import randomfrom datetime import date, timedeltadef get_random_date (start_date_str, end_date_str, date_format="%Y-%m-%d" ): """ 在给定的开始日期和结束日期之间生成一个随机日期。 参数: start_date_str (str): 开始日期字符串。 end_date_str (str): 结束日期字符串。 date_format (str): 日期字符串的格式。 返回: datetime.date: 生成的随机日期对象,如果日期解析失败则返回None。 """ try : start_dt = date.strptime(start_date_str, date_format) end_dt = date.strptime(end_date_str, date_format) except ValueError: print ("日期格式错误或日期无效。" ) return None if start_dt > end_dt: print ("开始日期不能晚于结束日期。" ) return None time_between_dates = end_dt - start_dt days_between_dates = time_between_dates.days if days_between_dates < 0 : return None random_number_of_days = random.randrange(days_between_dates + 1 ) random_date = start_dt + timedelta(days=random_number_of_days) return random_date start = "2024-01-01" end = "2024-12-31" random_birthday = get_random_date(start, end) if random_birthday: print (f"\n在 {start} 和 {end} 之间随机生成的生日: {random_birthday.strftime('%Y年%m月%d日' )} " ) invalid_start = "2025-01-01" get_random_date(invalid_start, end)
3、操作系统交互与文件处理模块 在 Python 编程中,与操作系统进行交互(如管理文件和目录、执行系统命令)以及处理文件(如复制、移动、压缩)是非常常见的任务。Python 标准库为此提供了多个强大的模块,主要包括 os
、shutil
、zipfile
和 tarfile
。
os
模块:与操作系统交互的基础(核心)os
模块提供了一种可移植的方式来使用操作系统的功能,例如读取或写入文件、操作目录路径、获取环境变量等。它的一个重要子模块是 os.path
,专门用于处理路径字符串。
os
及 os.path
常用功能方法/属性 描述 os.getcwd()
获取当前Python脚本的工作目录路径。 os.chdir(path)
更改当前Python脚本的工作目录到 path
。 os.listdir(path='.')
列出指定目录 path
下的所有文件和文件夹名称。 os.mkdir(path)
创建一个名为 path
的新目录 (如果路径中的父目录不存在会失败)。 os.makedirs(path, exist_ok=False)
递归创建多级目录,如果 exist_ok=True
则目录已存在时不会报错。 os.remove(path)
删除指定路径 path
的文件。 os.rmdir(path)
删除指定路径 path
的空目录 (如果目录非空则会失败)。 os.rename(src, dst)
重命名文件或目录,从 src
名称改为 dst
名称。 os.environ
一个表示环境变量的字典,可以用来获取或设置环境变量。 os.system(command)
在系统的shell中执行 command
命令 (谨慎使用,有安全风险)。 os.path.join(path, *paths)
强烈推荐 :智能地拼接一个或多个路径部分,自动处理不同操作系统的分隔符。os.path.exists(path)
检查指定的路径 path
是否存在 (文件或目录)。 os.path.isfile(path)
检查指定的路径 path
是否是一个文件。 os.path.isdir(path)
检查指定的路径 path
是否是一个目录。 os.path.getsize(path)
获取指定文件 path
的大小 (以字节为单位)。 os.path.basename(path)
获取路径 path
中的文件名部分。 os.path.dirname(path)
获取路径 path
中的目录部分。 os.path.abspath(path)
获取路径 path
的绝对路径。 os.path.splitext(path)
将路径 path
分割为 (文件名主体, 扩展名) 的元组。
批注:核心记忆功能 (os
和 os.path
)
os.getcwd()
, os.chdir()
: 管理当前工作目录。os.listdir()
: 查看目录内容。os.mkdir()
, os.makedirs()
: 创建目录。os.remove()
, os.rmdir()
: 删除文件和空目录。os.path.join()
: 极其重要 ,用于跨平台安全地构造路径。os.path.exists()
, os.path.isfile()
, os.path.isdir()
: 判断路径类型。os.path.basename()
, os.path.dirname()
, os.path.splitext()
: 解析路径字符串。os
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 from print_utils import *import osprint_header("目录操作" ) current_directory = os.getcwd() print_info(f"当前工作目录: {current_directory} " ) new_dir_name = "my_test_directory" if not os.path.exists(new_dir_name): os.mkdir(new_dir_name) print_info(f"目录 '{new_dir_name} ' 已创建。" ) multi_level_dir = os.path.join("parent_dir" , "child_dir" ) os.makedirs(multi_level_dir, exist_ok=True ) print_info(f"多级目录 '{multi_level_dir} ' 已创建或已存在。" ) print_info(f"当前目录内容: {os.listdir('.' )[:5 ]} " ) print_header("路径操作 (os.path)" ) file_path_str = "/usr/local/bin/python_script.py" print_info(f"处理路径: '{file_path_str} '" ) print_info(f" 目录名: {os.path.dirname(file_path_str)} " ) print_info(f" 文件名: {os.path.basename(file_path_str)} " ) print_info(f" 扩展名: {os.path.splitext(file_path_str)[1 ]} " ) print_info(f" 分割 (主体, 扩展名): {os.path.splitext(file_path_str)} " ) path1 = "projects" path2 = "my_project" filename = "main.py" full_project_path = os.path.join(current_directory, path1, path2, filename) print_info(f"使用 os.path.join 拼接的路径: {full_project_path} " ) print_header("文件操作" ) test_file_name = "temp_file.txt" with open (test_file_name, "w" ) as f: f.write("Hello from os module example!" ) if os.path.exists(test_file_name): print_info(f"文件 '{test_file_name} ' 存在。" ) print_info(f" 是否是文件: {os.path.isfile(test_file_name)} " ) print_info(f" 文件大小: {os.path.getsize(test_file_name)} 字节" ) renamed_file_name = "renamed_temp_file.txt" os.rename(test_file_name, renamed_file_name) print_info(f"文件已重命名为: '{renamed_file_name} '" ) os.remove(renamed_file_name) print_info(f"文件 '{renamed_file_name} ' 已删除。" ) else : print_info(f"文件 '{test_file_name} ' 不存在。" ) print_header("环境变量" ) python_path = os.environ.get('PYTHONPATH' ) print_info(f"环境变量 PYTHONPATH: {python_path if python_path else '未设置' } " ) print_header("执行系统命令" ) print_info("更好的方式是使用 subprocess 模块,它提供了更强的控制能力和安全性。" ) import shutildef remove_directory (directory_path ): if os.path.exists(directory_path): shutil.rmtree(directory_path) print_info(f"目录 '{directory_path} ' 已成功删除。" ) else : print_info(f"目录 '{directory_path} ' 不存在。" ) remove_directory("parent_dir" )
坑点与建议 (os
模块) :
路径拼接 :务必使用 os.path.join()
来构造路径,它会自动处理不同操作系统(Windows \
vs Linux/macOS /
)的路径分隔符,增强代码的可移植性。os.system()
:尽量避免使用 os.system()
,尤其当命令包含用户输入时,可能存在安全风险(命令注入)。对于需要执行外部命令的场景,subprocess
模块是更安全、更灵活的选择。os.remove()
vs os.rmdir()
:os.remove()
用于删除文件,os.rmdir()
用于删除空目录。如果要删除非空目录及其所有内容,请使用 shutil.rmtree()
。权限问题 :文件和目录操作可能会因为权限不足而失败,应使用 try-except PermissionError
来捕获和处理这类异常。pathlib
模块 :对于更现代、面向对象的文件系统路径操作,Python 3.4+ 引入的 pathlib
模块是一个很好的选择,它提供了更直观的 APi,但从使用层面受众程度来说,使用os模块的程序员会占更多数shutil
模块:高级文件操作shutil
(shell utilities) 模块提供了许多高级的文件和文件集合操作功能,例如复制、移动、删除整个目录树等,是对 os
模块中基本文件操作的补充。
shutil
常用函数函数 描述 shutil.copy(src, dst)
复制文件 src
到 dst
(如果 dst
是目录,则复制到该目录下)。 shutil.copy2(src, dst)
类似 copy()
,但同时会尝试复制文件的元数据 (如权限、时间戳)。 shutil.copyfile(src, dst)
仅复制文件内容从 src
到 dst
(不复制元数据)。 shutil.copytree(src, dst, dirs_exist_ok=False)
递归地复制整个目录树从 src
到 dst
。 shutil.rmtree(path)
危险操作 :递归地删除整个目录树 path
(包括非空目录)。shutil.move(src, dst)
递归地移动文件或目录 src
到 dst
(类似 Unix 的 mv
命令)。 shutil.make_archive(base_name, format, root_dir)
创建一个压缩归档文件 (如 zip 或 tar)。
批注:核心记忆函数 (shutil
模块)
shutil.copy2()
: 复制文件时通常比 copy()
更好,因为它保留元数据。shutil.copytree()
: 复制整个文件夹。shutil.rmtree()
: 要特别小心使用 ,用于删除整个文件夹及其内容。shutil.move()
: 移动文件或文件夹。shutil.make_archive()
: 创建压缩包。shutil
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 from print_utils import *import osimport shutilsrc_dir = "shutil_source_dir" dst_dir_base = "shutil_dest_base" os.makedirs(src_dir, exist_ok=True ) os.makedirs(dst_dir_base, exist_ok=True ) src_file = os.path.join(src_dir, "original_file.txt" ) with open (src_file, "w" ) as f: f.write("源文件测试" ) print_info(f"准备的源文件: {src_file} " ) copied_file_path = os.path.join(dst_dir_base, "copied_file.txt" ) shutil.copy2(src_file, copied_file_path) print_info(f"文件 '{src_file} ' 已使用 copy2 复制到 '{copied_file_path} '" ) copied_tree_path = os.path.join(dst_dir_base, "copied_source_tree" ) if os.path.exists(copied_tree_path): shutil.rmtree(copied_tree_path) shutil.copytree(src_dir, copied_tree_path) print_info(f"目录树 '{src_dir} ' 已复制到 '{copied_tree_path} '" ) moved_file_destination_dir = os.path.join(dst_dir_base, "moved_files_here" ) os.makedirs(moved_file_destination_dir, exist_ok=True ) moved_file_path = shutil.move(copied_file_path, moved_file_destination_dir) print_info(f"文件 '{copied_file_path} ' 已移动到 '{moved_file_path} '" ) moved_tree_path_new_name = os.path.join(dst_dir_base, "moved_source_tree_new_name" ) shutil.move(copied_tree_path, moved_tree_path_new_name) print_info(f"目录 '{copied_tree_path} ' 已移动并重命名为 '{moved_tree_path_new_name} '" ) dir_to_delete_completely = os.path.join(dst_dir_base, "temp_delete_me" ) os.makedirs(os.path.join(dir_to_delete_completely, "subdir" ), exist_ok=True ) with open (os.path.join(dir_to_delete_completely, "file.txt" ), "w" ) as f: f.write("temp" )print_info(f"准备删除目录: {dir_to_delete_completely} " ) shutil.rmtree(dir_to_delete_completely) print_info(f"目录 '{dir_to_delete_completely} ' 已使用 rmtree 删除。" ) archive_output_name = os.path.join(dst_dir_base, "my_archive" ) shutil.make_archive(archive_output_name, 'zip' , src_dir) print_info(f"目录 '{src_dir} ' 已压缩为 '{archive_output_name} .zip'" ) shutil.rmtree(src_dir) shutil.rmtree(dst_dir_base) print_info("\nshutil 测试环境已清理。" )
pathlib
模块:面向对象的文件系统路径pathlib
模块是 Python 3.4 版本引入的标准库,它将文件系统路径表示为具有方法和属性的实际对象,而不是简单的字符串。这种面向对象的方法使得路径操作更加清晰、易读,并且减少了在处理不同操作系统路径分隔符时可能出现的错误。
pathlib
常用类与方法核心类是 Path
,它同时代表文件和目录路径。
类/属性/方法 描述 from pathlib import Path
导入核心的 Path
类。 Path(path_str_or_Path_obj, ...)
创建一个 Path
对象,可以传入字符串路径或已有的 Path
对象。 Path.cwd()
(类方法) 返回一个表示当前工作目录的 Path
对象。 Path.home()
(类方法) 返回一个表示用户主目录的 Path
对象。 path_obj / "another_part"
使用 /
操作符轻松、跨平台地拼接路径部分。 path_obj.joinpath(*other_paths)
另一种拼接路径部分的方法,可以接受多个参数。 path_obj.name
(属性) 获取路径的最后一部分 (文件名或目录名)。 path_obj.stem
(属性) 获取文件名中不包含最后一个后缀的部分。 path_obj.suffix
(属性) 获取路径的最后一个文件扩展名 (例如 .txt
)。 path_obj.suffixes
(属性) 获取路径中所有文件扩展名的列表 (例如 ['.tar', '.gz']
)。 path_obj.parent
(属性) 获取路径的直接父目录。 path_obj.parents
(属性) 一个序列,包含路径的所有父级目录,直到根目录。 path_obj.exists()
检查路径是否存在 (无论是文件还是目录)。 path_obj.is_file()
检查路径是否指向一个已存在的文件。 path_obj.is_dir()
检查路径是否指向一个已存在的目录。 path_obj.is_symlink()
检查路径是否指向一个符号链接。 path_obj.is_absolute()
检查路径是否为绝对路径。 path_obj.resolve(strict=False)
将路径解析为绝对路径,并处理任何符号链接 (如果 strict=True
且路径不存在则报错)。 path_obj.absolute()
返回路径的绝对形式 (不解析符号链接)。 path_obj.mkdir(mode=0o777, parents=False, exist_ok=False)
创建此路径表示的目录,parents=True
会创建所有必需的父目录,exist_ok=True
则目录已存在时不报错。 path_obj.rmdir()
删除此路径表示的空目录。 path_obj.unlink(missing_ok=False)
删除此路径表示的文件或符号链接 (如果 missing_ok=True
且文件不存在则不报错)。 path_obj.rename(target)
将此路径重命名或移动到 target
路径。 path_obj.replace(target)
类似 rename
,但在目标已存在时会覆盖它 (如果操作系统支持原子操作)。 path_obj.touch(mode=0o666, exist_ok=True)
创建此路径表示的文件 (类似 Unix touch
命令),如果文件已存在则更新其修改时间 (除非 exist_ok=False
)。 path_obj.read_text(encoding=None, ...)
读取文件内容并将其作为字符串返回。 path_obj.write_text(data, encoding=None, ...)
将字符串 data
写入文件 (会覆盖已有内容)。 path_obj.read_bytes()
读取文件内容并将其作为字节串返回。 path_obj.write_bytes(data)
将字节串 data
写入文件 (会覆盖已有内容)。 path_obj.open(mode='r', ...)
以指定模式打开文件,返回一个文件对象 (与内置 open()
类似)。 path_obj.iterdir()
返回一个迭代器,产生此目录路径下的所有直接子项 (文件和目录) 的 Path
对象。 path_obj.glob(pattern)
在此路径下查找匹配 pattern
的文件和目录,返回一个生成器。 path_obj.rglob(pattern)
递归地在此路径及其所有子目录下查找匹配 pattern
的文件和目录,返回一个生成器。 path_obj.stat()
返回一个包含此路径元数据信息的 os.stat_result
对象 (如大小、修改时间等)。
批注:核心记忆功能 (pathlib
模块)
Path()
: 创建 Path
对象是所有操作的开始。/
操作符: 非常常用且直观 ,用于安全地拼接路径。.exists()
, .is_file()
, .is_dir()
: 判断路径状态。.name
, .stem
, .suffix
, .parent
: 方便地获取路径的各个组成部分。.mkdir(parents=True, exist_ok=True)
: 创建目录的常用组合。.read_text()
, .write_text()
, .read_bytes()
, .write_bytes()
: 简单快速地读写文件内容。.iterdir()
, .glob()
, .rglob()
: 遍历和搜索目录内容。使用 with path_obj.open(...) as f:
进行文件操作,确保文件正确关闭。 pathlib
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 from print_utils import *from pathlib import Pathimport os import shutil print_header("pathlib 模块功能演示" ) DEMO_ROOT_DIR_NAME: str = "pathlib_demo_space" DEMO_ROOT_PATH: Path = Path.cwd() / DEMO_ROOT_DIR_NAME def setup_demo_environment (base_path: Path ) -> None : """ 准备演示环境:创建目录结构和文件 """ print_info(f"创建演示环境于 {base_path} " ) if base_path.exists(): shutil.rmtree(base_path) base_path.mkdir(parents=True , exist_ok=True ) (base_path / "documents" ).mkdir() (base_path / "images" ).mkdir() (base_path / "archives" ).mkdir() (base_path / "main_script.py" ).write_text("# 主脚本" ) (base_path / "readme.md" ).write_text("# 项目说明" ) (base_path / "data.csv" ).write_text("name,age\nJohn,30\nJane,25" ) (base_path / "documents" / "report.pdf" ).write_text("# 报告" ) (base_path / "images" / "logo.png" ).write_text("# 图片" ) (base_path / "archives" / "archive.zip" ).write_text("# 压缩包" ) import random for i in range (3 ): first_dir = base_path / "documents" / f"level_{i + 1 } " first_dir.mkdir(exist_ok=True ) for j in range (3 ): second_dir = first_dir / f"sublevel_{j + 1 } " second_dir.mkdir(exist_ok=True ) for k in range (5 ): file_num = random.randint(1 , 100 ) (second_dir / f"file_{file_num} .txt" ).write_text(f"# 第{i + 1 } 层第{j + 1 } 子层的数字文件 {file_num} " ) print_success(f"演示环境准备完成于 {base_path} " ) def cleanup_demo_environment (base_path: Path ) -> None : """ 清理演示环境 """ print_info(f"清理演示环境于 {base_path} " ) if base_path.exists(): shutil.rmtree(base_path) print_success(f"演示环境清理完成,清理路径为 => {base_path} " ) def demo_path_creation_and_attributes (base_path: Path ) -> None : print_subheader("1. Path 对象创建与基本属性" ) p1: Path = Path("/usr/local/bin" ) p2: Path = base_path / "main_script.py" print_info(f"Path 对象 p1: {p1} " ) print_info(f"Path 对象 p2: {p2} " ) print_info(f"p1 是否为绝对路径: {p1.is_absolute()} " ) print_info(f"p2 是否为绝对路径: {p2.is_absolute()} " ) print_info(f"p2 的文件名: {p2.name} " ) print_info(f"p2 的后缀: {p2.suffix} " ) print_info(f"p2 的父目录: {p2.parent} " ) print_info(f"p2 的祖先目录: {p2.parents} " ) print_info(f"p2 的绝对路径: {p2.resolve()} " ) print_info(f"p2 的规范化路径: {p2.as_posix()} " ) print_info(f"p2 的网络路径: {p2.as_uri()} " ) for i, parent_dir in enumerate (p2.parents): print_info(f"p2 的第 {i + 1 } 个祖先目录: {parent_dir} " ) cwd_path: Path = Path.cwd() home_path: Path = Path.home() print_info(f"当前工作目录 (Path.cwd()): {cwd_path} " ) print_info(f"用户主目录 (Path.home()): {home_path} " ) r""" 1. Path 对象创建与基本属性 INFO: Path 对象 p1: \usr\local\bin INFO: Path 对象 p2: D:\python\PythonStudy\pathlib_demo_space\main_script.py INFO: p1 是否为绝对路径: False INFO: p2 是否为绝对路径: True INFO: p2 的文件名: main_script.py INFO: p2 的后缀: .py INFO: p2 的父目录: D:\python\PythonStudy\pathlib_demo_space INFO: p2 的祖先目录: <WindowsPath.parents> INFO: p2 的绝对路径: D:\python\PythonStudy\pathlib_demo_space\main_script.py INFO: p2 的规范化路径: D:/python/PythonStudy/pathlib_demo_space/main_script.py INFO: p2 的网络路径: file:///D:/python/PythonStudy/pathlib_demo_space/main_script.py INFO: p2 的第 1 个祖先目录: D:\python\PythonStudy\pathlib_demo_space INFO: p2 的第 2 个祖先目录: D:\python\PythonStudy INFO: p2 的第 3 个祖先目录: D:\python INFO: p2 的第 4 个祖先目录: D:\ INFO: 当前工作目录 (Path.cwd()): D:\python\PythonStudy INFO: 用户主目录 (Path.home()): C:\Users\Prorise """ def demo_path_joining_and_resolution (base_path: Path ) -> None : """演示路径的拼接和解析。""" print_subheader("2. 路径拼接与解析" ) part1 = base_path part2: str = "documents" part3: str = "report_final.docx" joined_path1 = Path = part1 / part2 / part3 print_info(f"使用 / 操作符拼接: {joined_path1} " ) joined_path2 = part1.joinpath(part2, part3) print_info(f"使用 joinPath () 方法拼接: {joined_path2} " ) readme_path: Path = base_path / "readme.md" if readme_path.exists(): print_info(f" 'readme.md' 的相对路径: {readme_path} " ) print_info(f" 'readme.md' 的绝对路径 (absolute()): {readme_path.absolute()} " ) print_info(f" 'readme.md' 的解析后绝对路径 (resolve()): {readme_path.resolve()} " ) else : print_warning(f" 'readme.md' 不存在,无法进行绝对路径解析。" ) r""" INFO: 使用 / 操作符拼接: D:\python\PythonStudy\pathlib_demo_space\documents\report_final.docx INFO: 使用 joinPath () 方法拼接: D:\python\PythonStudy\pathlib_demo_space\documents\report_final.docx INFO: 'readme.md' 的相对路径: D:\python\PythonStudy\pathlib_demo_space\readme.md INFO: 'readme.md' 的绝对路径 (absolute()): D:\python\PythonStudy\pathlib_demo_space\readme.md INFO: 'readme.md' 的解析后绝对路径 (resolve()): D:\python\PythonStudy\pathlib_demo_space\readme.md """ def demo_path_type_checks (base_path: Path ) -> None : """演示检查路径的类型和状态。""" print_subheader("3. 检查路径类型和状态" ) file_p: Path = base_path / "readme.md" dir_p: Path = base_path / "documents" non_existent_p: Path = base_path / "non_existent_file.tmp" print_info(f"路径 '{file_p.name} ':" ) print (f" - 是否存在 (exists()): {file_p.exists()} " ) print (f" - 是否是文件 (is_file()): {file_p.is_file()} " ) print (f" - 是否是目录 (is_dir()): {file_p.is_dir()} " ) print_info(f"路径 '{dir_p.name} ':" ) print (f" - 是否存在 (exists()): {dir_p.exists()} " ) print (f" - 是否是文件 (is_file()): {dir_p.is_file()} " ) print (f" - 是否是目录 (is_dir()): {dir_p.is_dir()} " ) print_info(f"路径 '{non_existent_p.name} ':" ) print (f" - 是否存在 (exists()): {non_existent_p.exists()} " ) def demo_file_directory_operations (base_path: Path ) -> None : """演示文件的创建、读写和目录的创建、遍历。""" print_subheader("4. 文件和目录操作" ) new_sub_dir: Path = base_path / "new_app_data" / "config" try : new_sub_dir.mkdir(parents=True , exist_ok=True ) print_success(f" 目录 '{new_sub_dir} ' 已创建 (或已存在)。" ) config_file: Path = new_sub_dir / "settings.json" sample_json_data: str = '{"theme": "dark", "version": "1.0"}' config_file.write_text(sample_json_data, encoding="utf-8" ) print_success(f" 文件 '{config_file.name} ' 已创建并写入内容。" ) read_data: str = config_file.read_text(encoding="utf-8" ) print_info(f" 从 '{config_file.name} ' 读取的内容: {read_data} " ) renamed_config_file: Path = new_sub_dir / "settings_v2.json" config_file.rename(renamed_config_file) print_success(f" 文件已重命名为: '{renamed_config_file.name} '" ) print_info(f" 旧路径 '{config_file} ' 是否还存在: {config_file.exists()} " ) print_info(f" 新路径 '{renamed_config_file} ' 是否存在: {renamed_config_file.exists()} " ) renamed_config_file.unlink(missing_ok=True ) print_warning(f" 文件 '{renamed_config_file.name} ' 已删除。" ) if new_sub_dir.is_dir(): new_sub_dir.rmdir() print_warning(f" 空目录 '{new_sub_dir} ' 已删除。" ) if new_sub_dir.parent.is_dir() and not list (new_sub_dir.parent.iterdir()): new_sub_dir.parent.rmdir() print_warning(f" 空目录 '{new_sub_dir.parent} ' 已删除。" ) except Exception as e: print_error(f" 文件/目录操作时发生错误: {e} " ) def demo_directory_iteration (base_path: Path ) -> None : """演示遍历目录内容和使用 glob 搜索文件。""" print_subheader("5. 目录遍历与文件搜索" ) print_info(f"遍历 '{base_path / 'documents' } ' 目录 (iterdir()):" ) docs_dir: Path = base_path / "documents" if docs_dir.is_dir(): for item in docs_dir.iterdir(): item_type = "[目录]" if item.is_dir() else "[文件]" print (f" {item_type} {item.name} " ) print_info(f"\n在 '{base_path / 'documents' } ' 目录下搜索所有 .txt 文件 (glob('*.txt')):" ) for txt_file in docs_dir.glob("*.txt" ): print (f" [文件] {txt_file.name} " ) print_info(f"\n在 '{base_path / 'documents' } ' 及其所有子目录下搜索 .txt 文件 (rglob('*.txt')):" ) for txt_file in docs_dir.rglob("*.txt" ): rel_path = txt_file.relative_to(docs_dir) print (f" [文件] {rel_path} " ) if __name__ == '__main__' : setup_demo_environment(DEMO_ROOT_PATH) demo_directory_iteration(DEMO_ROOT_PATH)
pathlib
模块开箱即用场景a. 递归查找指定类型的文件并处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from print_utils import *from pathlib import Pathfrom typing import List , Generator def find_files_by_extension (directory: Path, extension: str ) -> Generator[Path, None , None ]: """ 递归查找指定目录下具有特定扩展名的所有文件。 参数: directory (Path): 要搜索的根目录。 extension (str): 文件扩展名 (例如: ".txt", ".jpg"),注意包含点。 返回: Generator[Path, None, None]: 一个生成器,逐个产生找到的文件路径对象。 """ print_info(f"在目录 '{directory} ' 中递归查找所有 '{extension} ' 文件..." ) if not directory.is_dir(): print_error(f"错误: '{directory} ' 不是一个有效的目录。" ) return for file_path in directory.rglob(f"*{extension} " ): if file_path.is_file(): yield file_path if __name__ == "__main__" : _demo_search_path = Path.cwd() / "pathlib_find_demo" _demo_search_path.mkdir(exist_ok=True ) (_demo_search_path / "doc1.txt" ).write_text("text content 1" ) (_demo_search_path / "image.png" ).touch() (_demo_search_path / "subfolder" ).mkdir(exist_ok=True ) (_demo_search_path / "subfolder" / "doc2.txt" ).write_text("text content 2" ) (_demo_search_path / "subfolder" / "another.log" ).touch() (_demo_search_path / "archive.txt.gz" ).touch() print_header("find_files_by_extension 函数演示" ) found_files: List [Path] = list (find_files_by_extension(_demo_search_path, ".txt" )) if found_files: print_success(f"找到以下 .txt 文件 (共 {len (found_files)} 个):" ) for f_path in found_files: print (f" - {f_path} " ) else : print_warning("未找到任何 .txt 文件。" ) import shutil if _demo_search_path.exists(): shutil.rmtree(_demo_search_path) print_info(f"已清理演示目录: {_demo_search_path} " )
b. 安全地创建多级目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from print_utils import *from pathlib import Pathfrom typing import Union def ensure_directory_exists (dir_path: Union [str , Path] ) -> Path: """ 确保指定的目录路径存在,如果不存在则创建它 (包括所有必需的父目录)。 参数: dir_path (Union[str, Path]): 要确保存在的目录路径 (可以是字符串或Path对象)。 返回: Path: 对应的Path对象。 引发: OSError: 如果路径存在但不是一个目录,或者创建失败且非权限问题。 """ path_obj = Path(dir_path) print_info(f"检查并确保目录 '{path_obj} ' 存在..." ) try : path_obj.mkdir(parents=True , exist_ok=True ) print_success(f"目录 '{path_obj} ' 现在已存在。" ) return path_obj except OSError as e: print_error(f"创建目录 '{path_obj} ' 时发生错误: {e} " ) raise if __name__ == "__main__" : print_header("ensure_directory_exists 函数演示" ) new_data_path_str = "project_alpha/raw_data/year_2025" created_path_obj: Path = ensure_directory_exists(new_data_path_str) print_info(f"返回的 Path 对象: {created_path_obj} " ) print_info(f" 该路径是否是目录: {created_path_obj.is_dir()} " ) ensure_directory_exists(created_path_obj) import shutil top_level_created_dir = Path("project_alpha" ) if top_level_created_dir.exists(): shutil.rmtree(top_level_created_dir) print_info(f"已清理演示目录: {top_level_created_dir} " )
坑点与建议 (pathlib
模块) :
面向对象思维转变 : 如果你习惯了 os.path
中基于字符串的函数式调用 (例如 os.path.join(path, name)
), 切换到 pathlib
的面向对象方式 (例如 Path(path) / name
) 可能需要一点时间适应,但适应后通常会觉得更自然和强大。性能考虑 : 在绝大多数应用场景下,pathlib
的性能与 os.path
相当,甚至在某些情况下由于内部优化可能更快。只有在对路径操作进行极端密集的循环(例如,每秒处理数百万个路径)时,才可能需要进行性能分析比较。对于日常脚本和大多数应用程序,pathlib
带来的可读性和易用性提升远比微小的性能差异更重要。与旧代码的互操作性 : Path
对象实现了 os.PathLike
接口,这意味着在很多接受字符串路径作为参数的内置函数和标准库函数中(包括 open()
, os.chdir()
, shutil
的很多函数等),你可以直接传递 Path
对象,Python 会自动将其转换为字符串路径。这使得逐步迁移到 pathlib
或在现有代码中引入它变得容易。绝对路径 vs. 相对路径 : 要清楚你的 Path
对象代表的是绝对路径还是相对路径。resolve()
方法可以获取一个路径的绝对、规范化形式,并解析所有符号链接。absolute()
则简单地将相对路径转换为绝对路径,但不解析符号链接。文件系统操作的原子性 : 像 Path.rename()
或 Path.replace()
这样的操作,其原子性(即操作要么完全成功,要么完全不执行,不会留下中间状态)取决于底层操作系统和文件系统的支持。异常处理 : 所有执行实际文件系统I/O操作(如创建、删除、读写文件/目录)的 pathlib
方法都可能引发 OSError
的各种子类异常 (如 FileNotFoundError
, PermissionError
, FileExistsError
)。务必使用 try-except
块来妥善处理这些潜在的错误。pathlib
提供了一种更现代、更 Pythonic 的方式来与文件系统路径交互。一旦熟悉了它的面向对象特性,很多路径相关的编程任务都会变得更加简单和愉快。
zipfile
模块:处理 ZIP 压缩文件zipfile
模块用于创建、读取、写入、追加和列出 ZIP 文件的内容。通常与 with
语句一起使用以确保文件正确关闭。
zipfile
常用功能 (基于 ZipFile
对象)方法/属性 描述 ZipFile(file, mode='r', ...)
打开或创建一个 ZIP 文件 (mode
: 'r’读, 'w’写, 'a’追加)。 zf.namelist()
返回 ZIP 文件中所有成员的名称列表。 zf.infolist()
返回 ZIP 文件中所有成员的 ZipInfo
对象列表 (包含详细信息)。 zf.getinfo(name)
获取指定成员 name
的 ZipInfo
对象。 zf.extract(member, path=None)
从 ZIP 文件中提取单个成员 member
到指定路径 path
。 zf.extractall(path=None, ...)
从 ZIP 文件中提取所有成员到指定路径 path
。 zf.write(filename, arcname=None)
将文件 filename
写入 ZIP 文件,可选的 arcname
是在压缩包中的名称。 zf.read(name)
读取 ZIP 文件中成员 name
的内容并返回字节串。 zf.close()
关闭 ZIP 文件 (推荐使用 with
语句自动管理)。
批注:核心记忆功能 (zipfile
模块)
使用 with zipfile.ZipFile(...) as zf:
打开文件。 zf.write()
: 向压缩包中添加文件。zf.extractall()
: 解压整个压缩包。zf.namelist()
: 查看压缩包内容。zipfile
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import zipfileimport oszip_test_dir = "zip_test_files" os.makedirs(zip_test_dir, exist_ok=True ) file1_path = os.path.join(zip_test_dir, "doc1.txt" ) file2_path = os.path.join(zip_test_dir, "image.jpg" ) with open (file1_path, "w" ) as f: f.write("This is document one." )with open (file2_path, "w" ) as f: f.write("Fake image data." )zip_filename = "my_documents.zip" print (f"创建 ZIP 文件: {zip_filename} " )with zipfile.ZipFile(zip_filename, mode='w' , compression=zipfile.ZIP_DEFLATED) as zf: zf.write(file1_path, arcname="text_files/document1.txt" ) zf.write(file2_path, arcname="images/photo.jpg" ) zf.writestr("manuals/readme.txt" , "This is a readme file created directly in zip." ) print ("ZIP 文件创建完成。" )print (f"\n读取 ZIP 文件 '{zip_filename} ' 的内容:" )with zipfile.ZipFile(zip_filename, mode='r' ) as zf: print (" 文件列表 (namelist):" ) for name in zf.namelist(): print (f" - {name} " ) print ("\n 文件详细信息 (infolist):" ) for zipinfo in zf.infolist(): print (f" - 文件名: {zipinfo.filename} , 大小: {zipinfo.file_size} bytes, 修改日期: {zipinfo.date_time} " ) try : readme_content = zf.read("manuals/readme.txt" ) print (f"\n 'manuals/readme.txt' 的内容: {readme_content.decode('utf-8' )} " ) except KeyError: print (" 'manuals/readme.txt' 未在压缩包中找到。" ) extract_to_dir = "extracted_zip_contents" os.makedirs(extract_to_dir, exist_ok=True ) print (f"\n解压 ZIP 文件 '{zip_filename} ' 到 '{extract_to_dir} ':" )with zipfile.ZipFile(zip_filename, mode='r' ) as zf: zf.extractall(path=extract_to_dir) print (f"ZIP 文件解压完成。查看 '{extract_to_dir} ' 目录。" )os.remove(zip_filename) shutil.rmtree(zip_test_dir) shutil.rmtree(extract_to_dir) print ("\nzipfile 测试环境已清理。" )
tarfile
模块:处理 TAR 归档文件tarfile
模块用于读取和写入 tar 归档文件,包括使用 gzip, bz2 和 lzma 压缩的 tar 文件。与 zipfile
类似,推荐使用 with
语句。
tarfile
常用功能 (基于 TarFile
对象)方法/属性 描述 ( tarfile.open(name, mode='r', ...)
打开或创建一个 TAR 文件 (mode
: 'r’读, 'w’写, 'a’追加, ‘r:gz’, 'w:bz2’等)。 tf.getnames()
返回归档中所有成员的名称列表。 tf.getmembers()
返回归档中所有成员的 TarInfo
对象列表 (包含详细信息)。 tf.getmember(name)
获取指定成员 name
的 TarInfo
对象。 tf.extract(member, path="", ...)
从归档中提取单个成员 member
到指定路径 path
。 tf.extractall(path=".", members=...)
从归档中提取所有 (或指定 members
) 到路径 path
。 tf.add(name, arcname=None, ...)
将文件或目录 name
添加到归档,可选 arcname
是在归档中的名称。 tf.extractfile(member)
返回一个类似文件的对象,用于读取成员 member
的内容 (不实际解压到磁盘)。 tf.close()
关闭 TAR 文件 (推荐使用 with
语句自动管理)。
批注:核心记忆功能 (tarfile
模块)
使用 with tarfile.open(...) as tf:
打开文件,注意 mode
(如 ‘w:gz’ 创建gz压缩包)。 tf.add()
: 向归档中添加文件或目录。tf.extractall()
: 解压整个归档。tf.getnames()
: 查看归档内容。tarfile
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 import tarfileimport osimport shutil tar_test_dir = "tar_test_files" os.makedirs(tar_test_dir, exist_ok=True ) os.makedirs(os.path.join(tar_test_dir, "subdir" ), exist_ok=True ) file_content1 = "Content for tar file 1." file_content2 = "Content for tar file 2, in a subdirectory." path_file1 = os.path.join(tar_test_dir, "data.txt" ) path_file2 = os.path.join(tar_test_dir, "subdir" , "notes.log" ) with open (path_file1, "w" ) as f: f.write(file_content1)with open (path_file2, "w" ) as f: f.write(file_content2)tar_gz_filename = "my_archive.tar.gz" print (f"创建 TAR.GZ 文件: {tar_gz_filename} " )with tarfile.open (tar_gz_filename, mode='w:gz' ) as tf: tf.add(tar_test_dir, arcname="archive_root" ) print ("TAR.GZ 文件创建完成。" )print (f"\n读取 TAR 文件 '{tar_gz_filename} ' 的内容:" )with tarfile.open (tar_gz_filename, mode='r:gz' ) as tf: print (" 文件列表 (getnames):" ) for name in tf.getnames(): print (f" - {name} " ) print ("\n 文件详细信息 (getmembers):" ) for tarinfo in tf.getmembers(): type_str = "DIR" if tarinfo.isdir() else ("FILE" if tarinfo.isfile() else "OTHER" ) print (f" - 名称: {tarinfo.name} , 类型: {type_str} , 大小: {tarinfo.size} , 修改时间: {tarinfo.mtime} " ) try : member_to_read = "archive_root/subdir/notes.log" if member_to_read in tf.getnames(): extracted_file_obj = tf.extractfile(member_to_read) if extracted_file_obj: content_bytes = extracted_file_obj.read() print (f"\n '{member_to_read} ' 的内容: {content_bytes.decode('utf-8' )} " ) extracted_file_obj.close() else : print (f" 文件 '{member_to_read} ' 在归档中未找到。" ) except KeyError as e: print (f" 读取归档成员时出错: {e} " ) extract_tar_to_dir = "extracted_tar_contents" os.makedirs(extract_tar_to_dir, exist_ok=True ) print (f"\n解压 TAR 文件 '{tar_gz_filename} ' 到 '{extract_tar_to_dir} ':" )with tarfile.open (tar_gz_filename, mode='r:gz' ) as tf: tf.extractall(path=extract_tar_to_dir) print (f"TAR 文件解压完成。查看 '{extract_tar_to_dir} ' 目录。" )os.remove(tar_gz_filename) shutil.rmtree(tar_test_dir) shutil.rmtree(extract_tar_to_dir) print ("\ntarfile 测试环境已清理。" )
4.系统交互与进程管理模块 在 Python 开发中,与程序运行的底层环境(如操作系统、解释器本身)进行交互,以及启动和管理外部的子进程,是非常常见的需求。sys
、argparse
和 subprocess
这三个标准库模块为此提供了强大的支持。
sys
模块:与 Python 解释器深度交互sys
模块提供了访问和操作 Python 解释器本身以及其运行时环境的变量和函数。通过它,我们可以获取命令行参数、管理模块搜索路径、控制程序退出、了解当前平台信息等。
sys
模块常用属性和函数属性/方法 描述 sys.argv
获取脚本启动时传入的命令行参数列表 (第一个是脚本名)。 sys.exit([status])
用来终止当前 Python 程序的执行,可以指定一个退出状态码。 sys.path
一个列表,指明了 Python 导入模块时会去哪些目录下查找。 sys.platform
返回一个字符串,告诉你当前运行 Python 的操作系统平台是什么。 sys.version
获取当前 Python 解释器的详细版本信息字符串。 sys.version_info
以元组形式提供 Python 版本号的各部分 (主版本、次版本等)。 sys.executable
获取当前正在运行的 Python 解释器程序文件的绝对路径。 sys.stdin
代表标准输入流的对象,程序可以从中读取数据 (通常来自键盘)。 sys.stdout
代表标准输出流的对象,print()
函数默认向这里输出 (通常是屏幕)。 sys.stderr
代表标准错误流的对象,用于输出错误和警告信息 (通常是屏幕)。 sys.getrecursionlimit()
查询 Python 解释器允许的最大递归调用层数。 sys.setrecursionlimit(limit)
修改 Python 解释器允许的最大递归调用层数。 sys.modules
一个字典,记录了当前已加载的所有模块及其对象。
批注:核心记忆功能 (sys
模块)
sys.argv
: 开发命令行工具时,获取用户输入参数的起点。sys.exit()
: 控制脚本的退出行为和状态。sys.path
: 理解 Python 如何找到模块,偶尔需要动态修改它。sys.platform
, sys.version_info
: 编写跨平台或版本兼容代码时的判断依据。sys.stdin
, sys.stdout
, sys.stderr
: 对于需要精细控制输入输出的脚本很有用。sys
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 from print_utils import * import sysimport os print_header("sys 模块功能演示" ) print_info("以下示例展示 sys 模块的常用功能。" ) print_subheader("1. 命令行参数 (sys.argv)" ) print_info(f"脚本本身的名称 (sys.argv[0]): {sys.argv[0 ]} " ) if len (sys.argv) > 1 : print_info(f"所有命令行参数 (sys.argv): {sys.argv} " ) print_info(f"第一个传递的参数 (sys.argv[1]): {sys.argv[1 ]} " ) else : print_warning("未提供额外的命令行参数给脚本。" ) print_subheader("2. Python 版本和平台信息" ) print_info(f"Python 完整版本字符串 (sys.version):\n{sys.version} " ) print_info(f"Python 版本信息元组 (sys.version_info): {sys.version_info} " ) print_info(f" 主版本号 (major): {sys.version_info.major} " ) print_info(f" 次版本号 (minor): {sys.version_info.minor} " ) print_info(f" 修订版本号 (micro): {sys.version_info.micro} " ) print_info(f"当前操作系统平台 (sys.platform): {sys.platform} " ) print_info(f"Python 解释器可执行文件路径 (sys.executable):\n {sys.executable} " ) print_subheader("3. 模块搜索路径 (sys.path)" ) print_info("Python 解释器查找模块时会依次检查以下路径 (仅显示前5条):" ) for i, search_path in enumerate (sys.path[:5 ]): print (f" 路径 {i} : {search_path} " ) custom_lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'my_custom_libs' ) if custom_lib_path not in sys.path: sys.path.insert(0 , custom_lib_path) print_success(f"已将 '{custom_lib_path} ' 添加到 sys.path。现在可以尝试导入该目录下的模块。" ) else : print_info(f"路径 '{custom_lib_path} ' 已存在于 sys.path 中。" ) print_subheader("4. 标准输入、输出、错误流" ) sys.stdout.write(f"{Colors.GREEN} ✔ 这是一条通过 sys.stdout.write 输出的成功消息。{Colors.END} \n" ) sys.stderr.write(f"{Colors.FAIL} ❌ 这是一条通过 sys.stderr.write 输出的错误消息。{Colors.END} \n" ) print_subheader("5. 递归深度限制" ) current_recursion_limit = sys.getrecursionlimit() print_info(f"当前的递归深度限制是: {current_recursion_limit} " ) print_subheader("6. 程序退出 (sys.exit)" ) print_warning("以下 sys.exit() 调用将被注释掉,以允许脚本继续执行。" ) print_info("sys 模块演示结束。" )
坑点与建议 (sys
模块) :
sys.argv
的手动解析 : 对于需要多个参数、不同类型参数、可选参数、帮助信息等的命令行工具,手动解析 sys.argv
会变得非常复杂和易错。强烈推荐使用 argparse
模块来处理命令行参数。sys.path
的修改时机与影响 : 动态修改 sys.path
主要用于临时解决模块导入问题或在特定环境下加载模块。过度依赖动态修改 sys.path
可能导致项目结构混乱和难以维护。优先考虑使用 Python 的包管理机制、虚拟环境 (如 venv, conda) 和设置 PYTHONPATH
环境变量。sys.exit()
与异常处理 : sys.exit()
通过引发 SystemExit
异常来终止程序。这意味着 try...finally
块中的 finally
子句在程序退出前仍会执行,这对于释放资源(如关闭文件、网络连接)非常重要。捕获 SystemExit
异常通常是不必要的,除非你有非常特殊的理由需要在程序退出前执行一些额外的清理逻辑,并且标准的 finally
块无法满足需求。递归深度的修改 : Python 设置递归深度限制是为了防止因无限递归或过深递归耗尽调用栈空间,从而导致程序崩溃。只有在你完全理解递归算法的特性和潜在风险,并且确认需要更大深度时,才应谨慎地使用 sys.setrecursionlimit()
。大多数情况下,如果遇到递归深度限制,应首先检查算法是否存在逻辑错误或考虑将其转换为迭代实现。标准流的编码 : sys.stdin
, sys.stdout
, sys.stderr
的默认编码取决于操作系统和环境设置。在处理非 ASCII 字符时,尤其是在跨平台应用中,要注意编码问题,可能需要显式设置流的编码或使用 PYTHONIOENCODING
环境变量。argparse
模块:强大的命令行参数解析(核心)当你的 Python 脚本需要从命令行接收参数时,sys.argv
提供了原始的参数列表。但手动解析 sys.argv
会非常繁琐且容易出错,特别是当参数变多、类型各异、或需要可选参数和帮助信息时。这时,argparse
模块就派上了大用场。它使得创建用户友好的命令行界面变得简单。
argparse
模块核心功能功能/方法 描述 argparse.ArgumentParser(...)
创建一个新的参数解析器对象,它是定义和解析参数的起点。 parser.add_argument(name_or_flags, ...)
向解析器添加一个参数定义,指定其名称、类型、行为等。 parser.parse_args([args])
解析命令行传入的参数 (默认来自 sys.argv[1:]
),并返回一个包含参数值的对象。 parser.add_subparsers(...)
添加子命令功能,使你的工具可以像 git <command>
一样拥有多个操作模式。 add_argument()
常用参数:name_or_flags
定义参数的名称 (如 'filename'
) 或标志 (如 '-f'
, '--file'
)。 type
指定参数应该被自动转换成的目标类型 (如 int
, float
, str
)。 default
如果用户在命令行中没有提供该参数,则赋予该参数一个默认值。 help
为该参数提供一段简短的描述,会显示在自动生成的帮助信息中。 action
定义当参数出现时应执行的动作 (如 'store_true'
用于布尔开关, 'count'
用于计数)。 required=True/False
指明一个可选参数 (--option
) 是否必须被用户提供。 choices=[...]
限制参数的值只能从一个给定的选项列表中选择。 nargs
指定该参数可以接受的值的数量 (如 '+'
表示一个或多个)。
批注:核心记忆功能 (argparse
模块)
ArgumentParser()
: 创建解析器的第一步,可以设置程序描述等。add_argument()
: 最核心的方法 ,用它来定义你的工具接受哪些位置参数和可选参数,以及它们的行为。parse_args()
: 调用此方法来实际解析 sys.argv
中的参数,并得到一个包含所有参数值的命名空间对象。熟悉 add_argument()
的各种选项 (type
, default
, help
, action
, nargs
, choices
) 是用好 argparse
的关键。 add_subparsers()
: 当你的命令行工具功能较多,需要划分为不同的子命令时(例如 mytool command1 --optionA
和 mytool command2 --optionB
),此功能非常有用。argparse
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 from print_utils import *import argparseimport sys import os import shutil def list_directory (directory, all_files=False ): print (f"列出目录: {directory} " ) if all_files: print ("显示所有文件和目录" ) for root, dirs, files in os.walk(directory): print (f"当前目录: {root} " ) for file in files: print (f" 文件: {file} " ) for dir in dirs: print (f" 目录: {dir } " ) print ("\n" ) else : print ("显示目录内容" ) for file in os.listdir(directory): print (f" 文件: {file} " ) print ("\n" ) def delete_file (path ): if os.path.exists(path): if os.path.isfile(path): os.remove(path) print (f"文件已删除: {path} " ) elif os.path.isdir(path): shutil.rmtree(path) print (f"目录已删除: {path} " ) def setup_parser (): parser = argparse.ArgumentParser( prog="FileManager" , description="一个用于文件操作的命令行工具。" , epilog="--- File Manager Help Footer ---" ) subparsers = parser.add_subparsers( dest="command" , help ="选择要执行的操作" , required=True ) parser_list = subparsers.add_parser( "list" , help ="列出目录内容" ) parser_list.add_argument( "directory" , help ="要列出的目录路径" , default="." ) parser_list.add_argument( "--all" , help ="显示所有文件和目录" , action="store_true" ) parser_delete = subparsers.add_parser( "delete" , help ="删除文件" ) parser_delete.add_argument( "path" , help ="要删除的文件路径" ) return parser def main (): parser = setup_parser() args = parser.parse_args() print (f""" ██████╗ ██████╗ ██████╗ ██████╗ ██╗███████╗███████╗ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██║██╔════╝██╔════╝ ██████╔╝██████╔╝██║ ██║██████╔╝██║███████╗█████╗ ██╔═══╝ ██╔══██╗██║ ██║██╔══██╗██║╚════██║██╔══╝ ██║ ██║ ██║╚██████╔╝██║ ██║██║███████║███████╗ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚══════╝╚══════╝ """ ) if args.command == "list" : list_directory(args.directory, args.all ) elif args.command == "delete" : delete_file(args.path) else : parser.print_help() if __name__ == "__main__" : main()
坑点与建议 (argparse
模块) :
用户友好性是目标 :精心设计你的命令行参数和帮助信息。argparse
能够自动生成规范的帮助文档 (-h
或 --help
),这是 CLI 工具易用性的重要组成部分。利用 description
和 epilog
参数美化整体帮助信息,为每个参数提供清晰的 help
文本。参数类型 (type
) 和 choices
:充分利用 type
参数进行自动类型转换 (例如,type=int
会将用户输入的字符串 “10” 转换为整数 10
)。如果类型转换失败,argparse
会自动向用户报告错误。使用 choices
参数可以限制参数的有效取值范围。布尔开关 (action='store_true'
, action='store_false'
) :对于表示“是/否”状态的选项(例如 --verbose
, --quiet
, --debug
),使用 action="store_true"
(如果参数出现,则对应属性值为 True
,否则为 False
) 或 action="store_false"
(如果参数出现,则对应属性值为 False
,否则为 True
) 非常方便。nargs
的灵活运用 :nargs='?'
: 参数可选,如果提供了值则使用该值,否则使用 default
值 (如果未提供值但参数标志存在,则使用 const
值,需与 action
配合)。nargs='*'
: 零个或多个参数值,收集到一个列表中。nargs='+'
: 一个或多个参数值,收集到一个列表中 (至少需要一个)。nargs=整数N
: 固定需要 N 个参数值。子命令 (add_subparsers
) 的强大之处 :当你的工具功能复杂,包含多个独立的操作模式时(例如,像版本控制系统 git
那样,有 clone
, commit
, push
, pull
等不同命令),子命令是组织这些功能的理想方式。每个子命令可以拥有自己的一套参数,使得整体界面更加清晰。务必为子命令设置 dest
参数,以便后续判断用户选择了哪个子命令。默认值 (default
) 的合理设置 :为可选参数提供明智的默认值可以减少用户需要输入的参数数量,提升使用体验。帮助信息中可以使用 %(default)s
来自动显示默认值。互斥参数组 (add_mutually_exclusive_group
) :当你有一组参数,其中用户在任何时候只能指定一个时(例如,--verbose
和 --quiet
不能同时出现),可以使用互斥参数组来强制这种约束。参数顺序 :argparse
通常不关心可选参数 (--option
) 在命令行中出现的顺序,但位置参数必须按照它们被 add_argument()
添加的顺序提供。错误处理 :argparse
会自动处理很多常见的用户输入错误(如缺少必需参数、参数类型错误、无效选项值等),并向用户显示友好的错误信息和用法提示。你通常不需要自己编写大量的参数验证代码。argparse
是构建专业、健壮且用户友好的 Python 命令行应用程序的标准工具。投入时间学习和熟练运用它的各种特性,将极大提升你开发 CLI 工具的效率和质量。
subprocess
模块:管理子进程subprocess
模块允许你创建新的子进程,连接到它们的输入/输出/错误管道,并获取它们的返回码。它是 Python 中执行外部命令和管理子进程的推荐方式,取代了像 os.system()
, os.spawn*
等一些旧的模块和函数。
subprocess
常用功能函数/类/参数 描述 subprocess.run(args, ...)
推荐 :运行 args
指定的命令并等待其完成,返回一个 CompletedProcess
对象。subprocess.Popen(args, ...)
更底层的接口,创建一个子进程对象,可以进行非阻塞操作或更复杂的交互。 args
(用于 run
和 Popen
)要执行的命令及其参数,通常是一个列表 (如 ['ls', '-l']
) 或字符串 (当 shell=True
时)。 capture_output=True
(用于 run
)如果设为 True
,则会捕获子进程的标准输出和标准错误流的内容。 text=True
(用于 run
和 Popen
)如果设为 True
,标准输入、输出和错误流会以文本模式处理 (自动进行编解码)。 check=True
(用于 run
)如果设为 True
且命令返回非零退出码 (表示错误),则会抛出 CalledProcessError
异常。 shell=True
(用于 run
和 Popen
)谨慎使用 :如果为 True
,命令将通过系统的 shell 执行 (可能带来安全风险)。stdin
, stdout
, stderr
(用于 run
和 Popen
)指定子进程的标准输入、输出、错误流的来源或去向 (如 subprocess.PIPE
用于捕获)。 CompletedProcess
对象run()
函数成功完成后的返回值,包含 args
, returncode
, stdout
, stderr
等信息。Popen.communicate(input=None, timeout=None)
与子进程交互:向其 stdin
发送数据,并从其 stdout
和 stderr
读取所有输出,直到结束。 Popen.wait(timeout=None)
等待子进程执行完成,并返回其退出状态码。 Popen.poll()
检查子进程是否已经结束,如果结束则返回退出码,否则立即返回 None
(非阻塞)。 Popen.terminate()
/ Popen.kill()
/ Popen.send_signal(signal)
用于向子进程发送信号以终止它 (terminate 较温和,kill 较强制)。
批注:核心记忆功能 (subprocess
模块)
subprocess.run()
: 首选函数 ,适用于大多数执行外部命令并等待其完成的场景。它是对 Popen
的高级封装。args
参数的正确形式:当 shell=False
(默认且推荐) 时,args
应为列表;当 shell=True
时,args
为字符串。capture_output=True
和 text=True
: 获取命令输出时非常方便,可以得到解码后的文本。check=True
: 使得在命令执行失败时能自动抛出异常,简化错误处理。subprocess.PIPE
: 当你需要捕获子进程的输出或向其输入数据时,将其用于 stdout
, stderr
, stdin
参数。Popen
类:当需要更精细的控制,例如非阻塞 I/O、与进程进行持续的双向交互,或者需要并行运行多个子进程时使用。subprocess
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 from print_utils import *import subprocessimport sys import os print_header("subprocess 模块功能演示" ) print_subheader("1. 基本命令执行与等待 (subprocess.run)" ) list_files_command = ['ls' , '-la' ] if sys.platform != 'win32' else ['dir' , '/A' ] print_info(f"尝试执行命令: {' ' .join(list_files_command)} " ) try : completed_process_simple = subprocess.run( list_files_command, capture_output=True , text=True , check=False ) print_info(f" 命令实际执行: {' ' .join(completed_process_simple.args)} " ) print_info(f" 命令退出码: {completed_process_simple.returncode} " ) if completed_process_simple.returncode == 0 : print_success(" 命令成功执行。" ) print_info(" 标准输出 (前5行):" ) for line in completed_process_simple.stdout.splitlines()[:5 ]: print (f" {line} " ) else : print_warning(f" 命令执行可能存在问题 (退出码非0)。" ) if completed_process_simple.stderr: print_error(" 标准错误输出:" ) for line in completed_process_simple.stderr.splitlines()[:5 ]: print (f" {line} " ) except FileNotFoundError: print_error(f" 命令 '{list_files_command[0 ]} ' 未找到。请确保它在系统 PATH 中。" ) except Exception as e: print_error(f" 执行命令时发生意外错误: {e} " ) print_subheader("2. 自动错误检查 (check=True)" ) print_info("尝试执行一个不存在的命令,并使用 check=True:" ) try : subprocess.run( ["command_that_does_not_exist_12345" ], check=True , capture_output=True , text=True ) print_success(" (理论上这行不会执行,因为上面命令会失败)" ) except FileNotFoundError as e: print_warning(f" 命令未找到 (FileNotFoundError): {e} " ) except subprocess.CalledProcessError as e: print_warning(f" 命令执行失败 (CalledProcessError): {e} " ) print_info(f" 失败命令的返回码: {e.returncode} " ) print_info(f" 失败命令的输出 (stdout): {e.stdout.strip() if e.stdout else '无' } " ) print_info(f" 失败命令的错误 (stderr): {e.stderr.strip() if e.stderr else '无' } " ) print_subheader("3. 向子进程传递输入 (input 参数)" ) search_tool_command = ['grep' , 'Python' ] if sys.platform != 'win32' else ['findstr' , 'Python' ] text_to_search = "Hello World\nWelcome to Python scripting\nAnother line with Python here\nEnd of text" print_info(f"将向 {' ' .join(search_tool_command)} 命令输入以下文本:\n'''\n{text_to_search} \n'''" ) try : search_result = subprocess.run( search_tool_command, input =text_to_search, capture_output=True , text=True , check=False ) if search_result.returncode == 0 : print_success(" 找到匹配的行:" ) print (search_result.stdout) elif search_result.returncode == 1 and not search_result.stderr: print_warning(" 未找到任何匹配 'Python' 的行。" ) else : print_error(f" 命令执行出错或未找到,退出码: {search_result.returncode} " ) if search_result.stderr: print_error(f" 标准错误: {search_result.stderr.strip()} " ) except FileNotFoundError: print_error(f" 命令 '{search_tool_command[0 ]} ' 未找到。" ) print_subheader("4. 使用 shell=True (需要格外小心!)" ) shell_list_command = "ls -a" if sys.platform != 'win32' else "dir /A" print_warning(f"演示执行 shell 命令: '{shell_list_command} ' (通常应避免 shell=True)" ) try : shell_result = subprocess.run(shell_list_command, shell=True , capture_output=True , text=True , check=True ) print_info(" Shell 命令输出 (前5行):" ) for line in shell_result.stdout.splitlines()[:5 ]: print (f" {line} " ) except subprocess.CalledProcessError as e: print_error(f" Shell 命令执行失败: {e} " ) except FileNotFoundError: print_error(f" Shell 或命令未找到。" ) print_subheader("5. 使用 Popen 进行更精细控制" ) print_info("启动一个 Python 子进程,并通过管道与其交互:" ) python_child_code = "import sys; line = sys.stdin.readline().strip(); print(f'Child received and uppercased: {line.upper()}')" try : process = subprocess.Popen( [sys.executable, "-c" , python_child_code], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True , cwd=os.getcwd() ) print_info(f" Popen 子进程已启动 (PID: {process.pid} )。" ) input_to_child = "hello from parent process" print_info(f" 向子进程发送: '{input_to_child} '" ) stdout_data, stderr_data = process.communicate(input =input_to_child, timeout=5 ) print_info(f" 子进程退出码: {process.returncode} " ) if stdout_data: print_success(f" 子进程标准输出:\n {stdout_data.strip()} " ) if stderr_data: print_warning(f" 子进程标准错误:\n {stderr_data.strip()} " ) except subprocess.TimeoutExpired: print_error(" 与 Popen 子进程交互超时!正在尝试终止..." ) process.kill() stdout_data, stderr_data = process.communicate() print_warning(" 子进程已被终止。" ) except Exception as e: print_error(f" Popen 交互过程中发生错误: {e} " ) print_header("subprocess 模块演示结束。" )
5.日志与配置管理模块 在开发应用程序时,通过配置文件管理程序行为以及记录程序运行状态和错误信息(即日志)是至关重要的实践。Python 标准库为此提供了 configparser
和 logging
两个核心模块。
configparser
模块:读写 INI 配置文件configparser
模块用于处理结构化的配置文件,其格式类似于 Windows INI 文件。这种文件通常以 .ini
或 .cfg
为扩展名,由一个或多个“节 (section)”组成,每个节内包含若干“键=值 (key=value)”对。它非常适合存储应用程序的设置和参数。
示例配置文件 (example.ini
) 内容可能如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [DEFAULT] app_name = MyApplicationdebug_mode = false timeout = 30 [database] db_type = mysqlhost = 127.0 .0.1 port = 3306 username = db_userpassword = secret_password db_name = my_databaseconnection_retries = 3 [user_settings] theme = darklanguage = en-usnotifications_enabled = true items_per_page = 25
configparser
常用功能方法/类 描述 configparser.ConfigParser(...)
创建一个配置文件解析器对象,用于后续的读写操作。 config.read(filenames, encoding=None)
从指定的一个或多个文件 filenames
中读取配置信息。 config.sections()
返回配置文件中所有节的名称列表 (不包括 DEFAULT
节)。 config.has_section(section_name)
检查配置文件中是否存在名为 section_name
的节。 config.options(section_name)
返回指定节 section_name
下所有选项 (键) 的名称列表。 config.has_option(section, option)
检查指定的 section
中是否存在名为 option
的选项。 config.items(section_name)
返回指定节 section_name
下所有选项的 (键, 值) 对列表。 config.get(section, option, ...)
获取指定节 section
中选项 option
的值 (始终作为字符串返回)。 config.getint(section, option, ...)
获取选项值并将其转换为整数。 config.getfloat(section, option, ...)
获取选项值并将其转换为浮点数。 config.getboolean(section, option, ...)
获取选项值并将其转换为布尔值 (如 ‘true’, ‘yes’, ‘1’ 会转为 True
)。 config.add_section(section_name)
在配置中添加一个新的节 section_name
。 config.set(section, option, value)
设置指定节 section
中选项 option
的值为 value
(注意 value
必须是字符串)。 config.remove_option(section, option)
从指定节 section
中移除选项 option
。 config.remove_section(section)
移除整个节 section
及其所有选项。 config.write(fileobject, ...)
将当前的配置数据写入到一个已打开的文件对象 fileobject
中。
批注:核心记忆功能 (configparser
模块)
ConfigParser()
: 创建实例是第一步。read(filename)
: 加载配置文件内容。sections()
, options(section)
, items(section)
: 遍历配置结构。get(section, option)
: 获取配置值 (字符串)。getint()
, getfloat()
, getboolean()
: 常用 ,获取特定类型的值。set(section, option, value)
: 修改或添加配置项。write(file_object)
: 保存更改到文件。configparser
模块代码示例 (读取、修改、创建)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 from print_utils import *import configparserimport osprint_header("configparser 模块功能演示" ) temp_config_filename = "userConfig.ini" temp_config_content = """ [DEFAULT] name = John Doe age = 30 [DATABASE] host = localhost port = 3306 user = root password = root is_connected = True [USER] username = admin password = admin """ with open (temp_config_filename, "w" , encoding="utf-8" ) as f: f.write(temp_config_content.strip()) print_info(f"已创建临时配置文件: {temp_config_filename} " ) config = configparser.ConfigParser() def read_config_file (temp_config_filename: str = "userConfig.ini" ) -> None : print_subheader("1. 读取配置文件" ) files_read = config.read(temp_config_filename, encoding="utf-8" ) if not files_read: print_error(f"错误: 无法读取配置文件 '{temp_config_filename} '" ) else : print_success(f"成功读取配置文件: {files_read} " ) def check_config_sections () -> None : print_info(f"所有节 (不包括 DEFAULT): {config.sections()} " ) if "DEFAULT" in config: print_info("DEFAULT 节中的默认值:" ) for key, value in config.defaults().items(): print (f" {key} = {value} " ) database_config = "DATABASE" if config.has_section(database_config): print_info(f"'{database_config} ' 节的选项 (options): {config.options(database_config)} " ) host_value = config.get(database_config, "host" ) port_value_int = config.getint(database_config, "port" ) is_connected_value_bool = config.getboolean(database_config, "is_connected" ) print_info(f" {database_config} 节中的 'host' 值: {host_value} -> {type (host_value)} " ) print_info(f" {database_config} 节中的 'port' 值: {port_value_int} -> {type (port_value_int)} " ) print_info( f" {database_config} 节中的 'is_connected' 值: {is_connected_value_bool} -> {type (is_connected_value_bool)} " ) default_name = config.get("DEFAULT" , "name" ) default_age = config.getint("DEFAULT" , "age" ) print_info(f"DEFAULT 节中的 'name' 值: {default_name} -> {type (default_name)} " ) print_info(f"DEFAULT 节中的 'age' 值: {default_age} -> {type (default_age)} " ) def modify_config_file () -> None : print_subheader("2. 修改配置文件内容 (在内存中)" ) if config.has_section("DATABASE" ): config.set ("DATABASE" , "port" , "3307" ) config.set ("DATABASE" , "is_connected" , "False" ) print_success("已修改 'DATABASE' 节中的值" ) config.set ("DATABASE" , "database" , "mydb" ) print_success("已添加新的选项 'database'" ) config.add_section("LOGGING" ) config.set ("LOGGING" , "level" , "INFO" ) print_success("已添加新的节 'LOGGING'" ) if config.has_option("DATABASE" , "password" ): config.remove_option("DATABASE" , "password" ) print_success("已移除 'password' 选项" ) def write_config_file () -> None : print_subheader("3. 将修改后的配置写回文件" ) with open (temp_config_filename, "w" , encoding="utf-8" ) as configfile: config.write(configfile) print_success(f"已将修改后的配置写回文件: {temp_config_filename} " ) def create_new_config_file () -> None : print_subheader("4. 以编程方式创建全新的配置文件" ) new_config = configparser.ConfigParser() new_config["DEFAULT" ] = { "name" : "Alice" , "age" : 25 } new_config['WebApp' ] = {} web_app_section = new_config['WebApp' ] web_app_section['base_url' ] = 'https://myapp.com' web_app_section['api_key' ] = 'YOUR_API_KEY_HERE' web_app_section['debug_port' ] = str (9000 ) new_config.add_section('FTPCredentials' ) new_config.set ('FTPCredentials' , 'ftp_host' , 'ftp.example.com' ) new_config.set ('FTPCredentials' , 'ftp_user' , 'ftp_user_01' ) new_config.set ('FTPCredentials' , 'use_passive_mode' , 'True' ) print_info("为新配置添加了 'WebApp' 和 'FTPCredentials' 节及选项。" ) created_config_filename = "temp_created_config.ini" try : with open (created_config_filename, 'w' , encoding="utf-8" ) as f_new_config: new_config.write(f_new_config) print_success(f"新创建的配置已写入到: {created_config_filename} " ) except IOError as e: print_error(f"写入新创建的配置文件失败: {e} " ) if __name__ == '__main__' : read_config_file() check_config_sections() modify_config_file() write_config_file()
logging
模块:Python的日志记录系统(核心)logging
模块是 Python 内置的、功能强大且灵活的日志记录框架。良好的日志记录对于应用程序的调试、监控、问题排查和审计至关重要。它允许开发者根据日志的重要性(级别)将其发送到不同的目的地(如控制台、文件、网络等),并可以自定义日志的格式。
logging
模块基本使用与日志级别日志级别从低到高表示日志信息的重要性:
日志级别 (Level) 数值 (Value) 描述 DEBUG
10 最详细的诊断信息,通常只在调试问题时关心。 INFO
20 确认程序按预期运行的常规信息。 WARNING
30 表明发生了意外情况,或在不久的将来可能出现问题,但程序仍能按预期工作。 ERROR
40 由于更严重的问题,程序未能执行某些功能。 CRITICAL
50 严重错误,表明程序本身可能无法继续运行。
Python 的 logging
模块默认的日志级别是 WARNING
。这意味着,如果你不进行任何配置,只有 WARNING
, ERROR
, CRITICAL
级别的日志消息会被处理和显示。
logging.basicConfig(**kwargs)
是一个快速配置日志系统的便捷函数,通常在脚本的早期调用。如果root logger已经配置过handlers,它将不起作用。
basicConfig
常用参数描述 level
设置日志记录器处理消息的最低级别 (如 logging.DEBUG
, logging.INFO
)。 format
定义日志消息的输出格式字符串。 datefmt
如果 format
中包含时间 (%(asctime)s
),此参数定义时间的显示格式。 filename
指定日志输出到的文件名;如果省略,则输出到控制台 (sys.stderr
)。 filemode
如果指定了 filename
,此参数定义文件的打开模式 (如 'a'
追加, 'w'
覆盖)。
批注:核心记忆功能 (logging
模块 - 基础)
日志级别 :理解五个基本级别及其含义。logging.basicConfig()
: 进行简单、快速的日志配置的首选方法,尤其适用于小型脚本或应用的初期。logging.debug()
, logging.info()
, logging.warning()
, logging.error()
, logging.critical()
: 发出不同级别日志消息的函数。默认级别是 WARNING
: 如果不配置,DEBUG
和 INFO
消息不会显示。logging
模块基础配置代码示例 (basicConfig
)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from print_utils import * import loggingimport osprint_header("logging 模块基础配置 (basicConfig) 演示" ) def print_logging_basic (): print_subheader("1. 最简单的日志记录 (默认配置)" ) logging.debug("这是一条 DEBUG 消息 (默认情况下不显示)。" ) logging.info("这是一条 INFO 消息 (默认情况下不显示)。" ) logging.warning("这是一条 WARNING 消息 (默认显示)。" ) logging.error("这是一条 ERROR 消息 (默认显示)。" ) logging.critical("这是一条 CRITICAL 消息 (默认显示)。" ) print_info("注意:上面 DEBUG 和 INFO 级别的日志在默认情况下不会打印。" ) def print_logging_basic_level (): print_subheader("2. 让所有日志级别都能打印出来" ) logging.basicConfig(level=logging.DEBUG) logging.debug("这是一条 DEBUG 消息 (现在显示)。" ) logging.info("这是一条 INFO 消息 (现在显示)。" ) if __name__ == '__main__' : print_logging_basic_level()
对于更复杂的日志需求,basicConfig()
可能不够用。这时需要了解 logging
模块的三个核心组件:
Loggers (日志记录器) :它是与应用程序代码直接打交道的对象。 你可以通过 logging.getLogger(name)
获取 Logger 实例。通常使用模块名 __name__
作为 logger 名称 (例如 logger = logging.getLogger(__name__)
),这样可以方便地根据日志来源模块来配置和过滤日志。 Loggers 有层级关系,类似 Python 的包名。例如,名为 app.module1
的 logger 是名为 app
的 logger 的子级。 Loggers 可以设置自己的日志级别。如果一个 logger 的级别未设置,它会继承其父级 logger 的级别,最终到 root logger。 日志消息会沿着 logger 层级向上传播 (propagate) 到父级 logger 的 handlers,除非将 logger 的 propagate
属性设为 False
。 Handlers (处理器) :决定日志消息的最终去向 。一个 logger 可以关联多个 handler。常见的 Handlers 类型包括:StreamHandler
: 将日志发送到流,如 sys.stdout
或 sys.stderr
(控制台)。FileHandler
: 将日志写入磁盘文件。RotatingFileHandler
: 支持日志文件按大小轮转(达到一定大小时创建新文件)。TimedRotatingFileHandler
: 支持日志文件按时间间隔轮转(如每天、每小时)。NullHandler
: 什么也不做,常用于库模块中,让应用开发者决定如何处理库的日志。还有用于网络发送 (如 HTTPHandler
, SMTPHandler
)、系统日志等的 handlers。 Handlers 也可以设置自己的日志级别,只处理高于或等于该级别的日志消息。 Formatters (格式化器) :定义最终输出的日志消息的布局和格式。创建一个 Formatter 对象时,可以指定一个格式字符串,其中包含各种预定义的属性(如 %(asctime)s
时间, %(levelname)s
级别名, %(message)s
日志内容, %(name)s
logger名, %(module)s
模块名, %(lineno)d
行号等)。 创建好的 Formatter 对象需要附加到一个或多个 Handler 上。 批注:核心记忆功能 (logging
模块 - 组件)
logging.getLogger(name)
: 获取 Logger 实例,是进行高级日志配置的入口。Handlers 决定日志去哪儿 (控制台、文件、网络等),并可设置级别。Formatters 决定日志长什么样 (时间格式、包含哪些信息等)。Logger -> Handler -> Formatter 是日志消息处理的基本流程。 logging
模块高级配置代码示例 (使用字典配置 dictConfig
)使用字典来配置日志是推荐的方式,因为它清晰、灵活,并且可以从 JSON 或 YAML 等配置文件加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 from print_utils import * import loggingimport logging.configimport osimport sysdef setup_advanced_logging (): """ 设置并配置高级日志系统 """ print_header("logging 模块高级配置 (dictConfig) 演示" ) log_directory = "app_logs_adv" os.makedirs(log_directory, exist_ok=True ) application_log_file = os.path.join(log_directory, "advanced_app.log" ) error_log_file = os.path.join(log_directory, "error_only.log" ) print_subheader("1. 定义日志配置字典" ) logging_config = { 'version' : 1 , 'disable_existing_loggers' : False , 'formatters' : { 'verbose' : { 'format' : '%(asctime)s - %(name)s - [%(levelname)s] - (%(module)s.%(funcName)s:%(lineno)d) - %(message)s' , 'datefmt' : '%Y-%m-%d %H:%M:%S' }, 'simple' : { 'format' : '[%(levelname)s] %(asctime)s: %(message)s' , 'datefmt' : '%H:%M:%S' }, },z 'filters' : {}, 'handlers' : { 'console_stdout' : { 'class' : 'logging.StreamHandler' , 'level' : 'DEBUG' , 'formatter' : 'simple' , 'stream' : sys.stdout, }, 'app_file_handler' : { 'class' : 'logging.handlers.RotatingFileHandler' , 'level' : 'INFO' , 'formatter' : 'verbose' , 'filename' : application_log_file, 'maxBytes' : 1024 * 1024 * 5 , 'backupCount' : 3 , 'encoding' : 'utf-8' , }, 'error_file_handler' : { 'class' : 'logging.FileHandler' , 'level' : 'ERROR' , 'formatter' : 'verbose' , 'filename' : error_log_file, 'encoding' : 'utf-8' , 'mode' : 'a' } }, 'loggers' : { 'my_app_module' : { 'handlers' : ['console_stdout' , 'app_file_handler' , 'error_file_handler' ], 'level' : 'DEBUG' , 'propagate' : False , }, 'another_module' : { 'handlers' : ['console_stdout' ], 'level' : 'INFO' , 'propagate' : False , }, } } print_info("日志配置字典已定义。" ) print_subheader("2. 应用字典配置" ) logging.config.dictConfig(logging_config) print_success("已使用 dictConfig 应用日志配置。" ) return logging_config def demonstrate_logging_usage (): """ 演示如何使用配置好的日志系统 """ print_subheader("3. 获取并使用 Logger 实例" ) app_logger = logging.getLogger('my_app_module' ) print_info("获取 logger: 'my_app_module'" ) app_logger.debug("这是来自 'my_app_module' 的 DEBUG 日志。 (应出现在控制台)" ) app_logger.info("这是来自 'my_app_module' 的 INFO 日志。 (应出现在控制台和 app_file)" ) app_logger.warning("这是来自 'my_app_module' 的 WARNING 日志。 (处理同 INFO)" ) app_logger.error("这是来自 'my_app_module' 的 ERROR 日志。 (应出现在控制台、app_file 和 error_file)" ) app_logger.critical("这是来自 'my_app_module' 的 CRITICAL 日志。 (处理同 ERROR)" ) other_logger = logging.getLogger('another_module' ) print_info("\n获取 logger: 'another_module'" ) other_logger.debug("这是来自 'another_module' 的 DEBUG 日志。 (不应出现,因为 other_logger 级别是 INFO)" ) other_logger.info("这是来自 'another_module' 的 INFO 日志。 (应出现在控制台)" ) other_logger.error("这是来自 'another_module' 的 ERROR 日志。 (应出现在控制台)" ) unconfigured_logger = logging.getLogger('some.other.unconfigured.module' ) print_info("\n获取 logger: 'some.other.unconfigured.module' (将使用 root logger 配置)" ) unconfigured_logger.warning("这是来自 'unconfigured' logger 的 WARNING。(行为取决于 root logger)" ) setup_advanced_logging() demonstrate_logging_usage()
logging
模块使用文件配置 (.ini
/.cfg
)除了字典配置,logging
模块也支持从类似 INI 格式的配置文件加载配置,这通过 logging.config.fileConfig()
函数实现。
示例 log.cfg
文件内容 (简化版):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 [loggers] keys =root,module_logger[handlers] keys =consoleHandler,fileHandler[formatters] keys =simpleFormatter,detailFormatter[logger_root] level =WARNINGhandlers =consoleHandler[logger_module_logger] level =DEBUGhandlers =consoleHandler,fileHandlerqualname =my_app.moduleX propagate =0 [handler_consoleHandler] class =StreamHandlerlevel =DEBUGformatter =simpleFormatterargs =(sys.stdout,)[handler_fileHandler] class =FileHandlerlevel =INFOformatter =detailFormatterargs =('app_from_fileconfig.log' , 'a' , 'utf-8' ) [formatter_simpleFormatter] format =%(asctime)s - %(name)s - %(levelname)s - %(message)sdatefmt =%H:%M:%S[formatter_detailFormatter] format =%(asctime)s [%(levelname)-8 s] %(name)s (%(filename)s:%(lineno )d) %(message)sdatefmt =%Y-%m-%d %H:%M:%S
使用 fileConfig
加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 from print_utils import *import loggingimport logging.configimport osprint_header("logging 模块 fileConfig 演示" ) temp_log_cfg_content = """ [loggers] keys=root,sampleLogger [handlers] keys=consoleHandler [formatters] keys=simpleFormatter [logger_root] level=WARNING handlers=consoleHandler [logger_sampleLogger] level=DEBUG handlers=consoleHandler qualname=sampleApp propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,) [formatter_simpleFormatter] format=[%(levelname)s] %(name)s: %(message)s datefmt= """ cfg_filename = "temp_log_for_fileconfig.cfg" with open (cfg_filename, "w" , encoding="utf-8" ) as f_cfg: f_cfg.write(temp_log_cfg_content) print_info(f"已创建临时日志配置文件: {cfg_filename} " ) print_subheader("1. 使用 fileConfig 加载配置" ) try : logging.config.fileConfig(cfg_filename, disable_existing_loggers=False , encoding='utf-8' ) print_success(f"已从 '{cfg_filename} ' 加载日志配置。" ) print_subheader("2. 使用配置好的 Logger" ) logger_from_file = logging.getLogger('sampleApp' ) logger_from_file.debug("这是一条来自 fileConfig 配置的 DEBUG 日志 (sampleApp)。" ) logger_from_file.info("这是一条来自 fileConfig 配置的 INFO 日志 (sampleApp)。" ) root_logger_test = logging.getLogger("non_existent_so_root" ) root_logger_test.warning("这是一条来自 root logger 的 WARNING 日志 (通过 fileConfig 配置)。" ) root_logger_test.debug("这是一条来自 root logger 的 DEBUG 日志 (root 级别是 WARNING,所以不显示)。" ) except Exception as e: print_error(f"使用 fileConfig 加载配置时发生错误: {e} " ) print_warning("注意:fileConfig 对编码的处理可能不如 dictConfig 灵活。" ) print_warning("如果遇到编码问题,可能需要确保配置文件不含 BOM,或在旧版 Python 中修改 configparser 源码(不推荐)。" ) if os.path.exists(cfg_filename): os.remove(cfg_filename) print_info(f"已删除临时文件: {cfg_filename} " ) print_header("fileConfig 演示结束。" )
fileConfig
的编码问题 :fileConfig
在 Windows 下处理 UTF-8 文件(尤其是有 BOM 的)时可能存在问题,需要修改 configparser
源码的 read
方法来指定编码。这是一个历史问题。好消息是 :从 Python 3.10 开始,logging.config.fileConfig()
函数增加了 encoding
参数,可以直接指定文件编码,例如 logging.config.fileConfig('log.conf', encoding='utf-8')
。对于旧版 Python,如果遇到此问题,更推荐的做法是:
确保配置文件本身保存为不带 BOM 的 UTF-8。 或者,考虑将配置迁移到使用 dictConfig
,因为 dictConfig
从 Python 字典加载配置,字典可以从任何来源(如 JSON, YAML 文件)以正确的编码读取,从而避免 fileConfig
的编码限制。 logging
模块开箱即用场景a. 为脚本快速设置日志记录到文件和控制台 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 from print_utils import *import loggingimport sysimport osdef setup_script_logging (log_file_name="script.log" , console_level=logging.INFO, file_level=logging.DEBUG ): """ 为脚本设置一个简单的日志记录器,同时输出到控制台和文件。 """ print_info(f"尝试设置日志,输出到控制台 (级别: {logging.getLevelName(console_level)} ) 和文件 '{log_file_name} ' (级别: {logging.getLevelName(file_level)} )" ) logger = logging.getLogger("MyScriptLogger" ) logger.setLevel(logging.DEBUG) if logger.hasHandlers(): print_warning("Logger 已有 handlers,不再重复添加。" ) console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(console_level) console_formatter = logging.Formatter('%(asctime)s [%(levelname)-7s] %(message)s' , datefmt='%H:%M:%S' ) console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) try : log_dir = os.path.dirname(log_file_name) if log_dir and not os.path.exists(log_dir): os.makedirs(log_dir, exist_ok=True ) print_info(f"创建日志目录: {log_dir} " ) file_handler = logging.FileHandler(log_file_name, mode='a' , encoding='utf-8' ) file_handler.setLevel(file_level) file_formatter = logging.Formatter('%(asctime)s - %(name)s - [%(levelname)-8s] (%(filename)s:%(lineno)d) - %(message)s' , datefmt='%Y-%m-%d %H:%M:%S' ) file_handler.setFormatter(file_formatter) logger.addHandler(file_handler) print_success(f"日志将记录到文件: {os.path.abspath(log_file_name)} " ) except Exception as e: print_error(f"设置文件日志处理器失败: {e} " ) return logger if __name__ == "__main__" : print_header("setup_script_logging 函数演示" ) my_logger = setup_script_logging(log_file_name="logs/my_app_activity.log" ) my_logger.debug("这是一条详细的调试信息,用于诊断。" ) my_logger.info("应用程序已开始正常运行。" ) my_logger.warning("一个非关键性问题发生,但不影响核心功能。" ) my_logger.error("处理请求时发生错误,操作失败。" ) another_part_logger = logging.getLogger("MyScriptLogger" ) another_part_logger.info("来自应用另一部分的信息。" ) print_info("\n尝试再次调用 setup_script_logging (应提示已有 handlers):" ) setup_script_logging() my_logger.info("第二次调用 setup 后的日志。" ) print_header("日志记录演示结束。请检查控制台输出和 'logs/my_app_activity.log' 文件。" )
Loguru
模块:更简单的日志记录Loguru
是一个golang语言非常受欢迎的第三方日志库,在Python中也有类似的日志库,其设计目标是让日志记录变得尽可能简单和愉悦,同时又不失强大和灵活。它提供了许多开箱即用的功能,大大减少了配置日志所需的样板代码,尽管我们是介绍标准内置库,但原生logging的配置流程实在是太过繁琐
Loguru
核心特性与常用功能方法/特性 描述 from loguru import logger
导入 Loguru 的核心 logger
对象,大多数操作都通过它进行。 logger.add(sink, ...)
核心方法 :添加一个新的“接收器”(sink)来处理日志消息,如文件、函数或流。logger.debug("...")
至 logger.critical("...")
与标准库类似的日志记录方法,用于发出不同级别的日志消息。 logger.exception("...")
记录一条 ERROR
级别的消息,并自动附加当前的异常信息和堆栈跟踪。 logger.catch(exception=Exception, ...)
一个装饰器,可以优雅地捕获函数中发生的异常并自动记录它们。 logger.bind(**kwargs)
将自定义的键值对数据绑定到后续的日志记录中,方便实现结构化日志。 logger.remove(handler_id=None)
移除之前通过 add()
添加的日志处理器 (sink)。 add()
常用参数:sink
日志的输出目标 (文件名字符串、文件对象、可调用对象等)。 level="DEBUG"
此 sink 处理的最低日志级别。 format="..."
定义此 sink 输出日志的格式字符串 (Loguru 有自己丰富的格式化标记)。 rotation="..."
设置日志文件轮转条件 (如 "500 MB"
, "1 week"
, "00:00"
每天午夜)。 retention="..."
设置旧日志文件的保留策略 (如 "10 days"
, 5
保留最新的5个文件)。 compression="..."
设置日志文件压缩格式 (如 "zip"
, "gz"
)。 serialize=True
将日志消息序列化为 JSON 格式输出,用于结构化日志。 enqueue=True
异步记录日志,将日志消息放入队列中处理,可提高性能。
批注:核心记忆功能 (Loguru
模块)
logger.add()
: 最重要的方法 ,几乎所有的日志输出配置都通过它完成,极其灵活。logger.debug()
, info()
, warning()
, error()
, critical()
: 日常使用的日志记录方法。logger.exception()
: 记录错误和异常信息时非常方便。format
参数 (在 add()
中): 灵活定义日志输出格式。rotation
, retention
, compression
参数 (在 add()
中): 强大的日志文件管理功能。Loguru
模块代码示例重要提示 : Loguru
是一个第三方库,您需要先安装它:pip install loguru
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 import sys import os import time import logurufrom loguru import logger from print_utils import *print_header("Loguru 模块功能演示" ) def demo_loguru (): print_info("Loguru 默认会将日志输出到 stderr,并带有颜色和良好格式。" ) logger.info("这是默认的日志信息" ) logger.warning("这是警告信息" ) logger.error("这是错误信息" ) logger.debug("这是调试信息" ) logger.success("这是成功信息" ) logger.critical("这是严重错误信息" ) logger.exception("这是异常信息" ) logger.log("INFO" , "这是自定义日志级别" ) def append_stdout (): print_subheader("1. 添加基本控制台输出 (stdout)" ) logger.remove() logger.add( sys.stdout, level="DEBUG" , format ="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>" , colorize=True , ) print_success("已添加一个输出到stdout的Loguru处理器" ) logger.info("这是添加了处理器后的日志信息" ) logger.debug("这条消息不会被输出到stdout,因为级别低于DEBUG" ) logger.warning("这是一个警告信息" ) logger.error("这是一个错误信息" ) logger.success("这是一个成功信息" ) logger.critical("这是一个严重错误信息" ) def append_file (): print_subheader("2. 添加文件输出,并配置轮转和保留" ) logger.remove() log_file_path = "logs/loguru_app_{time:YYYY-MM-DD}.log" error_log_file_path = "logs/loguru_error_{time}.log" os.makedirs("logs" , exist_ok=True ) logger.add( log_file_path, level="DEBUG" , rotation="10 KB" , retention=5 , compression="zip" , format ="{time:HH:mm:ss} | {level} | {module}.{function} | {message}" , encoding="utf-8" ) print_success(f"已添加一个输出到 '{log_file_path} ' 的文件处理器 (INFO级别, 10KB轮转, 保留5个, zip压缩)。" ) logger.add( error_log_file_path, level="ERROR" , format ="<red>{time}</red> - {level} - {name}:{function}:{line} - {message}" , rotation="1 MB" , retention="30 days" , encoding="utf-8" ) print_success(f"已添加一个输出到 '{error_log_file_path} ' 的错误日志处理器 (ERROR级别)。" ) [logger.info(f"这是第 {i} 条日志信息" ) for i in range (1 , 100000 )] @logger.catch def divide (a, b ): return a / b def demo_catch (): print_subheader("4. 使用 @logger.catch 装饰器捕获函数异常" ) divide(1 , 0 ) divide(1 , 2 ) def demo_bind (): print_subheader("5. 使用 logger.bind() 实现结构化日志 (或添加上下文)" ) logger.remove() logger.add( sys.stdout, level="INFO" , format ="{time:HH:mm:ss} | {level: <7} | {extra} | {message}" , colorize=True ) print_success("已重新配置 stdout handler 以显示 {extra}。" ) user_logger = logger.bind(user_id=123 , session_id="abcdef123" ) user_logger.info("用户登录成功。" ) user_logger.warning("用户尝试访问未授权资源。" , resource_path="/admin" ) logger.bind(ip="192.168.1.100" ).info("来自特定IP的请求。" ) def demo_json (): print_subheader("6. 序列化为 JSON (结构化日志)" ) json_log_file = "logs/structured_logs.json" logger.remove() logger.add( json_log_file, level="INFO" , serialize=True , format ="{time:HH:mm:ss} | {level: <7} | {extra} | {message}" , rotation="1 MB" , encoding="utf-8" ) print_success(f"已添加一个输出到 '{json_log_file} ' 的 JSON 格式文件处理器。" ) logger.bind(transaction_id="txn_789" ).info( "订单已处理" , order_id=456 , customer_email="customer@example.com" , amount=99.99 , items_count=3 ) if __name__ == '__main__' : demo_json()
Loguru
模块开箱即用场景a. 快速为脚本设置控制台和文件日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 from print_utils import *from loguru import loggerimport sysimport osdef setup_quick_loguru_logging ( console_level="INFO" , log_file="app_activity_{time:YYYY-MM-DD}.log" , file_level="DEBUG" , log_dir="my_app_logs" ): """ 快速配置 Loguru,将日志同时输出到控制台和带轮转的文件。 """ print_info("正在配置 Loguru 日志系统..." ) os.makedirs(log_dir, exist_ok=True ) full_log_file_path = os.path.join(log_dir, log_file) logger.remove() logger.add( sys.stderr, level=console_level.upper(), format ="<level>{level: <8}</level> | <cyan>{time:HH:mm:ss}</cyan> | <level>{message}</level>" , colorize=True ) print_success(f"Loguru 控制台日志已配置 (级别: {console_level.upper()} )。" ) logger.add( full_log_file_path, level=file_level.upper(), format ="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} | {message}" , rotation="10 MB" , retention="7 days" , compression="zip" , encoding="utf-8" , enqueue=True ) print_success(f"Loguru 文件日志已配置 (级别: {file_level.upper()} ),路径: '{os.path.abspath(full_log_file_path)} '" ) logger.info("Loguru 日志系统初始化完成。" ) if __name__ == "__main__" : print_header("快速设置 Loguru 日志场景演示" ) setup_quick_loguru_logging(log_dir="project_X_logs" ) logger.debug("这是一条 DEBUG 级别的调试信息,只有文件日志会记录。" ) logger.info("用户 'Alice' 登录了系统。" ) logger.warning("配置项 'API_KEY' 未找到,使用默认值。" ) try : items = [] items[0 ] except IndexError: logger.exception("处理项目列表时发生错误!" ) logger.info("脚本演示部分执行完毕。" ) print_header("请检查控制台输出和 'project_X_logs' 目录下的日志文件。" )
自此,Python最常用的标准库就介绍完了=>(Python是全世界三方库生态最好的语言),所以掌握一定的标准库即可,剩下的库作为了解或是做底层开发会合适
6.较为不常用实用库 Python 标准库中包含了许多小而精的模块,它们为日常开发中的特定任务提供了便捷的解决方案。本节将介绍几个常用的工具模块:contextlib
用于简化上下文管理,tempfile
用于处理临时文件和目录,glob
用于文件路径的模式匹配,以及 uuid
用于生成通用唯一标识符。
contextlib
模块:简化上下文管理器上下文管理器是 Python 中一种重要的编程构造,主要通过 with
语句来使用。它能够帮助我们自动获取和释放资源,或者在代码块执行前后执行特定操作(如加锁/解锁,打开/关闭文件)。contextlib
模块提供了一些工具来更方便地创建和使用上下文管理器。
contextlib
常用功能这个模块最常用的只有**@contextlib.contextmanage**这个装饰器,其他的方法并不常用,简单了解即可
函数/装饰器 描述 @contextlib.contextmanager
一个装饰器,能让你用简单的生成器函数快速创建支持 with
语句的上下文管理器。 contextlib.closing(thing)
为那些有 close()
方法但本身不是上下文管理器的对象创建一个上下文管理器,确保其 close()
被调用。 contextlib.suppress(*exceptions)
创建一个上下文管理器,在其管理的 with
代码块中可以忽略指定的几种异常类型。 contextlib.ExitStack()
一个上下文管理器,可以动态地注册和管理多个其他的上下文管理器,确保它们都能被正确清理。 contextlib.nullcontext(enter_result=None)
一个什么也不做的上下文管理器,有时用于在条件分支中提供一个“空操作”的 with
目标。
批注:核心记忆功能 (contextlib
模块)
@contextlib.contextmanager
: 非常常用 ,是将自定义资源管理逻辑封装为上下文管理器的最便捷方式。contextlib.closing()
: 当遇到只有 close()
方法的老旧对象时很有用。contextlib.suppress()
: 优雅地处理某些预期内且不需要特别处理的异常。contextlib.ExitStack()
: 管理多个或动态数量的资源时非常强大。contextlib
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from print_utils import *import contextlibimport timeimport osfrom typing import List , Any print_header("contextlib 模块功能演示" ) print_subheader("1. 使用 @contextlib.contextmanager 创建计时器" ) """ yield 之前的部分 ≈ __enter__ 方法: 当 with 语句开始执行时, @contextlib.contextmanager 装饰的生成器函数中,yield 关键字之前的所有代码会被执行。这部分代码通常用来做一些准备工作,比如打开文件、连接数据库、获取资源等。如果 yield 语句产生了一个值 (例如 yield resource),这个值会赋给 with ... as var 中的 var。 yield 执行的时刻: 程序执行到 yield 时,会将控制权交还给 with 语句块内部的代码,让 with 块中的代码开始执行。 yield 之后的部分 (通常在 finally 块中) ≈ __exit__ 方法: 当 with 语句块中的代码执行完毕(无论是正常结束还是发生异常),程序会回到生成器函数中,从 yield 语句之后的地方继续执行。这部分代码(通常放在 finally 块中以确保其总能执行)用来做清理工作,比如关闭文件、释放资源、回滚事务等。finally 块保证了即使 with 块中发生错误,清理操作也能进行。 简单来说,@contextlib.contextmanager 巧妙地利用了生成器的特性,将一个普通的生成器函数变成了符合上下文管理器协议的对象,使得 yield 前的代码在进入 with 块时运行,yield 后的代码在退出 with 块时运行。这种方式比手动定义一个包含 __enter__ 和 __exit__ 方法的类要简洁得多。 """ @contextlib.contextmanager def simple_timer (label: str = "默认计时器" ): start = time.perf_counter() try : yield finally : end = time.perf_counter() print (f"{label} 耗时: {end - start:.6 f} 秒" ) print_info(f"计时器 {label} 结束。耗时: {end - start:.4 f} 秒" ) with simple_timer("文件读取" ): print_info("模拟读取文件内容..." ) time.sleep(2 ) with simple_timer("数据处理" ): print_info("模拟数据处理..." ) time.sleep(1 )
tempfile
模块:处理临时文件和目录在程序运行过程中,有时需要创建一些临时的文件或目录来存储中间数据,这些数据在程序结束或不再需要时应该被自动清理。tempfile
模块为此提供了安全且便捷的解决方案。
tempfile
常用功能函数/类 描述 tempfile.TemporaryFile(mode='w+b', ...)
创建一个匿名的临时文件对象 (没有可见的文件名),文件关闭时自动删除。 tempfile.NamedTemporaryFile(mode='w+b', delete=True, ...)
创建一个带名字的临时文件,文件关闭时通常也会自动删除 (除非 delete=False
)。 tempfile.SpooledTemporaryFile(max_size=0, ...)
创建一个在内存中操作的临时文件,当内容超过 max_size
字节时,会自动溢出到磁盘。 tempfile.TemporaryDirectory(suffix=None, ...)
创建一个临时目录,当上下文管理器退出或目录对象被垃圾回收时,目录及其内容会被自动删除。 tempfile.gettempdir()
获取系统用于存放临时文件的默认目录路径。 tempfile.gettempprefix()
获取生成临时文件或目录名称时使用的默认前缀。
批注:核心记忆功能 (tempfile
模块)
NamedTemporaryFile()
: 当你需要一个临时文件并且需要知道它的文件名时使用,通常配合 with
语句。TemporaryDirectory()
: 当你需要一个临时的目录来存放多个文件,并且希望它能自动清理时使用,通常配合 with
语句。TemporaryFile()
: 用于完全匿名的临时文件,不需要文件名。tempfile
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 from print_utils import *import tempfileimport osimport shutil print_header("tempfile 模块功能演示" ) print_subheader("1. 获取系统临时目录信息" ) system_temp_dir = tempfile.gettempdir() print_info(f"系统默认的临时目录路径: {system_temp_dir} " ) temp_prefix = tempfile.gettempprefix() print_info(f"生成临时文件/目录的默认前缀: {temp_prefix} " ) print_subheader("2. 创建匿名的临时文件 (TemporaryFile)" ) def create_temporary_file (): try : with tempfile.TemporaryFile(mode="w+t" , encoding="utf-8" ) as tf: print_info(f"临时文件创建成功: {tf.name} " ) tf.write("这是一个临时文件的内容" ) tf.write("\n这是第二行内容" ) tf.seek(0 ) content = tf.read() print_info(f"临时文件内容: {content} " ) print_success("匿名临时文件已在 with 块结束时自动关闭并删除" ) except Exception as e: print_error(f"创建临时文件时发生错误: {e} " ) def create_named_temporary_file (): try : with tempfile.NamedTemporaryFile(mode='w+t' , encoding='utf-8' , prefix='my_app_temp_' , suffix='.data' , delete=True ) as nf: print_info(f"带名字的临时文件创建成功: {nf.name} " ) nf.write("这是一个带名字的临时文件的内容" ) nf.write("\n这是第二行内容" ) nf.seek(0 ) content = nf.read() print_info(f"临时文件内容: {content} " ) print_success("带名字的临时文件已在 with 块结束时自动关闭" ) except Exception as e: print_error(f"创建带名字的临时文件时发生错误: {e} " ) def create_temporary_directory (): print_subheader("4.创建临时目录 (TemporaryDirectory)" ) try : with tempfile.TemporaryDirectory() as temp_dir: print_info(f"临时目录创建成功: {temp_dir} " ) temp_file_in_dir = os.path.join(temp_dir, "temp_file.txt" ) with open (temp_file_in_dir, "w" ) as f_in_dir: f_in_dir.write("这是文件在临时目录中的内容" ) print_info(f"临时文件创建成功: {temp_file_in_dir} " ) print_success("临时目录已在 with 块结束时自动删除" ) except Exception as e: print_error(f"创建临时目录时发生错误: {e} " ) if __name__ == '__main__' : create_temporary_file() create_named_temporary_file() create_temporary_directory() from print_utils import *import tempfileimport osimport shutil print_header("tempfile 模块功能演示" ) print_subheader("1. 获取系统临时目录信息" ) system_temp_dir = tempfile.gettempdir() print_info(f"系统默认的临时目录路径: {system_temp_dir} " ) temp_prefix = tempfile.gettempprefix() print_info(f"生成临时文件/目录的默认前缀: {temp_prefix} " ) print_subheader("2. 创建匿名的临时文件 (TemporaryFile)" ) def create_temporary_file (): try : with tempfile.TemporaryFile(mode="w+t" , encoding="utf-8" ) as tf: print_info(f"临时文件创建成功: {tf.name} " ) tf.write("这是一个临时文件的内容" ) tf.write("\n这是第二行内容" ) tf.seek(0 ) content = tf.read() print_info(f"临时文件内容: {content} " ) print_success("匿名临时文件已在 with 块结束时自动关闭并删除" ) except Exception as e: print_error(f"创建临时文件时发生错误: {e} " ) def create_named_temporary_file (): try : with tempfile.NamedTemporaryFile(mode='w+t' , encoding='utf-8' , prefix='my_app_temp_' , suffix='.data' , delete=True ) as nf: print_info(f"带名字的临时文件创建成功: {nf.name} " ) nf.write("这是一个带名字的临时文件的内容" ) nf.write("\n这是第二行内容" ) nf.seek(0 ) content = nf.read() print_info(f"临时文件内容: {content} " ) print_success("带名字的临时文件已在 with 块结束时自动关闭" ) except Exception as e: print_error(f"创建带名字的临时文件时发生错误: {e} " ) def create_temporary_directory (): print_subheader("4.创建临时目录 (TemporaryDirectory)" ) try : with tempfile.TemporaryDirectory() as temp_dir: print_info(f"临时目录创建成功: {temp_dir} " ) temp_file_in_dir = os.path.join(temp_dir, "temp_file.txt" ) with open (temp_file_in_dir, "w" ) as f_in_dir: f_in_dir.write("这是文件在临时目录中的内容" ) print_info(f"临时文件创建成功: {temp_file_in_dir} " ) print_success("临时目录已在 with 块结束时自动删除" ) except Exception as e: print_error(f"创建临时目录时发生错误: {e} " ) if __name__ == '__main__' : create_temporary_file() create_named_temporary_file() create_temporary_directory()
坑点与建议 (tempfile
模块) :
自动清理 : TemporaryFile
, NamedTemporaryFile
(当 delete=True
, 默认行为), 和 TemporaryDirectory
在与 with
语句一起使用时,会在退出 with
块时自动关闭并删除对应的文件或目录。这是它们主要的便利之处。NamedTemporaryFile
的 delete=True
在 Windows 上的行为 : 在 Windows 上,当 NamedTemporaryFile
以 delete=True
(默认) 打开时,文件在创建后不能被其他进程(甚至同一进程中的其他代码)通过其文件名再次打开,直到该 NamedTemporaryFile
对象被关闭。如果你需要在文件保持打开状态时让其他部分通过名字访问它,或者希望在关闭后文件仍然保留以便后续处理,应设置 delete=False
,但这种情况下你需要自己负责在不再需要时手动删除文件 (例如使用 os.remove()
)。文件名安全性 : tempfile
模块在生成临时文件名时会采取措施避免可预测的文件名,有助于防止一些安全问题。通常不建议使用像 mktemp()
这样只返回一个文件名但不实际创建文件的旧函数,因为它在文件名生成和文件创建之间存在竞争条件风险。优先使用 TemporaryFile
, NamedTemporaryFile
, TemporaryDirectory
这些会实际创建并管理临时实体的工具。权限 : 创建临时文件或目录仍然受限于当前用户的操作系统权限。如果程序没有在临时目录中写入的权限,操作将会失败。资源泄露 : 如果不使用 with
语句,而是手动创建 TemporaryFile
或 TemporaryDirectory
对象,你需要确保调用它们的 close()
方法(对于文件)或 cleanup()
方法(对于目录,尽管目录对象被垃圾回收时也会尝试清理)来释放资源和删除实体。with
语句是推荐的最佳实践。glob
模块:文件路径名模式匹配glob
模块用于查找所有匹配特定模式的文件路径名,它使用的模式规则类似 Unix shell 中的通配符。这对于需要批量处理符合某种命名规则的文件非常有用。
glob
常用功能函数 描述 glob.glob(pathname, *, recursive=False)
返回一个包含所有匹配 pathname
模式的文件和目录路径的列表。 glob.iglob(pathname, *, recursive=False)
返回一个迭代器,逐个产生匹配 pathname
模式的文件和目录路径 (更节省内存)。 glob.escape(pathname)
对 pathname
中的特殊字符 (如 *
, ?
, [
) 进行转义,使其能被字面匹配。
glob
模式中的特殊字符:
*
: 匹配任意多个(包括零个)任意字符。?
: 匹配单个任意字符。[...]
: 匹配方括号中出现的任意一个字符 (例如 [abc]
匹配 ‘a’, ‘b’, 或 ‘c’)。可以使用连字符表示范围 (例如 [0-9]
匹配任意数字)。[!...]
: 匹配任意不在方括号中出现的字符。**
(当 recursive=True
时): 匹配任意文件和任意层级的目录 (递归匹配)。批注:核心记忆功能 (glob
模块)
glob.glob(pattern)
: 最常用的功能,直接获取所有匹配路径的列表。理解 *
, ?
, [...]
通配符的含义。 recursive=True
配合 **
进行递归搜索。glob
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 from print_utils import * import globimport osimport shutil print_header("glob 模块功能演示" ) test_glob_dir = "glob_playground" def setup_glob_test_environment (): print_info(f"创建测试环境 '{test_glob_dir} '..." ) if os.path.exists(test_glob_dir): shutil.rmtree(test_glob_dir) os.makedirs(os.path.join(test_glob_dir, "subdir1" , "subsubdir" ), exist_ok=True ) os.makedirs(os.path.join(test_glob_dir, "subdir2" ), exist_ok=True ) files_to_create = [ os.path.join(test_glob_dir, "report.txt" ), os.path.join(test_glob_dir, "report_final.txt" ), os.path.join(test_glob_dir, "image01.jpg" ), os.path.join(test_glob_dir, "image02.png" ), os.path.join(test_glob_dir, "data_2025.csv" ), os.path.join(test_glob_dir, "config.ini" ), os.path.join(test_glob_dir, "subdir1" , "notes.txt" ), os.path.join(test_glob_dir, "subdir1" , "archive.zip" ), os.path.join(test_glob_dir, "subdir1" , "subsubdir" , "deep_log.txt" ), os.path.join(test_glob_dir, "subdir2" , "backup_2024.dat" ), ] for f_path in files_to_create: with open (f_path, "w" ) as f: f.write(f"Content of {os.path.basename(f_path)} " ) print_success("测试文件和目录已创建。" ) def cleanup_glob_test_environment (): if os.path.exists(test_glob_dir): shutil.rmtree(test_glob_dir) print_info(f"已清理测试环境 '{test_glob_dir} '。" ) setup_glob_test_environment() print_subheader("1. 基本通配符匹配 (glob.glob)" ) txt_files_pattern = os.path.join(test_glob_dir, "*.txt" ) print_info(f"查找模式: '{txt_files_pattern} '" ) found_txt_files = glob.glob(txt_files_pattern) print_info(f" 找到的 .txt 文件: {found_txt_files} " ) image_files_pattern = os.path.join(test_glob_dir, "image*" ) print_info(f"查找模式: '{image_files_pattern} '" ) found_image_files = glob.glob(image_files_pattern) print_info(f" 找到的以 'image' 开头的文件: {found_image_files} " ) specific_image_pattern = os.path.join(test_glob_dir, "image0?.jp*g" ) print_info(f"查找模式: '{specific_image_pattern} '" ) found_specific_images = glob.glob(specific_image_pattern) print_info(f" 找到的 'image0X.jp(e)g' 文件: {found_specific_images} " ) data_files_pattern = os.path.join(test_glob_dir, "data_[0-9][0-9][0-9][0-9].csv" ) print_info(f"查找模式: '{data_files_pattern} '" ) found_data_files = glob.glob(data_files_pattern) print_info(f" 找到的 'data_YYYY.csv' 文件: {found_data_files} " ) print_subheader("2. 递归匹配 (recursive=True 和 **)" ) recursive_txt_pattern = os.path.join(test_glob_dir, "**" , "*.txt" ) print_info(f"递归查找模式: '{recursive_txt_pattern} '" ) all_txt_recursively = glob.glob(recursive_txt_pattern, recursive=True ) print_info(f" 递归找到的所有 .txt 文件: {all_txt_recursively} " ) print_subheader("3. 使用迭代器进行匹配 (glob.iglob)" ) print_info(f"使用 iglob 迭代查找 '{recursive_txt_pattern} ' (适合大量匹配结果):" ) count = 0 for filepath in glob.iglob(recursive_txt_pattern, recursive=True ): print (f" - (iglob) 找到: {filepath} " ) count += 1 print_success(f" iglob 共找到 {count} 个匹配项。" ) print_subheader("4. 转义特殊字符 (glob.escape)" ) literal_filename_with_star = "file_with[star]*.txt" escaped_pattern_part = glob.escape(literal_filename_with_star) print_info(f" 原始文件名含特殊字符: '{literal_filename_with_star} '" ) print_info(f" 转义后的部分: '{escaped_pattern_part} '" ) cleanup_glob_test_environment() print_header("glob 模块演示结束。" )
坑点与建议 (glob
模块) :
**
与 recursive=True
: 要想让 **
通配符实现递归匹配(即匹配任意层级的子目录),必须同时在 glob.glob()
或 glob.iglob()
函数中将 recursive
参数设置为 True
。路径分隔符 : glob
模块会自动处理当前操作系统的路径分隔符(Windows 上的 \
和 Linux/macOS 上的 /
)。你在模式中可以使用 /
,它在 Windows 上也能正常工作。结果顺序 : glob
函数返回的路径列表的顺序是不确定的,它依赖于操作系统底层 listdir()
函数返回的顺序。如果需要排序的结果,你需要自己对返回的列表进行排序(例如 sorted(glob.glob(...))
)。性能 : 对于非常大的目录结构或非常多的文件,glob.glob()
会一次性将所有匹配项加载到内存中,可能消耗较多内存。在这种情况下,使用 glob.iglob()
返回一个迭代器会更节省内存,因为它逐个产生匹配项。隐藏文件 (以 .
开头的文件) : 在 Unix-like 系统上,像 *
这样的通配符默认不会匹配以点 .
开头的隐藏文件或目录名。如果需要匹配它们,模式需要显式地包含点,例如 .*
或 .[^.]*
。pathlib
模块的替代方案 : Python 3.4+ 引入的 pathlib
模块提供了面向对象的路径操作。Path
对象有自己的 glob()
和 rglob()
(recursive glob) 方法,使用起来可能更直观和 Pythonic。例如:1 2 3 4 from pathlib import Pathp = Path("./my_dir" ) txt_files = list (p.glob("*.txt" )) all_py_files = list (p.rglob("*.py" ))
无匹配项 : 如果没有文件或目录匹配给定的模式,glob.glob()
会返回一个空列表,glob.iglob()
会返回一个空的迭代器。程序不会因此报错。uuid
模块:生成通用唯一标识符UUID (Universally Unique Identifier) 是一种128位长的数字,设计用来在空间和时间上保证唯一性,而不需要一个中央协调机构。uuid
模块提供了生成各种版本 UUID 的功能。这些 ID 在分布式系统、数据库主键、临时文件名、会话 ID 等场景中非常有用。
uuid
常用功能与版本函数/属性 描述 uuid.uuid1([node, clock_seq])
基于当前时间和计算机的 MAC 地址生成 UUID (版本1)。可能涉及隐私。 uuid.uuid3(namespace, name)
基于一个命名空间 UUID 和一个名称的 MD5 哈希值生成 UUID (版本3),结果是确定的。 uuid.uuid4()
推荐常用 :生成一个完全随机的 UUID (版本4)。uuid.uuid5(namespace, name)
基于一个命名空间 UUID 和一个名称的 SHA-1 哈希值生成 UUID (版本5),结果是确定的。 UUID(hex=..., bytes=..., int=...)
(类构造器) 从不同表示形式(十六进制字符串、字节串、整数)创建一个 UUID 对象。 my_uuid.hex
(UUID对象属性) 获取 UUID 的32个字符的十六进制字符串表示 (无连字符)。 str(my_uuid)
(UUID对象转字符串) 获取 UUID 的标准36个字符的十六进制字符串表示 (带连字符)。 my_uuid.bytes
(UUID对象属性) 获取 UUID 的16字节表示。 my_uuid.int
(UUID对象属性) 获取 UUID 的128位整数表示。 my_uuid.version
(UUID对象属性) 获取 UUID 的版本号 (1, 3, 4, 或 5)。
预定义的命名空间 (用于 uuid3
和 uuid5
):
uuid.NAMESPACE_DNS
: 当 name
是一个完全限定域名时使用。uuid.NAMESPACE_URL
: 当 name
是一个 URL 时使用。uuid.NAMESPACE_OID
: 当 name
是一个 ISO OID 时使用。uuid.NAMESPACE_X500
: 当 name
是一个 X.500 DN 时使用。批注:核心记忆功能 (uuid
模块)
uuid.uuid4()
: 最常用 ,用于生成随机的、无法预测的唯一 ID。str(generated_uuid)
: 将 UUID 对象转换为标准的带连字符的字符串形式,便于存储和显示。generated_uuid.hex
: 获取不带连字符的紧凑十六进制字符串。uuid
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 from print_utils import * import uuidprint_header("uuid 模块功能演示" ) print_subheader("1. 生成 UUID 版本 1 (基于时间和 MAC 地址)" ) try : uuid_v1 = uuid.uuid1() print_info(f"UUID v1: {uuid_v1} " ) print_info(f" - 字符串表示: {str (uuid_v1)} " ) print_info(f" - 十六进制 (hex): {uuid_v1.hex } " ) print_info(f" - 版本号: {uuid_v1.version} " ) except Exception as e: print_warning(f"生成 UUID v1 时可能出现问题 (例如,无法获取 MAC 地址): {e} " ) print_subheader("2. 生成 UUID 版本 4 (随机生成)" ) uuid_v4_1 = uuid.uuid4() uuid_v4_2 = uuid.uuid4() print_info(f"UUID v4 (示例1): {uuid_v4_1} " ) print_info(f" - 字符串表示: {str (uuid_v4_1)} " ) print_info(f" - 十六进制 (hex): {uuid_v4_1.hex } " ) print_info(f" - 版本号: {uuid_v4_1.version} " ) print_info(f"UUID v4 (示例2): {uuid_v4_2} (每次调用都不同)" ) print_subheader("3. 生成 UUID 版本 3 (MD5 哈希)" ) namespace_for_v3 = uuid.NAMESPACE_DNS name_for_v3 = "example.com" uuid_v3 = uuid.uuid3(namespace_for_v3, name_for_v3) print_info(f"UUID v3 (NAMESPACE_DNS, '{name_for_v3} '): {uuid_v3} " ) print_info(f" - 版本号: {uuid_v3.version} " ) uuid_v3_again = uuid.uuid3(namespace_for_v3, name_for_v3) print_info(f" 再次生成 (应相同): {uuid_v3_again == uuid_v3} " ) print_subheader("4. 生成 UUID 版本 5 (SHA-1 哈希)" ) namespace_for_v5 = uuid.NAMESPACE_URL name_for_v5 = "https://www.python.org/" uuid_v5 = uuid.uuid5(namespace_for_v5, name_for_v5) print_info(f"UUID v5 (NAMESPACE_URL, '{name_for_v5} '): {uuid_v5} " ) print_info(f" - 版本号: {uuid_v5.version} " ) print_subheader("5. 从字符串或其他形式创建 UUID 对象" ) uuid_string = "a1b2c3d4-e5f6-7890-1234-567890abcdef" try : uuid_from_string = uuid.UUID(uuid_string) print_info(f"从字符串 '{uuid_string} ' 创建的 UUID 对象: {uuid_from_string} " ) print_info(f" 其整数表示 (uuid_from_string.int): {uuid_from_string.int } " ) print_info(f" 其字节表示 (uuid_from_string.bytes): {uuid_from_string.bytes } " ) except ValueError as e: print_error(f"从字符串创建 UUID 失败: {e} " ) print_header("uuid 模块演示结束。" )
坑点与建议 (uuid
模块) :
版本选择 :uuid.uuid4()
(版本4) : 通常是最佳选择 。它生成基于高质量随机数的 UUID,碰撞概率极低,且不包含任何可识别的硬件或时间信息,因此对隐私更友好。uuid.uuid1()
(版本1): 基于当前时间和计算机的 MAC 地址生成。虽然能保证在同一台机器上按时间顺序生成(大致上),但它可能暴露生成 UUID 的机器的 MAC 地址和生成时间,存在一定的隐私和安全风险。在需要基于时间排序且能容忍这些风险的特定场景下使用。uuid.uuid3()
和 uuid.uuid5()
: 这两个版本是确定性的,即对于相同的命名空间 UUID 和相同的名称字符串,它们总是生成相同的 UUID。uuid3
使用 MD5 哈希,uuid5
使用 SHA-1 哈希。它们适用于需要基于某个已知名称(如 URL、DNS名、OID等)生成一个唯一的、可重现的标识符的场景。由于 MD5 和 SHA-1 在密码学上已不被认为是强哈希算法,这些 UUID 主要用于标识,而非安全目的。碰撞概率 : UUID 的设计目标是全球唯一。对于版本4的随机 UUID,其可能性的数量非常巨大( $2^{122}$ 种,因为有几位用于版本和变体信息),因此两个独立生成的 uuid4
相同的概率微乎其微,在实际应用中基本可以忽略。存储与表示 :str(my_uuid_obj)
: 返回标准的36字符十六进制表示,包含连字符 (例如 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
)。这是最常见的交换和显示格式。my_uuid_obj.hex
: 返回32字符的紧凑十六进制表示,不含连字符。my_uuid_obj.bytes
: 返回16字节的字节串表示。my_uuid_obj.int
: 返回128位的整数表示。根据存储系统(数据库、文件名等)的要求和效率选择合适的表示形式。例如,某些数据库可能对存储字节串或整数形式的 UUID 更高效。命名空间的使用 (uuid3
, uuid5
) : 当使用 uuid3
或 uuid5
时,选择合适的预定义命名空间(NAMESPACE_DNS
, NAMESPACE_URL
, NAMESPACE_OID
, NAMESPACE_X500
)或者自己生成一个固定的 UUID 作为自定义命名空间,对于确保生成的 ID 的上下文和唯一性非常重要。7.底层工具库 本小节将探讨 Python 标准库中一系列强大的工具,它们可以帮助我们更有效地处理迭代、函数式编程、数据复制、美化输出、定义枚举、实现抽象基类以及运用类型提示来构建更健壮的应用程序。
typing
模块:类型提示支持typing
模块是 Python 中用于支持类型提示 (Type Hints) 的核心工具。类型提示允许开发者为变量、函数参数和函数返回值指定预期的类型。虽然 Python 本质上是动态类型语言(类型在运行时检查),但类型提示有诸多好处:
提高代码可读性和可理解性 :明确了函数期望什么类型的输入,以及会返回什么类型的结果。辅助静态分析工具 :像 MyPy, Pyright, Pytype 这样的静态类型检查器可以使用类型提示来检测代码中潜在的类型错误,而无需实际运行代码。改善IDE支持 :IDE 可以利用类型提示提供更准确的代码补全、重构和错误高亮。促进更好的API设计 :思考类型有助于设计出更清晰、更健壮的函数和类接口。类型提示在运行时通常不会强制执行(除非使用特定库如 pydantic
),它们更多的是给开发者和工具看的元数据。
typing
常用类型与构造器类型/构造器 描述 List[T]
(或 Python 3.9+ list[T]
)表示元素类型为 T
的列表。例如 List[int]
表示整数列表。 Dict[K, V]
(或 Python 3.9+ dict[K, V]
)表示键类型为 K
、值类型为 V
的字典。例如 Dict[str, int]
。 Tuple[T1, T2, ...]
(或 Python 3.9+ tuple[T1, ...]
)表示一个元组,其中每个元素都有特定的类型。例如 Tuple[str, int, bool]
。 Tuple[T, ...]
(或 Python 3.9+ tuple[T, ...]
)表示一个所有元素类型均为 T
的可变长度元组。 Set[T]
(或 Python 3.9+ set[T]
)表示元素类型为 T
的集合。例如 Set[str]
。 Optional[T]
表示一个值可以是类型 T
或者 None
。等价于 Union[T, None]
。 Union[T1, T2, ...]
(或 Python 3.10+ `T1T2 Any
表示一个未受约束的类型,可以是任何类型 (应谨慎使用,因为它会削弱类型检查)。 Callable[[Arg1Type, Arg2Type], ReturnType]
表示一个可调用对象 (如函数),指定其参数类型和返回类型。 TypeVar('T')
用于定义泛型类型变量,常用于编写泛型函数和类。 Generic[T]
用于创建泛型类的基类。 NewType('TypeName', BaseType)
创建一个概念上不同但运行时与 BaseType
相同的“新类型”,主要用于静态类型检查。 Type[C]
表示类 C
本身 (或其子类),而不是 C
的实例。 Iterable[T]
, Iterator[T]
分别表示可迭代对象和迭代器,其元素类型为 T
。 Mapping[K, V]
, Sequence[T]
表示更通用的映射类型 (如字典) 和序列类型 (如列表、元组)。
批注:核心记忆功能 (typing
模块)
基本集合类型:List
, Dict
, Tuple
, Set
的泛型版本。 Optional[T]
: 处理可能为 None
的值。Union[T1, T2]
: 当一个值可能有几种不同类型时使用 (或 Python 3.10+ 的 T1 | T2
语法)。Callable
: 为回调函数或作为参数传递的函数指定签名。Any
: 在确实无法或不需要指定具体类型时使用,但应尽量避免滥用。Python 3.9+ 的改进 : 许多标准集合类型 (如 list
, dict
, tuple
, set
)可以直接用作泛型类型,无需从 typing
模块导入对应的大写版本。例如,用 list[int]
代替 List[int]
。Python 3.10+ 的改进 : 引入了 |
操作符作为 Union
的更简洁语法,例如 int | str
代替 Union[int, str]
。typing
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 from print_utils import *from typing import ( List , Dict , Tuple , Set , Optional , Union , Any , Callable , TypeVar, Generic , NewType, Type , Sequence ) import os print_header("typing 模块 (类型提示) 功能演示" ) print_subheader("1. 基本类型提示" ) def greet (name: str , age: int , is_vip: bool = False ) -> str : """根据用户信息生成问候语。""" title: str = "VIP " if is_vip else "" return f"Hello, {title} {name} ! You are {age} years old." message: str = greet("Alice" , 30 , is_vip=True ) print_success(f"greet() 返回: {message} " ) def process_data (data: List [int ], factor: float = 1.0 ) -> List [float ]: """处理一个整数列表,返回一个浮点数列表。""" return [item * factor for item in data] int_list: List [int ] = [1 , 2 , 3 , 4 , 5 ] float_list: List [float ] = process_data(int_list, 0.5 ) print_success(f"process_data({int_list} , 0.5) 返回: {float_list} " ) print_subheader("2. Optional, Union, Any" ) def find_user (user_id: int ) -> Optional [Dict [str , Any ]]: """根据用户ID查找用户,如果找不到则返回 None。""" users_db: Dict [int , Dict [str , Any ]] = { 101 : {"name" : "Alice" , "email" : "alice@example.com" }, 102 : {"name" : "Bob" , "active" : False } } return users_db.get(user_id) user_profile: Optional [Dict [str , Any ]] = find_user(101 ) if user_profile: print_success(f"找到用户 101: {user_profile} " ) else : print_warning("用户 101 未找到。" ) user_profile_none: Optional [Dict [str , Any ]] = find_user(999 ) print_info(f"查找用户 999: {user_profile_none} " ) def parse_value (value: Union [str , int , float ] ) -> str : """根据不同类型的值返回描述。""" if isinstance (value, str ): return f"String value: '{value} '" elif isinstance (value, int ): return f"Integer value: {value} " elif isinstance (value, float ): return f"Float value: {value:.2 f} " return "Unknown type" print_info(f"parse_value('hello'): {parse_value('hello' )} " ) print_info(f"parse_value(123): {parse_value(123 )} " ) print_info(f"parse_value(3.14159): {parse_value(3.14159 )} " ) def process_anything (data: Any , operation: str = "print" ) -> None : """处理任何类型的数据 (应谨慎使用 Any)。""" print_info(f"Processing '{operation} ' on data of type {type (data).__name__} : {data} " ) process_anything([1 , 2 , 3 ], "summing (simulated)" ) process_anything({"key" : "value" }, "logging (simulated)" ) print_subheader("3. Callable (可调用对象)" ) def apply_operation (x: int , y: int , operation: Callable [[int , int ], int ] ) -> int : """应用一个二元整数操作并返回结果。""" return operation(x, y) def add (a: int , b: int ) -> int : return a + bdef multiply (a: int , b: int ) -> int : return a * bresult_add: int = apply_operation(10 , 5 , add) result_multiply: int = apply_operation(10 , 5 , multiply) print_success(f"apply_operation(10, 5, add) = {result_add} " ) print_success(f"apply_operation(10, 5, multiply) = {result_multiply} " ) print_subheader("4. TypeVar 和 Generic (泛型)" ) T = TypeVar('T' ) K = TypeVar('K' ) V = TypeVar('V' ) def get_first_element (items: Sequence [T] ) -> Optional [T]: """获取序列的第一个元素,如果序列为空则返回 None。""" return items[0 ] if items else None print_info(f"get_first_element([10, 20, 30]): {get_first_element([10 , 20 , 30 ])} " ) print_info(f"get_first_element(['a', 'b']): {get_first_element(['a' , 'b' ])} " ) print_info(f"get_first_element([]): {get_first_element([])} " ) class Container (Generic [T]): def __init__ (self, item: T ): self ._item: T = item def get_item (self ) -> T: return self ._item def __repr__ (self ) -> str : return f"Container[{type (self._item).__name__} ](item={self._item!r} )" int_container = Container[int ](123 ) str_container = Container("hello" ) print_success(f"int_container: {int_container} , item: {int_container.get_item()} " ) print_success(f"str_container: {str_container} , item: {str_container.get_item()} " ) print_subheader("5. NewType 和 Type (简介)" ) UserId = NewType('UserId' , int ) def get_user_by_id (user_id: UserId ) -> str : return f"User data for ID {user_id} " my_user_id: UserId = UserId(101 ) print_info(f"get_user_by_id(my_user_id): {get_user_by_id(my_user_id)} " ) print_info(f" 类型 of my_user_id: {type (my_user_id).__name__} (运行时仍是 int)" ) def create_instance (cls: Type [Container[str ]], value: str ) -> Container[str ]: """创建一个指定类 (必须是 Container[str] 或其子类) 的实例。""" return cls(value) if __name__ == '__main__' : pass print_header("typing 模块演示结束。" )
坑点与建议 (typing
模块) :
运行时无强制性 (通常) :类型提示主要是给静态类型检查器(如 MyPy)和 IDE 看的,Python 解释器本身在运行时通常不会因为类型不匹配而报错(除非你使用了像 pydantic
这样会进行运行时验证的库)。它们的目的是在开发阶段帮助发现潜在错误。逐渐采用 (Gradual Typing) :你不需要一次性为整个项目添加类型提示。可以从新的代码开始,或者从项目的关键部分开始,逐步引入类型提示。Any
的使用 : Any
类型表示“可以是任何类型”,它会有效地关闭对该变量或表达式的类型检查。应尽量避免使用 Any
,只在确实无法确定类型或为了兼容旧代码时才用。过度使用 Any
会降低类型提示带来的好处。Python 版本与类型提示语法 :Python 3.9+ : 许多标准内置集合类型 (如 list
, dict
, tuple
, set
) 可以直接用作泛型类型注解,例如 list[int]
而不是 from typing import List; List[int]
。这使得代码更简洁。Python 3.10+ : 引入了使用 |
操作符来表示联合类型 (Union
) 的语法,例如 int | str
代替 Union[int, str]
。还引入了 ParamSpec
和 Concatenate
用于更精确地注解高阶函数和装饰器。from __future__ import annotations
: 在 Python 3.7+ 的文件中,可以在文件顶部添加 from __future__ import annotations
。这使得所有类型提示都被视为字符串字面量,从而可以使用在当前 Python 版本中可能尚不支持的未来类型提示语法(例如,在 Python 3.8 中使用 list[int]
而不是 List[int]
,如果配合这个导入)。泛型 (TypeVar
, Generic
) : 当你编写的函数或类可以处理多种不同类型的数据,并且希望保持这些类型之间的一致性时(例如,一个函数接受一个列表并返回该列表中的一个元素,元素的类型应该相同),泛型是非常有用的。循环导入与类型提示 : 有时在进行类型提示时,如果两个模块相互导入对方定义的类型,可能会导致循环导入问题。一种常见的解决方法是将其中一个导入放在 if TYPE_CHECKING:
块中(需要 from typing import TYPE_CHECKING
),或者使用字符串字面量作为类型提示 (例如 my_var: 'MyOtherClass'
)。类型别名 (Type Aliases) :对于复杂的类型签名,可以使用类型别名来提高可读性。例如:Vector = List[float]
UserRecord = Dict[str, Union[int, str, bool]]
def process_vector(v: Vector) -> None: ...
itertools
提供了高效的迭代工具,functools
增强了函数式编程能力,copy
解决了对象复制的深浅问题,pprint
让复杂数据显示更友好,enum
带来了更安全和可读的常量定义方式,abc
模块是实现面向对象中接口和抽象的基石,而 typing
模块则为 Python 代码的健壮性和可维护性提供了强大的支持。掌握这些模块能让你的 Python 编程技能更上一层楼。
itertools
模块提供了一系列用于创建和使用迭代器的函数。这些函数实现了很多常见的迭代算法,运行速度快,内存效率高,并且能够以简洁的方式组合使用。当你需要处理复杂的迭代逻辑时,这个模块往往能提供优雅的解决方案。
函数 描述 itertools.count(start=0, step=1)
创建一个从 start
开始,按 step
无限递增 (或递减) 的数字迭代器。 itertools.cycle(iterable)
创建一个无限循环遍历 iterable
中元素的迭代器。 itertools.repeat(object[, times])
创建一个重复生成 object
的迭代器,可以指定重复 times
次,或无限重复。 itertools.chain(*iterables)
将多个可迭代对象连接起来,形成一个更长的迭代器。 itertools.islice(iterable, stop)
或 itertools.islice(iterable, start, stop[, step])
从可迭代对象中返回指定切片的元素,类似列表切片但作用于迭代器。 itertools.product(*iterables, repeat=1)
计算多个可迭代对象的笛卡尔积,相当于嵌套的 for 循环。 itertools.permutations(iterable, r=None)
从 iterable
中生成长度为 r
的所有可能排列 (元素不重复,顺序有关)。 itertools.combinations(iterable, r)
从 iterable
中生成长度为 r
的所有可能组合 (元素不重复,顺序无关)。 itertools.combinations_with_replacement(iterable, r)
允许元素重复的组合。 itertools.groupby(iterable, key=None)
根据 key
函数对连续的元素进行分组,返回 (键, 组内元素迭代器) 对。
批注:核心记忆功能 (itertools
模块)
count()
, cycle()
, repeat()
: 创建特定模式的无限或有限迭代器。chain()
: 轻松合并多个序列。product()
, permutations()
, combinations()
: 处理排列组合问题。groupby()
: 对已排序或按键分组的数据进行聚合处理。islice()
: 安全地从无限迭代器或普通迭代器中取一部分。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 from print_utils import *import itertoolsfrom typing import Iterable, Any , Tuple , Iterator, List print_header("itertools 模块功能演示" ) print_subheader("1. 无限迭代器与 islice" ) def demo_infinite_iterators () -> None : """演示 count, cycle, repeat 及如何使用 islice 获取部分元素。""" print_info("itertools.count(start, step):" ) limited_count: List [int ] = list (itertools.islice(itertools.count(10 , 2 ), 5 )) print_success(f" 从 count(10, 2) 中获取前5个元素: {limited_count} " ) print_info("\nitertools.cycle(iterable):" ) limited_cycle: List [str ] = list (itertools.islice(itertools.cycle(['A' , 'B' , 'C' ]), 8 )) print_success( f" 从 cycle(['A', 'B', 'C']) 中获取前8个元素: {limited_cycle} " ) print_info("\nitertools.repeat(object, times):" ) limited_repeat: List [str ] = list (itertools.islice(itertools.repeat("Hi" , 3 ), 3 )) print_success(f" 从 repeat('Hi', 3) 中获取前3个元素: {limited_repeat} " ) print_subheader("2. 连接与组合迭代器" ) def demo_combining_iterators () -> None : """演示 chain, product, permutations, combinations 的用法。""" list1: List [str ] = ['a' , 'b' ] tuple1: Tuple [int , ...] = (1 , 2 ) string1: str = "XY" print_info("itertools.chain(*iterables):" ) chained_elements: List [any ] = list (itertools.chain(list1, tuple1, string1)) print_success(f" 连接后的元素: {chained_elements} " ) print_info("\nitertools.product(*iterables, repeat=1):" ) cartesian_product: List [Tuple [str , str ]] = list (itertools.product('AB' , '12' )) print_success( f" product('AB', '12') 结果: {cartesian_product} " ) print_info("\nitertools.permutations(iterable, r):" ) perms: List [Tuple [str , ...]] = list (itertools.permutations("ABC" , 2 )) print_success( f" permutations('ABC', 2) 结果: {perms} " ) print_info("\nitertools.combinations(iterable, r):" ) combos: List [Tuple [str , ...]] = list (itertools.combinations("ABC" , 2 )) print_success(f" combinations('ABC', 2) 结果: {combos} " ) print_subheader("3. 分组迭代器 (groupby)" ) def demo_groupby_iterator () -> None : """演示 groupby。重要:groupby 需要数据预先按分组键排序。""" data_to_group: List [Tuple [str , str ]] = [ ('A' , 'Apple' ), ('A' , 'Ant' ), ('B' , 'Ball' ), ('B' , 'Banana' ), ('B' , 'Bat' ), ('C' , 'Cat' ), ('C' , 'Cow' ), ('C' , 'Car' ), ("C" , "Com" ) ] print_info(f"待分组数据 (已按首字母排序): {data_to_group} " ) for key_char, group_iter in itertools.groupby(data_to_group, key=lambda x: x[0 ]): group_items: List [Tuple [str , str ]] = list (group_iter) print_success(f" 以 {key_char} 开头的分组: {group_items} " ) print_info("\n对未排序数据使用 groupby (可能不符合预期):" ) unsorted_data: List [int ] = [1 , 1 , 2 , 3 , 3 , 3 , 1 , 2 , 2 ] print_info(f" 未排序数据: {unsorted_data} " ) for key_num, group_iter_num in itertools.groupby(unsorted_data): print (f" 键 '{key_num} ': {list (group_iter_num)} " ) print_warning(" 注意:groupby 对未排序数据是按连续相同元素进行分组的。" ) if __name__ == '__main__' : demo_groupby_iterator()
坑点与建议 (itertools
模块) :
无限迭代器 : 像 count()
, cycle()
, repeat()
(不带 times
参数) 这样的函数会产生无限序列。在直接使用它们进行迭代时,务必配合 itertools.islice()
或其他机制来限制迭代次数,否则会导致无限循环。迭代器消耗 : itertools
中的函数通常返回迭代器。迭代器是一次性的,遍历过后就会耗尽。如果需要多次使用结果,应先将其转换为列表或元组 (例如 list(my_iterator)
)。groupby()
的前提 : itertools.groupby()
函数在对数据进行分组前,要求数据必须已经按照分组的键排好序 。如果数据未排序,它只会将连续出现的相同键的元素分为一组,可能与预期不符。内存效率 : itertools
的函数通常设计为内存高效的,因为它们是基于迭代器操作的,一次只处理一个元素,而不是一次性加载所有数据到内存。这使得它们非常适合处理大型数据集。组合性 : itertools
函数的强大之处在于它们的组合性。你可以将多个 itertools
函数串联起来,用非常少的代码构建出复杂的迭代逻辑。functools
模块包含了一些用于高阶函数(操作或返回其他函数的函数)的工具。这些工具可以帮助你修改或扩展函数及其他可调用对象的行为,而无需完全重写它们。
函数/装饰器 描述 functools.partial(func, *args, **keywords)
创建一个新的函数,该函数在调用时会自动传入预设的 args
和 keywords
参数。 functools.reduce(function, iterable[, initializer])
将一个二元函数 function
累积地应用到 iterable
的项上,从而将序列规约为单个值。 functools.wraps(wrapped_func)
一个装饰器,用于更新一个包装函数 (wrapper) 的元信息 (如名称、文档字符串) 以匹配被包装函数 wrapped_func
。 @functools.lru_cache(maxsize=128, typed=False)
一个装饰器,为函数调用结果提供最近最少使用 (LRU) 缓存,可以显著提高重复调用相同参数的耗时函数的性能。 @functools.total_ordering
一个类装饰器,如果你定义了 __eq__()
和至少一个其他比较方法 (__lt__
, __le__
, __gt__
, __ge__
),它会自动为你补全所有其他的比较方法。 @functools.singledispatch
一个装饰器,将函数转换为单分派泛函数 (generic function),可以根据第一个参数的类型注册不同的实现。
批注:核心记忆功能 (functools
模块)
partial()
: 当你需要固定函数的一些参数,生成一个更特化的函数版本时非常有用。reduce()
: 用于执行累积计算,如求和、求积、或更复杂的聚合。wraps()
: 编写自定义装饰器时务必使用 ,以保持被装饰函数的元数据。lru_cache()
: 对于计算成本高且参数可能重复调用的函数,这是一个简单有效的性能优化手段。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 from print_utils import *import functoolsimport timefrom typing import Callable , Any , List def show_functools_intro (): print_header("functools 模块功能演示" ) def demo_partial (): print_subheader("1. functools.partial: 固定函数参数" ) def power (base: float , exponent: float ) -> float : """计算 base 的 exponent 次方。""" return base ** exponent square: Callable [[float ], float ] = functools.partial(power, exponent=2 ) cube: Callable [[float ], float ] = functools.partial(power, exponent=3 ) power_of_2: Callable [[float ], float ] = functools.partial(power, 2 ) print_info(f"power(3, 2) = {power(3 , 2 )} " ) print_success(f"square(3) (partial(power, exponent=2)): {square(3 )} " ) print_success(f"cube(3) (partial(power, exponent=3)): {cube(3 )} " ) print_success(f"power_of_2(5) (partial(power, 2)): {power_of_2(5 )} " ) def demo_reduce (): print_subheader("2. functools.reduce: 累积计算" ) numbers_for_factorial: List [int ] = [1 , 2 , 3 , 4 , 5 ] factorial_5: int = functools.reduce(lambda x, y: x * y, numbers_for_factorial) print_success(f"reduce(lambda x, y: x * y, {numbers_for_factorial} ) = {factorial_5} " ) def demo_lru_cache (): print_subheader("3. @functools.lru_cache: 函数结果缓存" ) @functools.lru_cache(maxsize=128 ) def expensive_fibonacci (n: int ) -> int : """一个计算斐波那契数的耗时函数 (递归实现,不加缓存效率极低)。""" print_info(f" 计算({n} )..." ) if n < 2 : return n time.sleep(0.5 ) return expensive_fibonacci(n - 1 ) + expensive_fibonacci(n - 2 ) print_info("第一次调用 expensive_fibonacci(10):" ) start_time = time.perf_counter() fib_10_first_call = expensive_fibonacci(10 ) duration1 = time.perf_counter() - start_time print_success(f" fibonacci(10) = {fib_10_first_call} , 耗时: {duration1:.6 f} 秒" ) print_info(f" 缓存信息: {expensive_fibonacci.cache_info()} " ) print_info("\n第二次调用 expensive_fibonacci(10) (应从缓存读取):" ) start_time_cached = time.perf_counter() fib_10_second_call = expensive_fibonacci(10 ) duration2 = time.perf_counter() - start_time_cached print_success(f" fibonacci(10) = {fib_10_second_call} , 耗时: {duration2:.6 f} 秒 (缓存)" ) expensive_fibonacci.cache_clear() print_info(f"\n缓存已清除。缓存信息: {expensive_fibonacci.cache_info()} " ) def demo_wraps (): print_subheader("4. @functools.wraps: 编写行为正确的装饰器" ) def simple_logging_decorator (original_function: Callable ) -> Callable : """一个简单的日志装饰器,演示 @functools.wraps 的作用。""" @functools.wraps(original_function ) def wrapper (*args: Any , **kwargs: Any ) -> Any : """wrapper 函数的文档字符串。""" print_info(f" 装饰器: 调用函数 '{original_function.__name__} ' 之前..." ) result = original_function(*args, **kwargs) print_info(f" 装饰器: 调用函数 '{original_function.__name__} ' 之后,结果: {result} " ) return result return wrapper @simple_logging_decorator def greet_person (name: str ) -> str : """向一个人问好并返回问候语。""" return f"Hello, {name} !" print_info("调用被装饰的 greet_person('Alice'):" ) greeting_output = greet_person("Alice" ) print_info("\n检查被装饰函数的元信息:" ) print_success(f" 函数名 (greet_person.__name__): '{greet_person.__name__} ' (应为 'greet_person')" ) print_success(f" 文档字符串 (greet_person.__doc__):\n '''{greet_person.__doc__.strip()} ''' (应为 greet_person 的文档)" ) def main (): """主函数,用于调用各个演示函数""" show_functools_intro() print ("\n请选择要运行的演示:" ) print ("1. functools.partial 演示" ) print ("2. functools.reduce 演示" ) print ("3. functools.lru_cache 演示" ) print ("4. functools.wraps 演示" ) print ("5. 运行所有演示" ) print ("0. 退出" ) choice = input ("\n请输入选项编号: " ) if choice == '1' : demo_partial() elif choice == '2' : demo_reduce() elif choice == '3' : demo_lru_cache() elif choice == '4' : demo_wraps() elif choice == '5' : demo_partial() demo_reduce() demo_lru_cache() demo_wraps() elif choice == '0' : print ("退出程序" ) else : print ("无效选项,请重新运行程序" ) if __name__ == "__main__" : main()
坑点与建议 (functools
模块) :
partial()
的参数顺序 : partial()
会按顺序填充位置参数,然后是关键字参数。如果 func
本身有一些默认参数,partial
可以覆盖它们,或者提供那些未被 partial
固定的参数。reduce()
的可读性 : 虽然 reduce()
很强大,但对于简单的求和、求积等操作,直接使用内置的 sum()
或一个显式的循环通常更具可读性。reduce()
更适用于一些自定义的复杂累积逻辑。确保 reduce
中使n用的二元函数易于理解。lru_cache()
的适用场景 :适用于那些输入参数组合相对有限,且单次计算成本较高的纯函数(即对于相同的输入总是返回相同输出,且没有副作用的函数)。 不适用于参数是可变对象(如列表、字典)且函数行为依赖于对象内部状态变化的情况,因为缓存是基于参数值的哈希。如果可变对象被修改但其哈希不变(或哈希变化但对象视为等价),可能导致缓存命中错误。typed=True
可以让缓存区分不同类型的参数(如 3
和 3.0
)。 maxsize
参数很重要,设置太小可能导致缓存命中率低,None
则不限制大小,可能消耗过多内存。wraps()
的必要性 : 在编写任何自定义装饰器时,强烈建议 使用 @functools.wraps(original_function)
来装饰内部的 wrapper
函数。这能确保被装饰后的函数保留其原始函数的名称 (__name__
)、文档字符串 (__doc__
)、参数列表、模块名等重要元信息,这对于调试、文档生成和代码可维护性至关重要。copy
模块:对象的浅拷贝与深拷贝在 Python 中,赋值操作 (=
) 对于可变对象(如列表、字典)通常只是创建了一个新的引用,指向同一个对象。这意味着修改一个引用会影响到所有指向该对象的引用。copy
模块提供了创建对象副本的方法,分为浅拷贝和深拷贝,用于在不同程度上复制对象及其内容。
copy
常用功能函数 描述 copy.copy(x)
创建对象 x
的浅拷贝 (shallow copy) 。 copy.deepcopy(x)
创建对象 x
的深拷贝 (deep copy) 。
浅拷贝 (Shallow Copy) vs. 深拷贝 (Deep Copy):
浅拷贝 (copy.copy()
) :
创建一个新的复合对象 (例如一个新的列表实例)。 然后,对于原始对象中的元素,如果是不可变类型(如数字、字符串、元组),则直接复制其值。 如果是可变类型(如列表、字典、自定义对象),则复制其引用 。也就是说,新列表中的可变元素和旧列表中的对应可变元素将指向内存中同一个 对象。
影响 : 修改新拷贝中嵌套的可变对象的内容,会影响到原始对象中对应的嵌套可变对象。
批注:核心记忆功能 (copy
模块)
理解浅拷贝 和深拷贝 的根本区别:浅拷贝只复制顶层容器,内部元素如果是可变对象则共享引用;深拷贝会递归复制所有嵌套的可变对象,创建完全独立的副本。 copy.copy()
: 用于浅拷贝。copy.deepcopy()
: 常用且重要 ,当你需要一个与原始对象完全独立的副本,尤其是当对象包含嵌套的可变结构时,应使用深拷贝。pprint
模块:数据结构的美化输出当需要打印复杂的 Python 数据结构(如深度嵌套的列表、字典)时,内置的 print()
函数的输出可能挤在一行,难以阅读和理解。pprint
模块 (pretty-print) 提供了“美化打印”的功能,可以将这些数据结构以更易读的格式输出,例如自动换行、缩进和排序(可选)。
pprint
常用功能函数/类 描述 pprint.pprint(object, stream=None, ...)
将对象 object
美化输出到指定的流 stream
(默认为 sys.stdout
)。 pprint.pformat(object, indent=1, width=80, ...)
返回对象 object
美化后的字符串表示,而不是直接打印。 pprint.PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, compact=False, sort_dicts=True)
创建一个 PrettyPrinter
对象,可以更细致地控制美化输出的参数,并可复用。
PrettyPrinter
或 pprint
/pformat
的常用参数:
indent
: 每级缩进的空格数。width
: 输出内容的目标宽度,超过此宽度会尝试换行。depth
: 控制嵌套数据结构的打印深度,超出此深度的部分会用 ...
表示。stream
: 输出流。compact
: 如果为 True
,则输出会更紧凑,尽可能将多个短元素放在同一行。sort_dicts
: (Python 3.8+) 如果为 True
(默认),则字典会按键排序后输出,保证输出顺序的一致性。批注:核心记忆功能 (pprint
模块)
pprint.pprint(my_complex_data)
: 最常用 ,直接美化打印数据结构。pprint.pformat(my_complex_data)
: 当你需要获取美化后的字符串而不是直接打印时使用。PrettyPrinter(sort_dicts=False)
: 如果不希望字典输出时按键排序,可以创建 PrettyPrinter
实例并设置。pprint
模块代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 from print_utils import * import pprintimport datetime print_header("pprint 模块功能演示" ) def create_complex_data_structure () -> dict : """创建一个复杂的嵌套数据结构用于演示。""" return { 'user_id' : 1001 , 'username' : 'alpha_user' , 'is_active' : True , 'profile' : { 'full_name' : 'Alpha Centauri User' , 'email' : 'alpha@example.com' , 'date_joined' : datetime.date(2023 , 1 , 15 ), 'preferences' : { 'theme' : 'dark_solarized' , 'notifications' : ['email' , 'push' ], 'language' : 'en-US' } }, 'posts' : [ {'post_id' : 'p001' , 'title' : 'First Post' , 'tags' : ['intro' , 'python' ]}, {'post_id' : 'p002' , 'title' : 'Advanced Python Topics' , 'tags' : ['python' , 'advanced' , 'performance' ]}, {'post_id' : 'p003' , 'title' : 'Data Structures in Python' , 'comments' : [ {'user' : 'beta_user' , 'text' : 'Great article!' }, {'user' : 'gamma_user' , 'text' : 'Very helpful, thanks.' } ] } ], 'friends_ids' : list (range (1020 , 1035 )) } complex_data: dict = create_complex_data_structure() print_subheader("1. 使用 pprint.pprint() 直接美化打印" ) print_info("使用普通 print() 打印复杂数据 (对比用):" ) print_info("\n使用 pprint.pprint() 打印复杂数据:" ) pprint.pprint(complex_data, indent=2 , width=100 , sort_dicts=False ) print_subheader("\n2. 使用 pprint.pformat() 获取美化后的字符串" ) formatted_string: str = pprint.pformat(complex_data, indent=2 , width=80 , depth=3 , compact=True , sort_dicts=True ) print_info("使用 pformat 获取的格式化字符串 (depth=3, compact=True, sort_dicts=True):" ) print (formatted_string)print_subheader("\n3. 使用 PrettyPrinter 类" ) custom_printer = pprint.PrettyPrinter( indent=4 , width=120 , depth=None , compact=False , sort_dicts=False ) print_info("使用自定义的 PrettyPrinter 实例打印:" ) custom_printer.pprint(complex_data['profile' ]) print_info("\n使用另一个配置 (字典排序) 的 PrettyPrinter 实例:" ) sorting_printer = pprint.PrettyPrinter(indent=2 , sort_dicts=True ) sorting_printer.pprint(complex_data['profile' ]['preferences' ]) if __name__ == '__main__' : pass print_header("pprint 模块演示结束。" )