(1)全网最详细的Python标准进阶笔记没有之一

Python 语言特性

Python 是一门 解释型弱类型动态 语言

编译型 vs 解释型

  • 编译型语言:先编译后运行,速度快,适合大型项目
  • 解释型语言:边解释边运行,灵活易改,适合快速开发

强类型 vs 弱类型

  • 强类型:类型检查严格,变量需声明类型
  • 弱类型:类型检查宽松,变量隐式转换

动态型 vs 静态型

  • 动态型语言:运行时确定数据类型,灵活但效率较低
  • 静态型语言:编译时确定数据类型,高效但灵活性较差

类型提示

def func(name: str, age: int) -> str:
    return f"{name}说,我今年{age}years old."

print(func('张三', 25))
print(func.__annotations__)  # 查看函数的类型注解

注意:Python 的类型提示不会强制执行类型检查,仅作为开发辅助和文档。


第一章:字符串打印格式化与PyCharm模板变量

本章将分为两个主要部分:首先介绍如何在 Python 控制台中使用 ANSI 转义序列来实现文本的彩色和格式化输出,并提供一个实用的封装示例;其次,我们将探讨如何利用 IDE(特别是 PyCharm,但概念也适用于其他IDE)中的 Live Template (实时模板/代码片段) 功能,通过预设的模板变量和缩写来大幅提升 Python 的编码效率。

字符串打印格式化 (ANSI 转义序列)

Python 允许在控制台中输出彩色文本和特殊格式,这在创建命令行界面 (CLI) 或需要突出显示特定输出时非常有用,可以显著增强用户体验和信息的可读性。这种效果通常是通过 ANSI 转义序列 (ANSI escape sequences) 来实现的。

基本语法

ANSI 转义序列的基本格式如下:

\033[参数m内容\033[0m

或者在 Python 字符串中,通常写作:

'\033[<参数>m<你的文本内容>\033[0m'

其中:

  • \033 (或 \x1b):这是 ESC 字符的八进制(或十六进制)表示,标志着转义序列的开始。
  • [:控制序列引导符 (Control Sequence Introducer, CSI)。
  • <参数>:一个或多个用分号 ; 分隔的数字。这些数字代码控制着文本的显示方式、前景色(文字颜色)和背景色。
  • m:表示设置图形再现参数 (Select Graphic Rendition, SGR) 的结束标志。
  • <你的文本内容>:你希望应用这些格式的实际文本。
  • \033[0m:这是一个特殊的重置序列,它会清除之前设置的所有格式属性,使后续的文本恢复到终端的默认显示状态。每次使用完特殊格式后,都强烈建议使用此序列来重置,以避免格式污染后续的输出。
ANSI 转义码表

下表列出了一些常用的 ANSI SGR 参数代码:

显示方式代码前景色代码背景色代码
默认0黑色30黑色40
高亮/粗体1红色31红色41
(通常不使用)2绿色32绿色42
下划线4黄色33黄色43
闪烁5蓝色34蓝色44
反白7紫红色35紫红色45
不可见8青蓝色36青蓝色46
白色37白色47

注意

  • 除了上述标准颜色 (30-37, 40-47),现代终端通常还支持高强度颜色 (例如,高亮红色使用 \033[1;31m 或者单独的亮色代码 \033[91m)、256色模式以及 RGB 真彩色模式 (例如 \033[38;2;r;g;bm 设置前景色为 RGB(r,g,b))。但这些高级模式的兼容性可能因终端模拟器而异。
  • “闪烁”(代码5) 和 “不可见”(代码8) 的支持程度也取决于终端。
实用示例
基本颜色设置
# 红色文字
print('\033[31m这是红色文字\033[0m')

# 绿色文字
print('\033[32m这是绿色文字\033[0m')

# 黄色文字 (通常与高亮/粗体结合使用效果更明显)
print('\033[1;33m这是高亮黄色文字\033[0m') # 1表示高亮/粗体,33表示黄色

# 蓝色文字
print('\033[34m这是蓝色文字\033[0m')
组合使用

可以同时设置显示方式、前景色和背景色,用分号分隔参数。

# 红色文字 + 黄色背景
print('\033[31;43m红字黄底\033[0m')

# 高亮 + 绿色文字
print('\033[1;32m高亮绿色文字\033[0m')

# 下划线 + 蓝色文字
print('\033[4;34m带下划线的蓝色文字\033[0m')

# 高亮 + 紫红色文字 + 白色背景
print('\033[1;35;47m高亮紫红色文字白色背景\033[0m')
实际应用场景 (封装为 print_utils.py 工具模块)

为了在项目中更方便、更一致地使用彩色打印,通常我们会将这些 ANSI 转义序列封装成常量或函数。以下是您之前提供的 print_utils.py 模块内容,它是一个很好的实践示例:

"""
打印工具模块,提供彩色和结构化的打印函数。
"""

# ======== 彩色打印工具 ========
class Colors:
    """存储 ANSI 颜色和样式代码的常量。"""
    HEADER = '\033[95m'    # 亮紫色 (常用于标题)
    BLUE = '\033[94m'      # 亮蓝色
    CYAN = '\033[96m'      # 亮青色
    GREEN = '\033[92m'     # 亮绿色
    WARNING = '\033[93m'   # 亮黄色
    FAIL = '\033[91m'      # 亮红色
    BOLD = '\033[1m'       # 粗体/高亮
    UNDERLINE = '\033[4m'  # 下划线
    END = '\033[0m'        # 重置所有格式


def print_header(text: str) -> None:
    """打印带特殊格式的标题。"""
    print(f"\n{Colors.HEADER}{Colors.BOLD}--- {text} ---{Colors.END}")

def print_subheader(text: str) -> None:
    """打印带下划线的青色子标题。"""
    print(f"\n{Colors.CYAN}{Colors.UNDERLINE}  {text}{Colors.END}")

def print_info(text: str) -> None:
    """打印普通信息 (默认颜色)。"""
    print(f" INFO: {text}")

def print_success(text: str) -> None:
    """打印成功信息 (绿色)。"""
    print(f"{Colors.GREEN}{text}{Colors.END}")

def print_warning(text: str) -> None:
    """打印警告信息 (黄色)。"""
    print(f"{Colors.WARNING}  ⚠️ [Warning] {text}{Colors.END}")

def print_error(text: str) -> None:
    """打印错误信息 (红色)。"""
    print(f"{Colors.FAIL}  ❌ [Error] {text}{Colors.END}")

def print_sql(sql: str) -> None:
    """打印SQL语句 (蓝色)。"""
    print(f"{Colors.BLUE}    SQL: {sql.strip()}{Colors.END}")

def print_result_item(item: any, indent: int = 4) -> None:
    """以结构化方式打印结果项,特别是字典。"""
    prefix = " " * indent
    if isinstance(item, dict):
        details = ", ".join([
            f"{Colors.BOLD}{key}{Colors.END}: {repr(value)}" for key, value in item.items()
        ])
        print(f"{prefix}Row({details})")
    else:
        print(f"{prefix}{repr(item)}")

# ======== END 彩色打印工具 ========

如何使用这个 print_utils 模块:

  1. 将上述代码保存为 print_utils.py 文件。
  2. 在您的其他 Python 脚本中,通过 from print_utils import *import print_utils 来使用这些函数。
# 示例:在另一个脚本中使用 print_utils.py
# from print_utils import print_header, print_success, print_error # 假设已导入

if __name__ == "__main__":
    # print_header("应用程序任务") # 调用封装好的函数
    # print_success("任务A已成功完成!")
    # print_error("任务B执行失败,错误代码:1024")
    pass # 仅为结构示例

PyCharm Live Templates 提升编码效率

Live Templates(实时模板或代码片段)是现代集成开发环境 (IDE) 如 PyCharm、VS Code 等提供的一项核心功能。它允许开发者定义常用的代码结构,并通过输入一个简短的缩写 (Abbreviation) 后按下特定按键(通常是 Tab)来快速插入这些代码块 (Template text)。这些模板通常还支持占位符变量,如 $VAR$$CURSOR$,在模板展开后,IDE 会引导用户快速填充这些变量或将光标定位到预设位置。

使用 Live Templates 可以:

  • 显著减少重复的样板代码输入
  • 提高编码速度和效率
  • 帮助保持代码风格和结构的一致性
  • 减少因手动输入而出错的可能性

我们需要根据如下步骤去键入模板

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1. 基本循环
a. for...in 循环 (遍历序列)
  • 模板用途: 快速生成一个遍历可迭代对象的 for 循环。
  • 建议缩写: fori (或您截图中的 iter)
  • 描述: for item in iterable: 循环
  • 模板文本:
    for $ITEM$ in $ITERABLE$:
        $CURSOR$
    
  • 主要占位符说明:
    • $ITEM$: 循环中每次迭代的元素变量名。
    • $ITERABLE$: 要遍历的序列或可迭代对象。
    • $CURSOR$: 模板展开后光标的初始位置。
b. for...in enumerate 循环 (带索引遍历)
  • 模板用途: 快速生成一个同时遍历索引和元素的 for 循环。
  • 建议缩写: forenum (或您截图中的 itere)
  • 描述: for index, item in enumerate(iterable): 循环
  • 模板文本:
    for $INDEX$, $ITEM$ in enumerate($ITERABLE$):
        $CURSOR$
    
  • 主要占位符说明:
    • $INDEX$: 循环中每次迭代的索引变量名。
    • $ITEM$: 循环中每次迭代的元素变量名。
    • $ITERABLE$: 要遍历的序列或可迭代对象。
c. for...in range 循环 (按次数)
  • 模板用途: 快速生成一个按指定次数执行的 for 循环。
  • 建议缩写: forr
  • 描述: for i in range(count): 循环
  • 模板文本:
    for $VAR$ in range($COUNT$):
        $CURSOR$
    
  • 主要占位符说明:
    • $VAR$: 循环计数变量名 (通常是 i)。
    • $COUNT$: 循环的次数。
2. 条件判断
a. if 语句
  • 模板用途: 快速生成一个基本的 if 条件判断语句。
  • 建议缩写: ifc
  • 描述: if condition: 语句
  • 模板文本:
    if $CONDITION$:
        $CURSOR$
    
  • 主要占位符说明:
    • $CONDITION$: if 语句的条件表达式。
    • $CURSOR$: 光标位于 if 代码块内部。
b. if-else 语句
  • 模板用途: 快速生成 if-else 条件判断结构。
  • 建议缩写: ifel
  • 描述: if condition: ... else: ... 语句
  • 模板文本:
    if $CONDITION$:
        $ACTION_IF_TRUE$
    else:
        $ACTION_IF_FALSE$
    $CURSOR$
    
  • 主要占位符说明: $CONDITION$, $ACTION_IF_TRUE$, $ACTION_IF_FALSE$, $CURSOR$
c. if-elif-else 语句
  • 模板用途: 快速生成 if-elif-else 多条件判断结构。
  • 建议缩写: ifelifel
  • 描述: if cond1: ... elif cond2: ... else: ... 语句
  • 模板文本:
    if $CONDITION1$:
        $ACTION1$
    elif $CONDITION2$:
        $ACTION2$
    else:
        $ACTION_ELSE$
    $CURSOR$
    
  • 主要占位符说明: $CONDITION1$, $ACTION1$, $CONDITION2$, $ACTION2$, $ACTION_ELSE$, $CURSOR$
3. 打印与日志
a. print(f"...") (f-string 打印)
  • 模板用途: 快速生成一个使用 f-string 格式化的 print 语句。
  • 建议缩写: prf
  • 描述: print(f"...") 语句
  • 模板文本:
    print(f"$CURSOR$")
    
  • 主要占位符说明: $CURSOR$: 光标直接定位在 f-string 的引号内。
b. logger.info (快速日志记录 - 假设 logger 对象已配置)
  • 模板用途: 快速插入一条 logger.info 日志记录。
  • 建议缩写: logi
  • 描述: logger.info(f"...")
  • 模板文本:
    logger.info(f"$MESSAGE$")
    $CURSOR$
    
  • 主要占位符说明: $MESSAGE$, $CURSOR$。 (类似地,可以为 debug, warning, error, exception 创建模板)
4. Python 结构与定义
  • 模板用途: 快速生成一个带类型注解和文档字符串的函数定义。
  • 建议缩写: defn
  • 描述: 带类型注解和文档字符串的函数定义
  • 模板文本:
    def $FUNCTION_NAME$($PARAMS$) -> $RETURN_TYPE$:
        """$DOCSTRING$"""
        $CURSOR$
        return $RETURN_VALUE$
    
  • 主要占位符说明: $FUNCTION_NAME$, $PARAMS$, $RETURN_TYPE$ (可默认为 None), $DOCSTRING$, $CURSOR$, $RETURN_VALUE$ (可默认为 None)。
c. 类定义 (基本结构)
  • 模板用途: 快速生成一个带 __init__ 方法的类定义。
  • 建议缩写: cls
  • 描述: 基本类定义 (含 __init__)
  • 模板文本:
    class $ClassName$:
        """$CLASS_DOCSTRING$"""
    
        def __init__(self, $ARGS$):
            """初始化 $ClassName$ 对象。
            
            Args:
                $ARGS_DOC$
            """
            $INIT_BODY$
            $CURSOR$
    
  • 主要占位符说明: $ClassName$, $CLASS_DOCSTRING$, $ARGS$, $ARGS_DOC$, $INIT_BODY$, $CURSOR$.
d. @dataclass 类定义
  • 模板用途: 快速生成一个使用 dataclasses 模块定义的类。
  • 建议缩写: dtcls
  • 描述: @dataclass 类定义
  • 模板文本:
    from dataclasses import dataclass
    
    @dataclass
    class $ClassName$:
        """表示 $ENTITY_DESCRIPTION$ 的数据类。"""
        $FIELD_NAME$: $FIELD_TYPE$
        $CURSOR$
    
  • 主要占位符说明: $ClassName$, $ENTITY_DESCRIPTION$, $FIELD_NAME$, $FIELD_TYPE$, $CURSOR$.
5. 异常处理
a. try-except 基本块
  • 模板用途: 快速生成一个基本的 try-except 异常处理块。
  • 建议缩写: tryex
  • 描述: try...except Exception as e:
  • 模板文本:
    try:
        $TRY_BODY$
    except $EXCEPTION_TYPE$ as e:
        # logger.exception(f"An error occurred: {e}") # 如果使用日志
        print(f"An error occurred ($EXCEPTION_TYPE$): {e}") # 简单打印
        $CURSOR$
    
  • 主要占位符说明: $TRY_BODY$, $EXCEPTION_TYPE$ (可默认为 Exception), $CURSOR$.
b. try-except-else-finally 完整块
  • 模板用途: 快速生成包含 elsefinally 子句的 try-except 块。
  • 建议缩写: tryexelfi
  • 描述: try...except...else...finally:
  • 模板文本:
    try:
        $TRY_BODY$
    except $EXCEPTION_TYPE$ as e:
        print(f"An error occurred ($EXCEPTION_TYPE$): {e}")
        $EXCEPT_BODY$
    else:
        print("Operation successful, no exceptions.")
        $ELSE_BODY$
    finally:
        print("Executing finally block.")
        $FINALLY_BODY$
    $CURSOR$
    
  • 主要占位符说明: $TRY_BODY$, $EXCEPTION_TYPE$, $EXCEPT_BODY$, $ELSE_BODY$, $FINALLY_BODY$, $CURSOR$.
6. 文件操作
a. with open(...) 上下文管理器
  • 模板用途: 快速生成使用 with 语句安全打开和操作文件的代码。
  • 建议缩写: fwith
  • 描述: with open(...) as f: (安全文件操作)
  • 模板文本:
    try:
        with open("$FILEPATH$", mode="$MODE$", encoding="utf-8") as $VARNAME$:
            $CURSOR$
            # content = $VARNAME$.read()
    except FileNotFoundError:
        print(f"Error: File '$FILEPATH$' not found.")
    except IOError as e:
        print(f"Error reading/writing file '$FILEPATH$': {e}")
    
  • 主要占位符说明: $FILEPATH$, $MODE$ (可默认为 'r'), $VARNAME$ (可默认为 f), $CURSOR$.
7. 推导式
a. 列表推导式 (List Comprehension)
  • 模板用途: 快速生成列表推导式。

  • 建议缩写: lc (或您截图中的 compl / compli for with if)

  • 描述: [expr for item in iterable if condition]

  • 模板文本 (带if):

    [$EXPRESSION$ for $ITEM$ in $ITERABLE$ if $CONDITION$]
    $CURSOR$
    
  • 模板文本 (不带if):

    [$EXPRESSION$ for $ITEM$ in $ITERABLE$]
    $CURSOR$
    
  • 主要占位符说明: $EXPRESSION$, $ITEM$, $ITERABLE$, $CONDITION$ (可选)。

b. 字典推导式 (Dictionary Comprehension)
  • 模板用途: 快速生成字典推导式。
  • 建议缩写: dc (或您截图中的 compd / compdi for with if)
  • 描述: {key_expr: val_expr for item in iterable if condition}
  • 模板文本 (带if):
    {$KEY_EXPRESSION$: $VALUE_EXPRESSION$ for $ITEM$ in $ITERABLE$ if $CONDITION$}
    $CURSOR$
    
  • 模板文本 (不带if):
    {$KEY_EXPRESSION$: $VALUE_EXPRESSION$ for $ITEM$ in $ITERABLE$}
    $CURSOR$
    
  • 主要占位符说明: $KEY_EXPRESSION$, $VALUE_EXPRESSION$, $ITEM$, $ITERABLE$, $CONDITION$ (可选)。
8. 其他
a. lambda 匿名函数
  • 模板用途: 快速创建一个简单的 lambda 函数。
  • 建议缩写: lam
  • 描述: lambda arguments: expression
  • 模板文本:
    $LAMBDA_VAR$ = lambda $ARGUMENTS$: $EXPRESSION$
    $CURSOR$
    
  • 主要占位符说明: $LAMBDA_VAR$, $ARGUMENTS$, $EXPRESSION$.
b. 注释标记
  • 模板用途: 快速插入标准的 TODO, FIXME, NOTE 注释。
  • 建议缩写: todo / fixme / note
  • 描述: # TODO: ... / # FIXME: ... / # NOTE: ...
  • 模板文本 (以 todo 为例):
    # TODO ($USER$ @ $DATE$): $MESSAGE$
    $CURSOR$
    
  • 主要占位符说明: $USER$ (IDE或可配置), $DATE$ (IDE或可配置), $MESSAGE$.

第二章:转义字符

# 常用转义字符示例
print("line1 \
line2 \
line3")  # 续行符,用于多行字符串

print("\\")  # 反斜杠符号 
print('\'')  # 单引号
print("\"")  # 双引号
print("\a")  # 响铃符号(某些终端会发声)
print("Hello \b World!")  # 退格符(删除前一个字符)
print("\000")  # 空字符(ASCII码为0)
print("Hello\nWorld")  # 换行符
print("Hello\tWorld")  # 水平制表符
print("Hello\rWorld")  # 回车符(将光标移到行首)
print("\f")  # 换页符

# 八进制和十六进制表示
print("\110\145\154\154\157")  # 八进制表示"Hello"
print("\x48\x65\x6c\x6c\x6f")  # 十六进制表示"Hello"

第三章:运算符

运算符优先级

加减乘除 > 比较 > not > and > or

运算符表

类型运算符描述
算术运算符+, -, *, /, //, %, **加、减、乘、除、整除、取模、幂
比较运算符==, !=, >, <, > =, <=等于、不等于、大于、小于、大于等于、小于等于
逻辑运算符and, or, not逻辑与、逻辑或、逻辑非
位运算符&, |, ^, ~, <<, > >按位与、按位或、按位异或、按位取反、左移、右移
赋值运算符=, +=, -=, *=, /=, //=, %= 等简单赋值、复合赋值
身份运算符is, is not判断对象标识
成员运算符in, not in判断成员关系

赋值技巧

# 不换行输出
print(1, end=' ')
print(2, end=' ')
print(3, end=' ')

# 增量赋值
n += 1  # 等价于 n = n + 1

# 链式赋值(同值多变量)
x = y = z = 10

# 交叉赋值(交换变量值)
m, n = 10, 20
m, n = n, m  # 交换值

第四章:类型转换详解

4.1 基本数据类型转换

整型转换 int()

最常用的一种转换类型,通过 int() 可以实现进制之间的快速转换,而无需调用函数

# 基本转换
int(3.14)       # 3 (截断小数部分,不是四舍五入)
int(-3.9)       # -3 (向零截断)
int("42")       # 42 (字符串必须表示有效的数字)
int("0xFF", 16) # 255 (可以指定进制,默认为10进制)
int("101", 2)   # 5 (二进制转十进制)
int(True)       # 1
int(False)      # 0

# 转换失败的情况
# int("3.14")   # ValueError: invalid literal for int() with base 10
# int("hello")  # ValueError: invalid literal for int() with base 10
# int([1, 2])   # TypeError: int() argument must be a string, a bytes-like object or a real number
浮点型转换 float()
# 基本转换
float(42)       # 42.0
float("3.14")   # 3.14
float("42")     # 42.0
float("-3.14e2")# -314.0 (支持科学计数法)
float("inf")    # inf (无穷大)
float("-inf")   # -inf (负无穷大)
float("nan")    # nan (非数值)
float(True)     # 1.0
float(False)    # 0.0

# 转换失败的情况
# float("hello") # ValueError: could not convert string to float: 'hello'
# float([1, 2])  # TypeError: float() argument must be a string or a real number
字符串转换 str()
# 基本转换 - 几乎任何Python对象都可以转为字符串
str(42)  # '42'
str(3.14)  # '3.14'
str(True)  # 'True'
str([1, 2, 3])  # '[1, 2, 3]'
str({'name': 'John'})  # "{'name': 'John'}"
str(None)  # 'None'
布尔值转换 bool()
# 基本转换规则:空值或零值转为False,其他为True
bool(0)          # False
bool(0.0)        # False
bool("")         # False
bool([])         # False
bool({})         # False
bool(set())      # False
bool(())         # False
bool(None)       # False

bool(1)          # True
bool(-1)         # True
bool(0.1)        # True
bool("hello")    # True
bool([0])        # True
bool({0: 0})     # True

4.2 集合类型转换

列表转换 list()
# 从其他可迭代对象创建列表
list("hello")           # ['h', 'e', 'l', 'l', 'o']
list((1, 2, 3))         # [1, 2, 3]
list({1, 2, 3})         # [1, 2, 3] (集合转列表,顺序不确定)
list({"a": 1, "b": 2})  # ['a', 'b'] (字典转列表,获取所有键)
list(range(5))          # [0, 1, 2, 3, 4]
元组转换 tuple()
# 从其他可迭代对象创建元组
tuple([1, 2, 3])        # (1, 2, 3)
tuple("hello")          # ('h', 'e', 'l', 'l', 'o')
tuple({1, 2, 3})        # (1, 2, 3) (集合转元组,顺序不确定)
tuple({"a": 1, "b": 2}) # ('a', 'b') (字典转元组,获取所有键)

# 特殊情况:创建空元组和单元素元组
empty_tuple = ()
single_element_tuple = (42,)  # 注意逗号是必需的
集合转换 set()
# 从其他可迭代对象创建集合(自动去重)
set([1, 2, 2, 3, 3, 3]) # {1, 2, 3}
set("hello")            # {'h', 'e', 'l', 'o'}
set((1, 2, 2, 3))       # {1, 2, 3}
set({"a": 1, "b": 2})   # {'a', 'b'} (字典转集合,获取所有键)

# 应用:列表去重
unique_items = list(set([1, 2, 2, 3, 3, 3]))  # [1, 2, 3] (顺序可能变化)

# 创建空集合
empty_set = set()  # 不能用{},那会创建空字典
字典转换 dict()
# 从键值对创建字典
dict([('a', 1), ('b', 2)])  # {'a': 1, 'b': 2}

# 从两个等长序列创建字典
keys = ['a', 'b', 'c']
values = [1, 2, 3]
dict(zip(keys, values))     # {'a': 1, 'b': 2, 'c': 3}

# 使用关键字参数创建字典
dict(a=1, b=2, c=3)        # {'a': 1, 'b': 2, 'c': 3}

# 合并两个字典 (Python 3.9+)
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
dict1 | dict2              # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

4.3 进阶类型转换

字节转换 bytes()bytearray()
# 字符串转字节
bytes("hello", "utf-8")    # b'hello'
bytes("你好", "utf-8")     # b'\xe4\xbd\xa0\xe5\xa5\xbd'

# 整数列表转字节
bytes([65, 66, 67])        # b'ABC'

# 创建指定长度的空字节序列
bytes(5)                   # b'\x00\x00\x00\x00\x00'

# bytearray是可变版本的bytes
ba = bytearray("hello", "utf-8")  # bytearray(b'hello')
ba[0] = 72                        # 修改第一个字节为'H'
print(ba)                         # bytearray(b'Hello')

# 字节转回字符串
b"hello".decode("utf-8")          # 'hello'
bytearray(b"hello").decode("utf-8") # 'hello'

4.4 实用转换场景与技巧

数字和字符串之间的转换
# 格式化数字为字符串
str(1234)                  # '1234'
"{:,}".format(1234567.89)  # '1,234,567.89' (添加千位分隔符)
"{:.2f}".format(3.14159)   # '3.14' (保留2位小数)
f"{123.456:.2e}"           # '1.23e+02' (科学计数法)

# 解析数字字符串
int("1234")                # 1234
float("3.14")              # 3.14
float("1.23e45")           # 1.23e+45

# 处理货币字符串
price = "$1,234.56"
float(price.replace("$", "").replace(",", ""))  # 1234.56
数据类型检测与安全转换
# 安全地转换为整数
def safe_int(value,default=0):
    try:
        return int(value)
    except (ValueError,TypeError):
        return default


print(safe_int("123"))  # 123
print(safe_int("abc"))   # 0
print(safe_int(None))   # 0
print(safe_int("456", 100))   # 456
print(safe_int("xyz", 100))   # 100

#================================================================================================
类型转换与数据验证
# 验证并转换输入数据
def validate_age(age_str):
    try:
        age = int(age_str) # 尝试将字符串转换为整数
        if 0 <= age <= 120: # 验证年龄是否在0到120之间
            return age # 若年龄有效,则返回年龄
        else:
            raise ValueError("年龄必须在0到120之间") # 若年龄无效,则抛出异常
    except ValueError:
        raise ValueError("无效的年龄格式") # 若输入的年龄不是整数,则抛出异常

# 适用于表单验证
def validate_form(form_data):
    errors = {}

    # 验证和转换名称
    name = form_data.get("name","").strip()
    if not name:
        errors["name"] = "请输入姓名"

        #验证和转换年龄
    age_str = form_data.get("age","")
    try:
        age = validate_age(age_str)
    except ValueError as e:
        errors["age"] = str(e) # 把异常信息转换为字符串
    return errors or None # 如果errors为空,则返回None,否则返回errors

# 测试
form_data = {
    "name": "张三",
    "age": ""
}
print(validate_form(form_data)) # 输出:{'age': '无效的年龄格式'}

4.5 类型转换陷阱与最佳实践

常见陷阱
# 1. 精度损失
int(3.99999)  # 3 (信息丢失)

# 2. 截断而非四舍五入
int(3.6)  # 3 (很多新手误以为会四舍五入)

# 3. 不安全的eval
user_input = "1 + 1"
eval(user_input)  # 2 (安全)

user_input = "__import__('os').system('rm -rf 防止手残')" 
# eval(user_input)  # 危险!可以执行任意代码,执行rm -rf / 会导致整个系统被删除

# 4. 大数字字符串转换性能问题
very_large_num = "9" * 1000000
# int(very_large_num)  # 会很慢并占用大量内存

# 5. 浮点数精度问题
float("0.1") + float("0.2") != float("0.3")  # True,因为浮点精度问题

第五章:数据类型

5.1 字符串类型(str)

字符串输出方式
占位符类型说明
%s字符串(使用 str() 方法转换任何 Python 对象)
%d十进制整数
%f十进制浮点数(小数), 自动保留六位小数。
# 占位符方式
print('我的名字是 %s, 我的年龄是 %d岁' % ('小明', 28))

#    
print('我的名字是 {}, 我的年龄是 {}岁'.format('小明', 28))
# format带索引
print('我的名字是 {1}, 我的年龄是 {0}岁'.format(28, '小明'))

# f-string方式(推荐)
name = '小明'
age = 28
print(f'我的名字是 {name}, 我的年龄是 {age}岁')
字符串常用方法
方法描述示例
startswith()判断是否以指定内容开头str.startswith('hello')
endswith()判断是否以指定内容结尾str.endswith('world')
isdigit()判断是否为数字组成'12345'.isdigit()
isalpha()判断是否为文字组成'hello'.isalpha()
count()统计元素出现次数'hello'.count('l')
find()查找子串位置,未找到返回-1'hello world'.find('world')
upper()转为大写'hello'.upper()
lower()转为小写'HELLO'.lower()
replace()替换字符串'hello'.replace('h', 'H')
split()分割字符串为列表'a,b,c'.split(',')
join()拼接列表为字符串','.join(['a', 'b', 'c'])
strip()去除两端空格或指定字符' hello '.strip()
lstrip()去除左侧空格或指定字符' hello'.lstrip()
rstrip()去除右侧空格或指定字符'hello '.rstrip()
title()将字符串标题化'hello world'.title()
# ======== 字符串方法示例 ========

# startswith() - 判断是否以指定内容开头
text = "Hello, World!"
print(f"'Hello, World!' 是否以'Hello'开头: {text.startswith('Hello')}")  # True
print(f"'Hello, World!' 是否以'World'开头: {text.startswith('World')}")  # False

# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========

# endswith() - 判断是否以指定内容结尾
print(f"'Hello, World!' 是否以'!'结尾: {text.endswith('!')}")  # True
print(f"'Hello, World!' 是否以'Hello'结尾: {text.endswith('Hello')}")  # False
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# isdigit() - 判断是否为数字组成
num_str = "12345"
print(f"'12345' 是否全为数字: {num_str.isdigit()}")  # True
print(f"'Hello' 是否全为数字: {'Hello'.isdigit()}")  # False
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# isalpha() - 判断是否为文字组成
alpha_str = "hello"
print(f"'hello' 是否全为字母: {alpha_str.isalpha()}")  # True
print(f"'hello123' 是否全为字母: {'hello123'.isalpha()}")  # False
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# count() - 统计元素出现次数
print(f"'hello' 中 'l' 出现的次数: {'hello'.count('l')}")  # 2
print(f"'Mississippi' 中 's' 出现的次数: {'Mississippi'.count('s')}")  # 4
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# find() - 查找子串位置,未找到返回-1
print(f"'hello world' 中 'world' 的位置: {'hello world'.find('world')}")  # 6
print(f"'hello world' 中 'python' 的位置: {'hello world'.find('python')}")  # -1
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# upper() - 转为大写
print(f"'hello' 转大写: {'hello'.upper()}")  # HELLO
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# lower() - 转为小写
print(f"'HELLO' 转小写: {'HELLO'.lower()}")  # hello
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# replace() - 替换字符串
print(f"'hello' 替换 'h' 为 'H': {'hello'.replace('h', 'H')}")  # Hello
print(f"'hello hello' 替换所有 'l' 为 'L': {'hello hello'.replace('l', 'L')}")  # heLLo heLLo
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# split() - 分割字符串为列表
print(f"'a,b,c' 按逗号分割: {'a,b,c'.split(',')}")  # ['a', 'b', 'c']
print(f"'hello world' 按空格分割: {'hello world'.split()}")  # ['hello', 'world']
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# join() - 拼接列表为字符串
print(f"用逗号连接 ['a', 'b', 'c']: {','.join(['a', 'b', 'c'])}")  # a,b,c
print(f"用空格连接 ['hello', 'world']: {' '.join(['hello', 'world'])}")  # hello world
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# strip() - 去除两端空格或指定字符
print(f"' hello ' 去除两端空格: {' hello '.strip()}")  # hello
print(f"'xxxhelloxxx' 去除两端的x: {'xxxhelloxxx'.strip('x')}")  # hello
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# lstrip() - 去除左侧空格或指定字符
print(f"' hello' 去除左侧空格: {' hello'.lstrip()}")  # hello
print(f"'xxxhello' 去除左侧的x: {'xxxhello'.lstrip('x')}")  # hello
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# rstrip() - 去除右侧空格或指定字符
print(f"'hello ' 去除右侧空格: {'hello '.rstrip()}")  # hello
print(f"'helloxxx' 去除右侧的x: {'helloxxx'.rstrip('x')}")  # hello
# ======== ======== ======== ======== ======== ======== ======== ======== ======== ========
# title() - 将字符串标题化(每个单词首字母大写)
print(f"'hello world' 标题化: {'hello world'.title()}")  # Hello World

5.2 列表类型(list)

添加元素
# append():在列表末尾添加元素
l = ['Python', 'C++', 'Java']
l.append('PHP')  # ['Python', 'C++', 'Java', 'PHP']

# extend():批量添加元素,逐一添加
l = ['Python', 'C++', 'Java']
l.extend(['C#', 'C', 'JavaScript'])  # ['Python', 'C++', 'Java', 'C#', 'C', 'JavaScript']

# insert():在指定位置插入元素
l = ['Python', 'C++', 'Java']
l.insert(1, 'C')  # ['Python', 'C', 'C++', 'Java']
删除元素
# remove():删除指定元素(第一个匹配项)
nums = [40, 36, 89, 2, 36, 100, 7]
nums.remove(36)  # [40, 89, 2, 36, 100, 7]

# pop():删除指定索引元素,返回被删除的元素
nums = [40, 36, 89, 2, 36, 100, 7]
nums.pop(3)  # [40, 36, 89, 36, 100, 7]
nums.pop()   # 不指定索引,默认删除最后一个

# clear():清空列表
nums = [40, 36, 89, 2, 36, 100, 7]
nums.clear()  # []

# del:删除指定元素或切片
nums = [40, 36, 89, 2, 36, 100, 7]
del nums[2]    # 删除单个元素
del nums[:2]   # 删除切片范围的元素
查找元素
# index():查找元素索引
nums = [40, 36, 89, 2, 36, 100]
print(nums.index(36))  # 返回第一个匹配元素的索引:1

# in 运算符:判断元素是否存在
if 89 in nums:
    print("元素存在")
    
# enumerate():同时获取索引和元素
for index, value in enumerate(nums):
    print(f"索引 {index}: 值 {value}")
    
# count():统计元素出现次数
print(nums.count(36))  # 返回元素出现次数:2
排序
# sort():原地排序
nums = [40, 36, 89, 2, 100, 7]
nums.sort()  # [2, 7, 36, 40, 89, 100]
nums.sort(reverse=True)  # 降序:[100, 89, 40, 36, 7, 2]

# sorted():返回新列表,原列表不变
nums = [40, 36, 89, 2, 100, 7]
sorted_nums = sorted(nums)  # [2, 7, 36, 40, 89, 100]
print(nums)  # 原列表不变:[40, 36, 89, 2, 100, 7]

# 自定义排序(按字符串长度),key可以编写一个匿名函数作为条件
words = ['apple', 'banana', 'cherry', 'date']
sorted_words = sorted(words, key=len)  # ['date', 'apple', 'cherry', 'banana']

# 使用sorted函数结合lambda函数进行复杂排序
# 创建一个包含学生信息的列表(姓名、年龄、成绩、是否参加课外活动)
students = [
    {"name": "张三", "age": 18, "score": 85, "extracurricular": True},
    {"name": "李四", "age": 17, "score": 92, "extracurricular": False},
    {"name": "王五", "age": 19, "score": 78, "extracurricular": True},
    {"name": "赵六", "age": 18, "score": 85, "extracurricular": False},
    {"name": "钱七", "age": 20, "score": 90, "extracurricular": True}
]

# 基本排序:按照学生成绩从高到低排序
# lambda x: x["score"] - 定义一个匿名函数,接收一个参数x(列表中的每个元素),返回x的score值
# reverse=True - 表示降序排序(从高到低)
sorted_by_score = sorted(students, key=lambda x: x["score"], reverse=True)
print("按照学生成绩从高到低排序:")
for student in sorted_by_score:
    print(f"姓名:{student['name']},年龄:{student['age']},学生成绩:{student['score']}")


访问嵌套列表
nested_list = ['prorise', 185, True, [1, 2, 3]]
print(nested_list[3][1])  # 访问嵌套列表的元素:2

5.3 元组类型(tuple)

元组的创建方式
# 1. 使用小括号创建
t1 = (1, 2, 3, 4, 5)

# 2. 省略小括号创建
t2 = 1, 2, 3, 4, 5

# 3. 单元素元组(必须加逗号,否则只是普通值,不是元组)
t3 = (42,)   # 正确:这是一个元组
t4 = (42)    # 错误:这是一个整数,不是元组
t5 = 42,     # 正确:这也是一个元组

# 4. 使用tuple()函数从其他可迭代对象创建
t6 = tuple([1, 2, 3])        # 从列表创建
t7 = tuple("hello")          # 从字符串创建: ('h', 'e', 'l', 'l', 'o')
t8 = tuple({1: 'a', 2: 'b'}) # 从字典创建: (1, 2) - 只保留键

# 5. 创建空元组
empty_tuple = ()
also_empty = tuple()

# 6. 嵌套元组
nested = (1, (2, 3), (4, 5, 6))

# 7. 不同类型的元素
mixed = (1, "hello", True, None, 3.14)

元组的特性与操作
不可变性
t = (1, 2, 3)
# t[0] = 5  # TypeError: 'tuple' object does not support item assignment

# 但可以通过连接创建新元组
t = t + (4, 5)  # 新元组: (1, 2, 3, 4, 5)
t = t * 2       # 重复元组: (1, 2, 3, 4, 5, 1, 2, 3, 4, 5)

# 元组的不可变性让它可以作为字典的键
d = {(1, 2): "tuple as key"}
# d[[1, 2]] = "list as key"  # TypeError: unhashable type: 'list'

元组中可变对象的行为

# 元组中的可变对象(如列表)的内容可以修改
t = (1, [2, 3], 4)
t[1].append(5)  # 正确:修改元组中列表的内容
print(t)        # (1, [2, 3, 5], 4)

# t[1] = [6, 7]  # TypeError: 'tuple' object does not support item assignment

重要说明:元组的不可变性只适用于元组本身的结构(元素的标识符不能改变),而不适用于元组中所包含的可变对象的内容。

访问与切片
t = (0, 1, 2, 3, 4, 5)

# 索引访问(从0开始)
print(t[0])    # 0
print(t[-1])   # 5 (负索引从末尾开始)

# 切片
print(t[1:4])  # (1, 2, 3)
print(t[:3])   # (0, 1, 2)
print(t[3:])   # (3, 4, 5)
print(t[::2])  # (0, 2, 4) - 步长为2
print(t[::-1]) # (5, 4, 3, 2, 1, 0) - 逆序
元组解包
# 基本解包
t = (1, 2, 3)
a, b, c = t
print(a, b, c)  # 1 2 3

# 使用星号(*)收集多余的元素(Python 3.x)
t = (1, 2, 3, 4, 5)
a, b, *rest = t
print(a, b, rest)  # 1 2 [3, 4, 5]

first, *middle, last = t
print(first, middle, last)  # 1 [2, 3, 4] 5

# 忽略某些值(使用下划线作为惯例)
a, _, c = (1, 2, 3)
print(a, c)  # 1 3

# 交换变量值
a, b = 10, 20
a, b = b, a  # 使用元组解包交换值
print(a, b)  # 20 10
元组方法和内置函数

元组的方法比列表少,因为它是不可变的。主要方法有:

t = (1, 2, 3, 2, 4, 2)

# 1. count() - 计算元素出现的次数
print(t.count(2))  # 3

# 2. index() - 返回元素首次出现的索引
print(t.index(3))  # 2
# print(t.index(5))  # ValueError: tuple.index(x): x not in tuple

# 3. 使用内置函数
print(len(t))      # 6 - 元组长度
print(min(t))      # 1 - 最小元素
print(max(t))      # 4 - 最大元素
print(sum(t))      # 14 - 元素和
print(sorted(t))   # [1, 2, 2, 2, 3, 4] - 返回排序后的列表(非元组)
元组的应用场景
作为函数的返回值
def get_user_info():
    # 返回多个值
    return "Alice", 30, "alice@example.com"

# 调用函数并解包返回值
# 判断返回值是否为一个元组
if isinstance(get_user_info(), tuple):
    name, age, email = get_user_info()
    print(f"姓名: {name}, 年龄: {age}, 邮箱: {email}") # 输出姓名: Alice, 年龄: 30, 邮箱: alice@example.com
else:
    print("返回值不是一个元组")
作为不可变集合
# 使用元组作为字典键
locations = {
    (40.730610, -73.935242): "New York",
    (34.052235, -118.243683): "Los Angeles",
    (41.878113, -87.629799): "Chicago"
}

# 更新字典,不更改元组
locations[(51.507351, -0.127758)] = "London"
确保数据不被修改
def process_config(config):
    # config是一个元组,确保处理过程中不被修改
    print("处理配置:", config)
    # 安全的操作...

# 使用
settings = ("debug", "verbose", "log_level=info")
process_config(settings)

5.4 字典类型(dict)

# 创建字典
person = {"name": "张三", "age": 25, "city": "北京"}

# 访问值
print(person["name"])  # 张三

# 设置/修改值
person["age"] = 26
person["email"] = "zhangsan@example.com"  # 添加新键值对

# 删除键值对
del person["city"]
字典常用方法
方法描述示例
get()获取值,键不存在时返回默认值dict.get('key', 默认值)
keys()获取所有键dict.keys()
values()获取所有值dict.values()
items()获取所有键值对dict.items()
setdefault()获取值,键不存在则设置默认值dict.setdefault('key', 默认值)
update()更新字典dict1.update(dict2)
pop()移除指定键值对并返回值dict.pop('key')

5.5 集合类型(set)

# 创建集合
s1 = {1, 2, 3, 4, 5}
s2 = set([1, 2, 3, 3, 2, 1])  # 结果:{1, 2, 3}

# 添加元素
s1.add(6)

# 删除元素
s1.discard(3)  # 元素不存在不会报错
s1.remove(2)   # 元素不存在会报错
集合操作
操作描述示例
&交集(共同元素)s1 & s2
|并集(去重合并)s1 | s2
-差集(属于 s1 不属于 s2)s1 - s2
^对称差集(不同时属于 s1 和 s2)s1 ^ s2
# 求两个集合的交集(共同元素)
s1 = {1, 2, 3, 4, 5}
s2 = {1, 3, 5, 7, 9}
print(s1 & s2)  # {1, 3, 5}

# 求两个集合的并集(去重后在合并)
print(s1 | s2)  # {1, 2, 3, 4, 5, 7, 9}

# 求两个集合的差集(s1中有而s2中没有的元素)
print(s1 - s2)  # {2, 4}

# 求两个集合的对称差集(s1和s2中不同时存在的元素)
print(s1 ^ s2)  # {2, 4, 7, 9}

第六章:条件循环分支

条件语句的高级用法

链式比较

Python 支持数学风格的链式比较,使得条件表达式更简洁明了。

# 传统方式
if x > 0 and x < 10:
    print("x在0到10之间")

# 链式比较
if 0 < x < 10:
    print("x在0到10之间")
    
# 多重链式比较
if 10 <= x < 20 <= y < 30:
    print("x在10到20之间且y在20到30之间")
短路逻辑评估

Python 使用短路逻辑评估条件表达式,即一旦表达式的真假已经确定,后续部分不再执行。

# 短路与(and)
if expensive_function() and rare_condition():
    # 如果expensive_function()返回False,不会执行rare_condition()
    do_something()

# 短路或(or)
if quick_check() or expensive_operation():
    # 如果quick_check()返回True,不会执行expensive_operation()
    do_something()
条件表达式的嵌套

可以在条件表达式内部嵌套其他条件表达式,创建复杂的决策树。

# 复杂嵌套的三元表达式
result = (
    "高分" if score >= 90 else 
    "良好" if score >= 80 else 
    "及格" if score >= 60 else 
    "不及格"
)

# 更复杂的嵌套条件
category = (
    "儿童" if age < 12 else
    "青少年" if age < 18 else
    "成人" if age < 65 else
    "老年人"
)
模式匹配(Python 3.10+)

Python 3.10 引入了结构化模式匹配,类似于其他语言的 switch/case 语句,但功能更强大。

# 基本模式匹配
def analyze_type(data):
    match data:
        case int():
            return "整数"
        case float():
            return "浮点数"
        case str():
            return "字符串"
        case list():
            return "列表"
        case dict():
            return "字典"
        case _:
            return "其他类型"
#===========#===========#===========#===========#===========#===========#===========#===========
# 结构匹配和变量绑定
def process_point(point):
    match point:
        case (0, 0):
            return "原点"
        case (0, y):
            return f"Y轴上的点 y={y}"
        case (x, 0):
            return f"X轴上的点 x={x}"
        case (x, y) if x == y:
            return f"对角线上的点 ({x}, {y})"
        case (x, y):
            return f"普通点 ({x}, {y})"

# 匹配对象属性
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def analyze_point(point):
    match point:
        case Point(x=0, y=0):
            return "原点"
        case Point(x=0, y=y):
            return f"Y轴上的点 y={y}"
        case Point(x=x, y=0):
            return f"X轴上的点 x={x}"
        case Point(x=x, y=y) if x == y:
            return f"对角线上的点 ({x}, {y})"
        case Point():
            return f"普通点 ({point.x}, {point.y})"

循环的高级技巧

循环与迭代器协议

理解迭代器协议有助于更高效地编写循环代码:

class CountDown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        """
        返回迭代器对象
        
        这个方法使CountDown类的实例成为一个可迭代对象。
        当使用for循环遍历该对象时,Python会自动调用这个方法获取迭代器。
        
        返回:
            self: 返回自身作为迭代器
        """
        return self

    def __next__(self):
        """
        获取迭代器中的下一个值
        
        这个方法定义了迭代过程中如何获取下一个元素。
        每次调用时,计数器减1并返回减1前的值。
        当计数器减到0时,抛出StopIteration异常表示迭代结束。
        """
        if self.start <= 0:  # 如果当前值小于等于0,表示迭代已结束
            raise StopIteration
        current = self.start  # 保存当前值
        self.start -= 1       # 计数器减1
        return current        # 返回减1前的值

    
# 测试代码
for i in CountDown(5):
    print(i)
生成器表达式代替列表推导式

生成器表达式在处理大量数据时更节省内存:

import sys
# 列表推导式 - 一次性创建所有元素
sum_of_squares = sys.getsizeof(sum([x*x for x in range(1000000)]))  # 占用大量内存
print(f"占用内存: {sum_of_squares} bytes") # 占用内存: 32 bytes

# 生成器表达式 - 按需生成元素
sum_of_squares = sys.getsizeof(sum(x*x for x in range(1000000)))    # 内存效率高
print(f"占用内存: {sys.getsizeof(sum_of_squares)} bytes") # 占用内存: 28 bytes
使用 enumerate 获取索引
# 传统方式
fruits = ['apple', 'banana', 'cherry']
for i in range(len(fruits)):
    print(f"{i + 1}. {fruits[i]}")

# 使用enumerate更优雅(并可指定起始索引)
for i, fruit in enumerate(fruits, 1):  # 从1开始计数
    print(f"{i}. {fruit}")
并行迭代多个序列
# 使用zip()并行迭代
names = ['Alice', 'Bob', 'Charlie']
ages = [24, 32, 28]
cities = ['New York', 'Boston', 'Chicago']

for name, age, city in zip(names, ages, cities):
    print(f"{name}, {age}岁, 来自{city}")

流程控制的高级模式

嵌套循环的优化
# 嵌套循环的替代方案
import itertools

# 传统嵌套循环
results = []
for x in range(3):
    for y in range(3):
        for z in range(3):
            results.append((x, y, z))

# 使用product代替嵌套循环
results = list(itertools.product(range(3), range(3), range(3)))
递归与循环

有些问题使用递归比循环更直观:

通过下列的代码处理思想,对于常见的树结构都能以递归的思想去解决问题

# 递归处理嵌套结构
def process_nested_list(nested_list, depth=0):
    result = []
    for item in nested_list:
        if isinstance(item, list):
            # 递归处理子列表
            sub_result = process_nested_list(item, depth + 1)
            result.extend(sub_result)
        else:
            # 处理叶节点
            result.append((depth, item))
    return result


# 递归执行逻辑解析:
# 1. 函数接收一个嵌套列表和当前深度(默认为0)
# 2. 遍历列表中的每个元素:
#    - 如果元素是列表,递归调用自身处理该子列表,深度+1
#    - 如果元素不是列表,将(深度,元素值)添加到结果中
# 3. 最终返回包含所有元素及其深度信息的扁平列表
#
# 例如处理 [1, [2, [3, 4], 5], 6]:
# - 1不是列表,添加(0,1)
# - [2,[3,4],5]是列表,递归处理:
#   - 2不是列表,添加(1,2)
#   - [3,4]是列表,递归处理:
#     - 3不是列表,添加(2,3)
#     - 4不是列表,添加(2,4)
#   - 5不是列表,添加(1,5)
# - 6不是列表,添加(0,6)
# 最终结果: [(0,1), (1,2), (2,3), (2,4), (1,5), (0,6)]

# 使用示例
data = [1, [2, [3, 4], 5], 6]
print(process_nested_list(data))  # [(0, 1), (1, 2), (2, 3), (2, 4), (1, 5), (0, 6)]

第七章: 文件操作

Python 提供了强大而灵活的文件操作接口,从基础的读写能力到高级的路径操作和目录管理。本章将由浅入深地介绍 Python 文件操作的全面知识。

7.1 文件打开模式

文件操作的第一步是打开文件,Python 提供了多种打开模式以满足不同需求。

模式描述文件不存在时文件存在时常见应用场景
r只读模式报错从头读取读取配置文件、日志文件
w只写模式创建新文件清空内容生成报告、写入日志
a追加模式创建新文件在末尾追加日志记录、数据收集
r+读写模式报错可读可写需要同时读写的场景
w+读写模式创建新文件清空内容需要先写后读的场景
a+追加读写创建新文件追加且可读数据分析、日志分析
b二进制模式与其他模式组合处理二进制图片、视频、压缩文件
t文本模式(默认)与其他模式组合处理文本文本文件处理

实用提示:选择合适的文件模式可以避免数据丢失。例如,使用 w 模式时要格外小心,因为它会清空现有文件。当不确定时,使用 a 模式追加内容会更安全。

7.2 基本文件操作

7.2.1 文件读取操作
# 使用 with 语句自动处理文件关闭(推荐方式)
with open('example.txt', mode='r', encoding='utf-8') as file:
    # 方法1:一次性读取整个文件
    content = file.read()  # 读取全部内容到内存
    print(content)
    
    # 注意:read()后文件指针已经到达文件末尾
    # 需要重新打开文件或使用seek(0)重置指针
    file.seek(0)  # 重置文件指针到文件开头
    
    # 方法2:读取一行
    line = file.readline()  # 读取第一行(包含换行符)
    print(line)
    
    # 方法3:读取所有行到列表
    file.seek(0)  # 重置文件指针
    lines = file.readlines()  # 返回包含所有行的列表
    print(lines)  # ['第一行\n', '第二行\n', ...]
    
    # 方法4:逐行读取(内存效率最高,推荐用于大文件)
    file.seek(0)  # 重置文件指针
    for line in file:  # 文件对象本身是可迭代的
        # line包含换行符,通常需要移除
        print(line.strip())  # 去除行首行尾的空白字符
7.2.2 文件写入操作
# 写入文件(覆盖模式)
with open('example.txt', mode='w', encoding='utf-8') as file:
    # 方法1:写入字符串
    file.write('第一行内容\n')  # 注意需手动添加换行符
    file.write('第二行内容\n')
    
    # 方法2:写入多行
    lines = ['第三行内容\n', '第四行内容\n']
    file.writelines(lines)  # 注意writelines不会自动添加换行符
    
# 追加内容到文件
with open('example.txt', mode='a', encoding='utf-8') as file:
    file.write('追加的内容\n')
    
# 读写模式示例
with open('example.txt', mode='r+', encoding='utf-8') as file:
    content = file.read(10)  # 读取前10个字符
    file.write('插入的内容')  # 在当前位置(第10个字符后)写入
7.2.3 多文件操作
# 同时操作多个文件(例如:文件复制)
with open('source.txt', 'r', encoding='utf-8') as source, \
     open('destination.txt', 'w', encoding='utf-8') as destination:
    
    # 按块读取和写入(适用于大文件)
    chunk_size = 4096  # 4KB 的块大小
    while True:
        chunk = source.read(chunk_size)
        if not chunk:  # 到达文件末尾
            break
        destination.write(chunk)
        
    # 或者简单地复制所有内容
    # source.seek(0)  # 先重置到文件开头
    # destination.write(source.read())
    
    # 或者逐行复制(适合文本处理)
    # source.seek(0)
    # for line in source:
    #     # 可以在此添加行处理逻辑
    #     destination.write(line)
7.2.4 文件修改示例

在实际应用中,我们经常需要读取、修改并写回文件:

# 示例:为Markdown文件的所有标题增加一个#符号
file_name = 'document.md'
output_file = 'document_modified.md'

# 读取并修改
with open(file_name, 'r', encoding='utf-8') as input_file:
    lines = input_file.readlines()
    
    # 处理每一行
    for i in range(len(lines)):
        # 如果行以#开头(表示标题),则增加一个#
        if lines[i].strip().startswith('#'):
            lines[i] = '#' + lines[i]

# 将修改后的内容写入新文件
with open(output_file, 'w', encoding='utf-8') as output_file:
    output_file.writelines(lines)
    
print(f"文件已修改并保存为 {output_file}")

最佳实践:对于文件修改操作,始终先写入临时文件,然后在确认写入成功后才替换原文件,这样可以防止文件损坏。

7.3 文件指针控制

文件指针(或文件位置)决定了读写操作的起始位置。掌握指针控制对于高级文件操作至关重要。

7.3.1 seek() 和 tell() 函数
with open('example.txt', 'w+', encoding='utf-8') as file:
    # 写入一些测试内容
    file.write("Hello World! 你好世界!") # 英文部分 "Hello World " 占用 12 个字符,中文部分 "你好世界!" 占用 12 个字符,剩余的空格+符号占据了 4 个字符 总共 28 个字符
    
    # tell() 获取当前文件指针位置
    position = file.tell()
    print(f"写入内容后的位置: {position}")  # 写入内容后的位置: 28

    # 将指针移回文件开头
    file.seek(0)
    print(f"回到文件开头位置: {file.tell()}")   # 回到文件开头位置: 0

    # 读取全部内容
    content = file.read()
    print(f"文件全部内容: {content}") # 文件全部内容: Hello World! 你好世界!
    print(f"读取后的位置: {file.tell()}")  # 读取后的位置: 28

    # 再次回到文件开头
    file.seek(0)
    
    # 读取前5个字符
    print(f"前5个字符: {file.read(5)}") # 前5个字符: Hello
    print(f"读取5个字符后的位置: {file.tell()}") # 读取5个字符后的位置: 5

    # 回到文件开头
    file.seek(0)
    
    # 一次性定位到第13个字符位置(从文件开头算起)
    file.seek(13)
    print(f"直接定位到第10个位置: {file.tell()}")
    print(f"从第13个位置开始读取的内容: {file.read()}") # 从第13个位置开始读取的内容: 你好世界!

注意:在文本模式下,由于字符编码原因,seek() 可能无法精确定位到任意字节位置。对于需要精确控制的场景,应使用二进制模式 (‘rb’, ‘wb’ 等)。

7.3.2 实际应用场景
# 场景:在大日志文件中读取最后100行
def tail(file_path, n=10):
    """读取文件最后n行,类似于Unix的tail命令"""
    with open(file_path, 'rb') as f:
        # 移动到文件末尾
        f.seek(0, 2)
        # 文件总大小
        total_size = f.tell()
        
        # 初始化变量
        block_size = 1024
        block = -1
        lines = []
        
        # 从文件末尾向前读取
        while len(lines) < n and -block * block_size < total_size:
            # 移动到倒数第block个块
            position = max(0, total_size + block * block_size)
            f.seek(position)
            
            # 读取数据块
            data = f.read(min(block_size, total_size - position))
            
            # 处理可能被截断的行
            if position > 0:
                # 丢弃第一行不完整数据
                data = data.split(b'\n', 1)[1] if b'\n' in data else b''
            
            # 计算行数
            lines_in_block = data.split(b'\n')
            
            # 合并行
            lines = lines_in_block + lines
            block -= 1
        
        # 返回最后n行
        return [line.decode('utf-8') for line in lines[-n:]]

# 使用示例
last_lines = tail('large_log_file.txt', 100)
for line in last_lines:
    print(line)

7.4 缓冲区管理

理解缓冲区对于优化文件操作性能至关重要,特别是在处理大量小块写入时。

7.4.1 缓冲设置与刷新
# 设置不同的缓冲策略
# buffering=0: 无缓冲 (仅在二进制模式可用)
# buffering=1: 行缓冲 (仅在文本模式可用)
# buffering>1: 指定缓冲区大小(字节)
# buffering=-1: 使用默认缓冲区大小

# 无缓冲 (每次写入都直接写入磁盘)
with open('binary_file.bin', 'wb', buffering=0) as f:
    f.write(b'Data will be written immediately')

# 行缓冲 (遇到换行符时刷新)
with open('log.txt', 'w', buffering=1) as f:
    f.write('This line will be buffered\n')  # 遇到\n会刷新
    f.write('Until a newline is encountered')  # 保留在缓冲区

# 指定缓冲区大小
with open('large_file.txt', 'w', buffering=4096) as f:
    # 4KB缓冲区,适合频繁小写入
    for i in range(10000):
        f.write(f'Line {i}\n')  # 数据会积累到4KB才写入磁盘

# 手动刷新缓冲区
with open('important.txt', 'w') as f:
    f.write('Critical data')
    f.flush()  # 立即刷新缓冲区,确保数据写入磁盘
    os.fsync(f.fileno())  # 进一步确保数据写入物理存储设备
7.4.2 缓冲区触发条件

缓冲区会在以下条件下自动刷新:

  1. 缓冲区满时
  2. 文件关闭时(如 with 块结束)
  3. 调用 flush() 方法时
  4. 程序正常退出时
  5. 行缓冲模式下遇到换行符时

性能提示:对于大量小写操作,使用适当的缓冲区大小可以显著提高性能。但对于关键数据,应及时调用 flush() 确保数据安全。

7.5 文件路径操作

有效管理文件路径是文件操作的基础,Python 提供了两种主要方式:传统的 os.path 模块和现代的 pathlib 库。

7.5.1 使用 os.path 模块
方法/属性描述
os.getcwd()获取当前工作目录
os.chdir(path)改变当前工作目录
os.mkdir(name)创建目录
os.makedirs(path)递归创建多级目录
os.rmdir(name)删除空目录
os.remove(path)删除文件
os.listdir(path)列出指定目录的文件和目录
os.rename(src, dst)重命名文件或目录
os.system(command)执行系统命令
os.environ环境变量字典
os.path.exists(path)检查路径是否存在
os.path.isfile(path)检查路径是否为文件
os.path.isdir(path)检查路径是否为目录
os.path.join(path1, path2)连接路径
os.path.split(path)根据最后一个路径分隔符分割路径为(目录, 文件名)
os.path.splitext(path)根据最后一个点号分割路径为(文件名,拓展名)
os.path.dirname(path)获取路径的目录部分
os.path.basename(path)获取路径的文件名部分
os.path.getsize(path)获取文件大小(字节)
import os.path
import datetime

# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))

# 在当前目录下创建data.txt
data_path = os.path.join(current_dir, "data.txt")

# 首先创建一个测试文件
with open(data_path, 'w', encoding='utf-8') as file:
    file.write("这是测试内容!\nHello World!")

# 文件路径处理演示
print(f"=== 文件路径信息 ===")
directory = os.path.dirname(data_path)
filename = os.path.basename(data_path)
name, extension = os.path.splitext(filename)
print(f"目录: {directory}")
print(f"文件名: {filename}")
print(f"纯名称: {name}")
print(f"扩展名: {extension}")

print(f"\n=== 路径检查 ===")
exists = os.path.exists(data_path)
is_file = os.path.isfile(data_path)
is_dir = os.path.isdir(data_path)
print(f"文件是否存在: {exists}")
print(f"是否是文件: {is_file}")
print(f"是否是目录: {is_dir}")

print(f"\n=== 文件信息 ===")
try:
    size = os.path.getsize(data_path)
    mod_time = os.path.getmtime(data_path) # 获取修改时间
    create_time = os.path.getctime(data_path) # 获取创建时间
    access_time = os.path.getatime(data_path) # 获取访问时间
    
    # 转换时间戳为可读时间
    mod_datetime = datetime.datetime.fromtimestamp(mod_time)
    create_datetime = datetime.datetime.fromtimestamp(create_time)
    access_datetime = datetime.datetime.fromtimestamp(access_time)
    
    print(f"文件大小: {size} 字节")
    print(f"创建时间: {create_datetime}")
    print(f"修改时间: {mod_datetime}")
    print(f"访问时间: {access_datetime}")
    
    print(f"\n=== 文件内容 ===")
    with open(data_path, 'r', encoding='utf-8') as file:
        content = file.read()
        print(f"文件内容:\n{content}")
        
except OSError as e:
    print(f"获取文件信息时发生错误: {e}")

7.5.2 使用 pathlib 模块 (Python 3.4+)
from pathlib import Path
import datetime

# 获取当前脚本所在目录并创建data.txt的路径
current_file = Path(__file__)
data_path = current_file.parent / "data.txt"

# 创建并写入测试文件
data_path.write_text("这是测试内容!\nHello World!", encoding='utf-8')

print("=== 基本路径信息 ===")
print(f"完整路径: {data_path}")
print(f"父目录: {data_path.parent}")
print(f"文件名: {data_path.name}")
print(f"纯名称: {data_path.stem}")
print(f"扩展名: {data_path.suffix}")
print(f"所有路径组件: {data_path.parts}")

print("\n=== 路径解析 ===")
print(f"绝对路径: {data_path.absolute()}")
print(f"解析路径: {data_path.resolve()}")
try:
    print(f"相对路径: {data_path.relative_to(Path.cwd())}")
except ValueError:
    print("无法计算相对路径(文件可能在不同驱动器或目录)")

print("\n=== 文件状态检查 ===")
print(f"文件是否存在: {data_path.exists()}")
print(f"是否是文件: {data_path.is_file()}")
print(f"是否是目录: {data_path.is_dir()}")
print(f"是否是符号链接: {data_path.is_symlink()}")

print("\n=== 文件信息 ===")
if data_path.exists() and data_path.is_file():
    stat = data_path.stat()
    print(f"文件大小: {stat.st_size} 字节")
    print(f"创建时间: {datetime.datetime.fromtimestamp(stat.st_ctime)}")
    print(f"修改时间: {datetime.datetime.fromtimestamp(stat.st_mtime)}")
    print(f"访问时间: {datetime.datetime.fromtimestamp(stat.st_atime)}")

print("\n=== 文件内容 ===")
print(f"文件内容:\n{data_path.read_text(encoding='utf-8')}")

print("\n=== 路径修改示例 ===")
new_name = data_path.with_name("newdata.txt")
new_ext = data_path.with_suffix(".csv")
new_stem = data_path.with_stem("newdata")
print(f"修改整个文件名: {new_name}")
print(f"修改扩展名: {new_ext}")
print(f"仅修改文件名(不含扩展名): {new_stem}")
功能os.path 方式pathlib 方式推荐
路径连接os.path.join(dir, file)Path(dir) / filepathlib 更直观
获取目录os.path.dirname(path)path.parentpathlib 属性访问更清晰
获取文件名os.path.basename(path)path.namepathlib 属性访问更清晰
分离扩展名os.path.splitext(path)[1]path.suffixpathlib 更简洁
检查存在os.path.exists(path)path.exists()两者类似,pathlib 面向对象
获取绝对路径os.path.abspath(path)path.absolute()两者相当

最佳实践:在新项目中优先使用 pathlib,它提供了更现代、更直观的面向对象接口。在维护旧代码时可能需要继续使用 os.path。

7.6 高级文件操作

7.6.1 二进制文件操作
# 读取整个二进制文件
def read_binary_file(file_path):
    """读取并返回整个二进制文件的内容"""
    with open(file_path, "rb") as f:
        return f.read()


# 分块读取大型二进制文件
def process_large_binary_file(file_path, chunk_size=1024 * 1024):
    """分块处理大型二进制文件,避免内存溢出"""
    with open(file_path, "rb") as f:
        while True:
            chunk = f.read(chunk_size)  # 每次读取1MB
            if not chunk:  # 到达文件末尾
                break

            # 处理当前数据块
            process_chunk(chunk)  # 假设的处理函数


# 处理数据块的函数
def process_chunk(chunk):
    """处理二进制数据块的函数"""
    # 这里可以根据需要实现具体的数据处理逻辑
    # 例如:计算校验和、搜索特定字节模式、转换数据格式等
    chunk_size = len(chunk)
    print(f"处理数据块: {chunk_size} 字节")

    # 示例:计算数据块的哈希值
    import hashlib
    chunk_hash = hashlib.md5(chunk).hexdigest()
    print(f"数据块MD5哈希值: {chunk_hash}")

    # 示例:检查数据块中的特定字节序列
    if b'\x00\x00\xff\xff' in chunk:
        print("在数据块中发现特定字节序列")

    # 返回处理结果(可选)
    return {
        'size': chunk_size,
        'hash': chunk_hash
    }


# 读取文件特定部分
def read_file_section(file_path, start, length):
    """读取文件中从start位置开始的length个字节"""
    with open(file_path, "rb") as f:
        f.seek(start)  # 移动文件指针到指定位置
        return f.read(length)  # 读取指定长度字节


# 检测文件类型
def detect_file_type(file_path):
    """通过文件头部特征识别文件类型"""
    # 常见文件格式的魔数(Magic Numbers)
    file_signatures = {
        b'\x89PNG\r\n\x1a\n': 'PNG image',
        b'\xff\xd8\xff': 'JPEG image',
        b'GIF87a': 'GIF image (87a)',
        b'GIF89a': 'GIF image (89a)',
        b'%PDF': 'PDF document',
        b'PK\x03\x04': 'ZIP archive',
        b'\x50\x4b\x03\x04': 'ZIP archive',  # PK..
        b'\x1f\x8b\x08': 'GZIP compressed file',
    }

    with open(file_path, "rb") as f:
        # 读取文件前16个字节用于检测
        header = f.read(16)

    for signature, file_type in file_signatures.items():
        if header.startswith(signature):
            return file_type

    return "未知文件类型"


# 实际演示:处理图像文件
def image_file_demo():
    """演示二进制文件操作的实际应用"""
    # 定义两个图形的基础文件路径
    png_path = "example.png"
    jpg_path = "example.jpg"

    # 检测图像类型
    print(f"检测文件类型: {png_path}{detect_file_type(png_path)}") # 检测文件类型: example.png 是 JPEG image
    print(f"检测文件类型: {jpg_path}{detect_file_type(jpg_path)}") # 检测文件类型: example.jpg 是 JPEG image

    # 读取PNG文件头部信息
    png_header = read_file_section(png_path, 0, 24)
    print(f"\nPNG文件头部字节: {png_header.hex(' ', 4)}")  # PNG文件头部字节: ffd8ffe0 00104a46 49460001 01000001 00010000 ffdb0043

    # 获取文件大小
    png_data = read_binary_file(png_path)
    jpg_data = read_binary_file(jpg_path)

    # 小技巧:在len函数中使用:, 即可以将数字以千分位分隔符展示
    print(f"\n{png_path} 文件大小: {len(png_data):,} 字节") # example.png 文件大小: 7,189 字节
    print(f"{jpg_path} 文件大小: {len(jpg_data):,} 字节") # example.jpg 文件大小: 7,189 字节

    # 处理大型二进制文件
    process_large_binary_file(png_path)
    process_large_binary_file(jpg_path)

if __name__ == '__main__':
    image_file_demo()
7.6.2 临时文件操作

临时文件对于需要中间处理结果但不想留下永久文件的操作非常有用。

import tempfile
import os
import time
import random


# 示例2: 使用临时目录处理多个文件
def process_batch_files(data_items):
    """在临时目录中创建多个文件并进行批处理"""
    results = []

    with tempfile.TemporaryDirectory(prefix="batch_") as temp_dir:
        print(f"创建临时工作区 {temp_dir}")

        # 创建多个数据文件
        file_paths = []
        for i, data in enumerate(data_items):
            # 获取到临时目录下的文件路径
            file_path = os.path.join(temp_dir, f"data_{i}.txt")
            with open(file_path, "w", encoding="utf-8") as f:
                f.write(data)
            file_paths.append(file_path)

        # 处理所有的文件
        for file_path in file_paths:
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()
                # 模拟处理:计算字符数并添加到结果集中
                results.append(f"文件{os.path.basename(file_path)} 包含 {len(content)} 个字符")
        # 列出处理的文件
        print(f"处理文件为{','.join(os.path.basename(p) for p in file_paths)}")

        # 退出with块后,tempfile会自动删除临时目录
        # 暂停30秒,等待用户查看结果
        print("处理开始,大约需要30秒,请稍候...")
        time.sleep(random.randint(10, 30))
        print("处理完成")

    return results



# 演示临时目录的批处理使用
data_to_process = [
    "这是第一个文件的测试内容!",
    "这是第二个文件包含更多的信息以及携带数字13123123123132",
    "这是第三个文件包含中文,你好,世界!"]

results = process_batch_files(data_to_process)
print("\n处理结果为:")
for r in results:
    print(results)

7.7 目录操作

目录操作是文件系统操作的重要组成部分,Python 提供了多种目录操作方法。

7.7.1 基本目录操作
import os
import shutil

# 获取当前工作目录
current_dir = os.getcwd()
print(f"当前工作目录: {current_dir}")

# 更改当前工作目录
os.chdir("../")
print(f"更改后的工作目录: {os.getcwd()}")

# 列出目录内容
entries = os.listdir(".")
print(f"目录内容: {entries}")

# 过滤目录内容
files_only = [f for f in entries if os.path.isfile(f)]
dirs_only = [d for d in entries if os.path.isdir(d)]
print(f"文件: {files_only}")
print(f"目录: {dirs_only}")

# 创建目录
os.chdir(current_dir) # 切换回原目录
os.mkdir("new_dir") # 创建单个目录
os.makedirs("new_dir2/sub_dir/sub_sub_dir",exist_ok=True) # 创建多级目录 (exist_ok=True 忽略已存在的目录)

# 删除目录
os.rmdir("new_dir") # 只能删除空目录
shutil.rmtree("new_dir2") # 删除目录以及所有内容(谨慎使用!!!)
7.7.2 使用 pathlib 进行目录操作
from pathlib import Path
import shutil

# 创建目录
Path("new_dir").mkdir(exist_ok=True)
Path("parent/child/grandchild").mkdir(parents=True, exist_ok=True) # 创建多层目录 parents=True 递归创建父目录

# 列出目录内容
p = Path(".")
for item in p.iterdir():
    if item.is_file():
        print(f"文件: {item},大小: {item.stat().st_size} bytes")
    elif item.is_dir():
        print(f"目录: {item}")

# 过滤特定类型的文件
python_files = list(p.glob("*.py")) # 列出当前目录下所有.py 文件
all_python_files = list(p.rglob("*.py")) # 递归搜索所有子目录

print(f"当前目录下Python文件: {[f.name for f in python_files]}")
print(f"递归搜索所有Python文件: {[f.name for f in all_python_files]}")
print(f"所有Python文件: {len(all_python_files)}")


# 删除目录
Path('new_dir').rmdir()  # 只能删除空目录
shutil.rmtree('parent') # 删除目录及其所有内容
操作os/shutil 方法pathlib 方法优势比较
获取当前目录os.getcwd()Path.cwd()pathlib 返回 Path 对象,便于后续操作
切换工作目录os.chdir(path)(无直接等效方法)os 更适合改变全局工作目录
列出目录内容os.listdir(path)Path(path).iterdir()pathlib 直接返回 Path 对象,无需再拼接路径
创建单个目录os.mkdir(path)Path(path).mkdir()功能相同,pathlib 更面向对象
创建多级目录os.makedirs(path, exist_ok=True)Path(path).mkdir(parents=True, exist_ok=True)语义更明确,参数名更具描述性
删除空目录os.rmdir(path)Path(path).rmdir()功能相同,pathlib 更面向对象
递归删除目录shutil.rmtree(path)(无直接等效方法,需循环删除)os/shutil 提供单一高效操作
文件路径拼接os.path.join(dir, file)Path(dir) / filepathlib 使用/运算符更直观简洁
检查路径是否存在os.path.exists(path)Path(path).exists()功能相同,pathlib 更面向对象
检查是否为文件os.path.isfile(path)Path(path).is_file()功能相同,pathlib 更面向对象
检查是否为目录os.path.isdir(path)Path(path).is_dir()功能相同,pathlib 更面向对象
7.7.3 递归遍历目录树

递归遍历是处理目录层次结构的强大工具,在很多需要列举文件目录树的场景都可以采用该思路去打印输出

# 使用os.walk遍历目录树
import os
#示例输出:
# 学习用/
#     data.txt - 0.04 KB
#     example.jpg - 7.02 KB
#     example.png - 7.02 KB
#     example.py - 4.34 KB
#     example.txt - 0.03 KB
#     main.py - 4.54 KB
#     requirements.txt - 0.01 KB
#     study.py - 1.40 KB
#     电子商务系统实现笔记.md - 24.60 KB
#     (目录大小: 49.00 KB)

def scan_directory(directory):
    """递归扫描目录,显示结构和文件大小"""
    total_size = 0
    for root, dirs, files in os.walk(directory):  # 其中root:当前目录路径,dirs:子目录列表,files:文件列表
        # 计算当前的目录深度(用于缩进)
        # 计算目录深度:
        # 1. root.replace(directory, "") - 将当前路径中的起始目录部分替换为空字符串,得到相对路径
        # 2. .count(os.sep) - 统计相对路径中目录分隔符(如'/'或'\')的数量
        # 每一个分隔符代表一层目录深度,因此分隔符的数量就等于目录的嵌套层级
        level = root.replace(directory, "").count(os.sep)
        indent = ' ' * 4 * level
        # 打印当前目录
        print(f"{indent}{os.path.basename(root)}/")

        # 缩进子文件
        sub_indent = ' ' * 4 * (level + 1)

        # 统计当前目录下的文件大小
        dir_size = 0
        for file in files:
            file_path = os.path.join(root, file)
            file_size = os.path.getsize(file_path)
            dir_size += file_size
            print(f"{sub_indent}{file} - {file_size / 1024:.2f} KB")

        # 累加总大小
        total_size += dir_size
        print(f"{sub_indent}(目录大小: {dir_size / 1024:.2f} KB)")

    return total_size / 1024  # 返回总大小(单位:KB)
if __name__ == '__main__':
    total_size = scan_directory('D:/python/学习用')
    print(f"总大小: {total_size:.2f} KB")

7.7.4 文件复制、移动和删除操作
import os
import shutil

# ========== 文件复制操作 ==========
# shutil.copy(src, dst): 复制文件到目标路径,不保留元数据(如文件的创建时间、修改时间等)
# 参数说明:src - 源文件路径,dst - 目标路径(可以是目录或文件名)
# 返回值:目标文件路径
shutil.copy('source.txt', 'destination.txt')

# shutil.copy2(src, dst): 复制文件到目标路径,保留所有元数据(如修改时间、访问时间、权限等)
# 参数说明:同copy函数
# 返回值:目标文件路径
shutil.copy2('source.txt', 'destination.txt')

# ========== 目录复制操作 ==========
# shutil.copytree(src, dst): 递归复制整个目录树,目标目录不能已存在
# 参数说明:src - 源目录,dst - 目标目录(必须不存在)
# 返回值:目标目录路径
shutil.copytree('source_dir', 'destination_dir')

# shutil.copytree 高级用法:使用ignore参数忽略特定文件
# shutil.ignore_patterns(): 创建一个忽略函数,用于过滤不需要复制的文件
# 参数说明:可变参数,接受多个glob风格的模式字符串
shutil.copytree('source_dir', 'destination_dir',
                ignore=shutil.ignore_patterns('*.pyc', '*.git'))

# ========== 文件移动操作 ==========
# shutil.move(src, dst): 移动文件或目录到目标路径
# 参数说明:src - 源路径,dst - 目标路径
# 返回值:目标路径
# 用法1:重命名文件
shutil.move('old_name.txt', 'new_name.txt')
# 用法2:移动文件到其他目录
shutil.move('file.txt', 'directory/')

# ========== 文件删除操作 ==========
# os.remove(path): 删除指定路径的文件(不能删除目录)
# 参数说明:path - 要删除的文件路径
# 注意:如果文件不存在会抛出FileNotFoundError异常
os.remove('file.txt')

7.8 文件监控与变更检测

在某些应用场景中,需要监控文件变化并作出响应。

7.8.1 基础文件监控
import os
import time


def monitor_file(file_path, interval=1, encoding='utf-8'):
    """监控文件变化,并输出新增内容"""
    if not os.path.exists(file_path):
        print(f"文件 {file_path} 不存在")
        return

    # 获取初始状态
    last_size = os.path.getsize(file_path) # 文件大小
    last_modified = os.path.getmtime(file_path) # 最后修改时间戳
    print(f"开始监控文件:{file_path}")
    print(f"文件大小:{last_size} 最后修改时间:{time.ctime(last_modified)}")
    try:
        while True:
            # 检查文件是否被修改
            current_modified = os.path.getmtime(file_path)
            current_size = os.path.getsize(file_path)
            if current_modified != last_modified:
                print(f"文件在{time.ctime(current_modified)}被修改")
            # 如果文件增大了,读取新增内容
            if current_size > last_size:
                # 尝试不同的编码方式读取文件
                encodings_to_try = ['utf-8', 'gbk', 'gb2312', 'gb18030']
                content_read = False
                
                for enc in encodings_to_try:
                    try:
                        with open(file_path, "r", encoding=enc) as f:
                            f.seek(last_size) # 移动文件指针到上次读取位置
                            new_content = f.read()
                            print(f"新增内容:\n{new_content}")
                            # 更新当前使用的编码
                            encoding = enc
                            content_read = True
                            break
                    except UnicodeDecodeError:
                        continue
                
                if not content_read:
                    # 如果所有编码都失败,尝试以二进制方式读取并显示
                    try:
                        with open(file_path, "rb") as f:
                            f.seek(last_size)
                            binary_content = f.read()
                            print(f"无法解码文件内容,显示二进制内容: {binary_content}")
                    except Exception as e:
                        print(f"读取文件失败: {e}")
                        
            elif current_size < last_size: # 文件缩小了,可能是被清空了
                print("文件大小减小了,可能被截断或重写")

            # 更新状态
            last_modified = current_modified
            last_size = current_size

            time.sleep(interval) # 休眠一段时间再检查文件
    except KeyboardInterrupt:
        print("监控已停止")



if __name__ == '__main__':
    monitor_file("destination.txt", interval=1)

7.8.2 使用 watchdog 库进行高级文件监控

对于更复杂的文件系统监控需求,Python 的第三方库 watchdog 提供了更强大的功能:

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, DirCreatedEvent, FileCreatedEvent, DirDeletedEvent, \
    FileDeletedEvent, DirModifiedEvent, FileModifiedEvent, DirMovedEvent, FileMovedEvent
import time
import os


class MyHandler(FileSystemEventHandler):
    def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None:
        if not event.is_directory:
            print(f"文件被创建: {event.src_path}")

    def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None:
        if not event.is_directory:
            print(f"文件被删除: {event.src_path}")

    def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
        if not event.is_directory:
            print(f"文件被修改: {event.src_path}")

    def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None:
        if not event.is_directory:
            print(f"文件被移动: {event.src_path} -> {event.dest_path}")


def watch_directory(path: str) -> None:
    # 确保监控的是目录而不是文件
    if os.path.isfile(path):
        # 如果是文件,则监控其所在的目录
        directory = os.path.dirname(path)
        if not directory:  # 如果是当前目录下的文件
            directory = '.'
    else:
        directory = path
    
    event_handler = MyHandler()
    observer = Observer()
    observer.schedule(event_handler, directory, recursive=True)
    observer.start()
    try:
        print(f"开始监控目录: {directory}")
        print("按Ctrl+C停止监控...")
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


if __name__ == '__main__':
    watch_directory(".")  # 监控当前目录,或者指定一个确实存在的目录路径

7.9 实际应用场景示例

7.9.1 文件备份工具
import os
import shutil
import datetime
from pathlib import Path
import zipfile

def backup_directory(source_dir, backup_dir=None, zip_backup=True):
    """
    创建目录的备份
    
    参数:
        source_dir: 要备份的源目录
        backup_dir: 备份文件存放目录(默认在源目录的父目录)
        zip_backup: 是否创建zip压缩备份
    
    返回:
        备份文件的路径
    """
    # 确保源目录存在
    source_path = Path(source_dir)
    if not source_path.exists() or not source_path.is_dir():
        raise ValueError(f"源目录 '{source_dir}' 不存在或不是一个目录")
    
    # 设置默认备份目录
    if backup_dir is None:
        backup_dir = source_path.parent
    else:
        backup_dir = Path(backup_dir)
        if not backup_dir.exists():
            backup_dir.mkdir(parents=True)
    
    # 创建备份文件名(包含时间戳)
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_name = f"{source_path.name}_backup_{timestamp}"
    backup_path = backup_dir / backup_name
    
    if zip_backup:
        # 创建ZIP备份
        zip_path = str(backup_path) + '.zip'
        print(f"创建ZIP备份: {zip_path}")
        
        with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            # 遍历源目录中的所有文件
            for root, _, files in os.walk(source_dir):
                for file in files:
                    file_path = os.path.join(root, file)
                    # 计算文件在ZIP中的相对路径
                    rel_path = os.path.relpath(file_path, source_dir)
                    print(f"添加: {rel_path}")
                    zipf.write(file_path, rel_path)
        
        return zip_path
    else:
        # 创建目录备份(复制)
        print(f"创建目录备份: {backup_path}")
        shutil.copytree(source_path, backup_path)
        return str(backup_path)

# 使用示例
# source = "/path/to/important_data"
# backup = backup_directory(source, zip_backup=True)
# print(f"备份已创建: {backup}")
7.9.2 日志分析工具
from collections import Counter
import re
from datetime import datetime
import matplotlib.pyplot as plt
from pathlib import Path

def analyze_log_file(log_path, pattern=None):
    """
    分析日志文件并生成报告
    
    参数:
        log_path: 日志文件路径
        pattern: 用于匹配日志行的正则表达式模式(默认为None,表示所有行)
    
    返回:
        包含分析结果的字典
    """
    log_path = Path(log_path)
    if not log_path.exists():
        raise FileNotFoundError(f"日志文件不存在: {log_path}")
    
    # 初始化结果
    results = {
        'total_lines': 0,
        'matched_lines': 0,
        'errors': 0,
        'warnings': 0,
        'by_hour': Counter(),
        'ip_addresses': Counter(),
        'status_codes': Counter(),
        'top_urls': Counter()
    }
    
    # 编译正则表达式
    if pattern:
        regex = re.compile(pattern)
    
    # IP地址模式
    ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
    
    # HTTP状态码模式
    status_pattern = re.compile(r'\s(\d{3})\s')
    
    # 时间戳模式(假设格式为[DD/Mon/YYYY:HH:MM:SS +ZZZZ])
    timestamp_pattern = re.compile(r'\[(\d{2}/\w{3}/\d{4}):(\d{2}):\d{2}:\d{2}\s[+\-]\d{4}\]')
    
    # URL模式
    url_pattern = re.compile(r'"(?:GET|POST|PUT|DELETE)\s+([^\s"]+)')
    
    # 错误和警告模式
    error_pattern = re.compile(r'ERROR|CRITICAL|FATAL', re.IGNORECASE)
    warning_pattern = re.compile(r'WARNING|WARN', re.IGNORECASE)
    
    # 读取和分析日志文件
    with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
        for line in f:
            results['total_lines'] += 1
            
            # 应用模式匹配过滤(如果提供)
            if pattern and not regex.search(line):
                continue
            
            results['matched_lines'] += 1
            
            # 提取IP地址
            ip_matches = ip_pattern.findall(line)
            if ip_matches:
                results['ip_addresses'].update([ip_matches[0]])
            
            # 提取HTTP状态码
            status_match = status_pattern.search(line)
            if status_match:
                results['status_codes'].update([status_match.group(1)])
            
            # 提取URL
            url_match = url_pattern.search(line)
            if url_match:
                results['top_urls'].update([url_match.group(1)])
            
            # 提取时间并按小时汇总
            time_match = timestamp_pattern.search(line)
            if time_match:
                date_str, hour = time_match.groups()
                results['by_hour'].update([int(hour)])
            
            # 检查错误和警告
            if error_pattern.search(line):
                results['errors'] += 1
            elif warning_pattern.search(line):
                results['warnings'] += 1
    
    return results

def generate_log_report(results, output_dir=None):
    """生成日志分析报告(文本和图表)"""
    output_dir = Path(output_dir) if output_dir else Path.cwd()
    if not output_dir.exists():
        output_dir.mkdir(parents=True)
    
    # 创建文本报告
    report_path = output_dir / "log_analysis_report.txt"
    with open(report_path, 'w', encoding='utf-8') as f:
        f.write("=== 日志分析报告 ===\n")
        f.write(f"总行数: {results['total_lines']}\n")
        f.write(f"匹配行数: {results['matched_lines']}\n")
        f.write(f"错误数: {results['errors']}\n")
        f.write(f"警告数: {results['warnings']}\n\n")
        
        f.write("=== 按小时分布 ===\n")
        for hour in sorted(results['by_hour']):
            f.write(f"{hour}时: {results['by_hour'][hour]}行\n")
        
        f.write("\n=== 前10个IP地址 ===\n")
        for ip, count in results['ip_addresses'].most_common(10):
            f.write(f"{ip}: {count}次\n")
        
        f.write("\n=== HTTP状态码统计 ===\n")
        for status, count in results['status_codes'].most_common():
            f.write(f"{status}: {count}次\n")
        
        f.write("\n=== 前10个URL ===\n")
        for url, count in results['top_urls'].most_common(10):
            f.write(f"{url}: {count}次\n")
    
    # 生成图表报告
    
    # 1. 按小时分布图
    plt.figure(figsize=(10, 6))
    hours = range(24)
    counts = [results['by_hour'].get(hour, 0) for hour in hours]
    plt.bar(hours, counts)
    plt.xlabel('小时')
    plt.ylabel('日志条目数')
    plt.title('日志按小时分布')
    plt.xticks(hours)
    plt.grid(True, axis='y', alpha=0.3)
    plt.savefig(output_dir / 'hourly_distribution.png')
    
    # 2. HTTP状态码分布饼图
    plt.figure(figsize=(8, 8))
    status_codes = list(results['status_codes'].keys())
    counts = list(results['status_codes'].values())
    plt.pie(counts, labels=status_codes, autopct='%1.1f%%', startangle=140)
    plt.axis('equal')
    plt.title('HTTP状态码分布')
    plt.savefig(output_dir / 'status_codes_pie.png')
    
    # 3. 前5个IP地址条形图
    plt.figure(figsize=(10, 6))
    top_ips = results['ip_addresses'].most_common(5)
    ips = [ip for ip, _ in top_ips]
    counts = [count for _, count in top_ips]
    plt.barh(ips, counts)
    plt.xlabel('请求次数')
    plt.ylabel('IP地址')
    plt.title('前5个IP地址')
    plt.grid(True, axis='x', alpha=0.3)
    plt.tight_layout()
    plt.savefig(output_dir / 'top_ips.png')
    
    return report_path

# 使用示例
# log_file = "access.log"
# results = analyze_log_file(log_file)
# report_path = generate_log_report(results, "reports")
# print(f"报告已生成: {report_path}")
7.9.3 文件同步工具
import os
import shutil
import hashlib
from pathlib import Path
import time
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("file_sync.log"),
        logging.StreamHandler()
    ]
)

def calculate_file_hash(filepath):
    """计算文件的MD5哈希值"""
    hash_md5 = hashlib.md5()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

def sync_directories(source_dir, target_dir, delete=False, exclude=None):
    """
    同步两个目录的内容
    
    参数:
        source_dir: 源目录
        target_dir: 目标目录
        delete: 是否删除目标目录中源目录没有的文件
        exclude: 要排除的文件/目录列表
    
    返回:
        操作统计信息
    """
    source_dir = Path(source_dir)
    target_dir = Path(target_dir)
    exclude = exclude or []
    
    # 确保目录存在
    if not source_dir.exists():
        raise ValueError(f"源目录不存在: {source_dir}")
    
    if not target_dir.exists():
        logging.info(f"创建目标目录: {target_dir}")
        target_dir.mkdir(parents=True)
    
    # 初始化统计
    stats = {
        "copied": 0,
        "updated": 0,
        "deleted": 0,
        "skipped": 0
    }
    
    # 获取源文件清单
    source_files = {}
    for root, dirs, files in os.walk(source_dir):
        # 从dirs中移除要排除的目录(修改原地)
        dirs[:] = [d for d in dirs if d not in exclude]
        
        for file in files:
            if file in exclude:
                continue
                
            file_path = Path(root) / file
            rel_path = file_path.relative_to(source_dir)
            source_files[rel_path] = file_path
    
    # 同步文件到目标目录
    for rel_path, source_path in source_files.items():
        target_path = target_dir / rel_path
        
        # 确保目标目录存在
        target_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 检查文件是否需要更新
        if not target_path.exists():
            logging.info(f"复制新文件: {rel_path}")
            shutil.copy2(source_path, target_path)
            stats["copied"] += 1
        else:
            # 比较修改时间和哈希值
            source_mtime = os.path.getmtime(source_path)
            target_mtime = os.path.getmtime(target_path)
            
            if abs(source_mtime - target_mtime) > 1:  # 1秒容差
                # 进一步比较内容哈希
                source_hash = calculate_file_hash(source_path)
                target_hash = calculate_file_hash(target_path)
                
                if source_hash != target_hash:
                    logging.info(f"更新文件: {rel_path}")
                    shutil.copy2(source_path, target_path)
                    stats["updated"] += 1
                else:
                    stats["skipped"] += 1
            else:
                stats["skipped"] += 1
    
    # 处理需要删除的文件
    if delete:
        for root, dirs, files in os.walk(target_dir):
            for file in files:
                file_path = Path(root) / file
                rel_path = file_path.relative_to(target_dir)
                
                if rel_path not in source_files and file not in exclude:
                    logging.info(f"删除多余文件: {rel_path}")
                    file_path.unlink()
                    stats["deleted"] += 1
        
        # 删除空目录(从下向上遍历)
        for root, dirs, files in os.walk(target_dir, topdown=False):
            for dir_name in dirs:
                dir_path = Path(root) / dir_name
                if not any(dir_path.iterdir()):  # 检查目录是否为空
                    logging.info(f"删除空目录: {dir_path.relative_to(target_dir)}")
                    dir_path.rmdir()
    
    return stats

# 定期同步功能
def periodic_sync(source_dir, target_dir, interval=3600, delete=False, exclude=None):
    """
    定期同步两个目录
    
    参数:
        source_dir: 源目录
        target_dir: 目标目录
        interval: 同步间隔(秒)
        delete: 是否删除目标目录中多余文件
        exclude: 要排除的文件/目录列表
    """
    logging.info(f"启动定期同步: 从 {source_dir}{target_dir}, 间隔 {interval}秒")
    
    try:
        while True:
            logging.info("开始同步...")
            start_time = time.time()
            
            try:
                stats = sync_directories(source_dir, target_dir, delete, exclude)
                logging.info(f"同步完成: 复制={stats['copied']}, 更新={stats['updated']}, "
                            f"删除={stats['deleted']}, 跳过={stats['skipped']}")
            except Exception as e:
                logging.error(f"同步出错: {str(e)}")
            
            # 计算实际等待时间
            elapsed = time.time() - start_time
            wait_time = max(0, interval - elapsed)
            
            logging.info(f"等待{wait_time:.1f}秒后进行下次同步...")
            time.sleep(wait_time)
            
    except KeyboardInterrupt:
        logging.info("同步服务已停止")

# 使用示例
# sync_directories("/path/to/source", "/path/to/backup", delete=True, exclude=[".git", "node_modules"])
# 定期同步(每小时)
# periodic_sync("/path/to/source", "/path/to/backup", interval=3600, delete=True)

第八章: 函数知识总结

8.1 内置函数

数学和数值计算
abs(-2)           # 绝对值:2
divmod(20, 3)     # 商和余数:(6, 2)
round(4.51)       # 四舍五入:5
pow(10, 2)        # 幂运算:100
pow(10, 2, 3)     # 幂运算后取余:1
sum([1, 2, 3])    # 求和:6
min(5, 3, 9)      # 最小值:3
max(7, 3, 15)     # 最大值:15
进制转换
bin(10)           # 转二进制:'0b1010'
oct(10)           # 转八进制:'0o12'
hex(10)           # 转十六进制:'0xa'
字符编码
bytes("你好", encoding="utf-8")  # 字符串转字节:b'\xe4\xbd\xa0\xe5\xa5\xbd'
bytearray("hi", encoding='utf-8')  # 可变字节数组
ord('a')          # 字符码位:97
chr(65)           # 码位对应字符:'A'
反射函数
dir(obj)          # 查看对象的所有属性
hasattr(obj, 'attr')  # 检查对象是否有属性
getattr(obj, 'attr')  # 获取对象属性
setattr(obj, 'attr', value)  # 设置对象属性
delattr(obj, 'attr')  # 删除对象属性
其他常用内置函数
len("hello")      # 获取长度:5
isinstance(obj, type)  # 类型检查
issubclass(cls1, cls2)  # 检查类继承关系
id(obj)           # 获取对象内存地址
type(obj)         # 获取对象类型

8.2 函数定义与调用

# 基本函数定义
def function_name(parameters):
    """文档字符串:描述函数功能"""
    # 函数体
    return value  # 可选

# 调用函数
result = function_name(arguments)

8.3 函数参数

位置参数
def test(x, y, z):
    print(x, y, z)
    
test(1, 2, 3)  # 必须按顺序提供所有参数
关键字参数
def test(x, y, z):
    print(x, y, z)
    
test(y=1, x=2, z=3)  # 通过参数名指定,顺序可变
默认参数
def test(x, y, z=2):
    print(x, y, z)
    
test(1, 2)      # z使用默认值2
test(1, 2, 3)   # 覆盖默认值
可变参数
# *args:接收多余的位置参数,形成元组
def test(x, *args):
    print(x)
    print(args)
    
test(1, 2, 3, 4)  # x=1, args=(2, 3, 4)

# **kwargs:接收多余的关键字参数,形成字典
def test(x, **kwargs):
    print(x)
    print(kwargs)
    
test(x=1, y=2, z=3)  # x=1, kwargs={'y': 2, 'z': 3}

# 混合使用
def test(x, *args, **kwargs):
    print(x, args, kwargs)
    
test(1, 2, 3, y=4, z=5)  # x=1, args=(2, 3), kwargs={'y': 4, 'z': 5}

8.4 作用域与命名空间

名称空间

Python 有三层名称空间:

  1. 内置名称空间:Python 解释器启动时创建,包含内置函数
  2. 全局名称空间:模块级别创建,包含模块中定义的变量
  3. 局部名称空间:函数调用时创建,包含函数内部变量
LEGB 规则

变量查找顺序:

  1. Local:局部作用域
  2. Enclosing:外部嵌套函数作用域
  3. Global:全局作用域
  4. Built-in:内置作用域
作用域修饰
# global:声明变量为全局变量
x = 10
def modify_global():
    global x
    x = 20
    
modify_global()
print(x)  # 输出:20

# nonlocal:访问外层函数变量
def outer():
    x = 10
    def inner():
        nonlocal x
        x = 20
    inner()
    print(x)  # 输出:20
    
outer()

8.5 高阶函数特性

函数作为参数传递
def apply_function(func, value):
    return func(value)
    
def square(x):
    return x ** 2
    
result = apply_function(square, 5)  # 结果:25
函数作为返回值
def get_multiplier(factor):
    def multiplier(x):
        return x * factor
    return multiplier
    
double = get_multiplier(2)
triple = get_multiplier(3)

print(double(5))  # 结果:10
print(triple(5))  # 结果:15
函数存储在数据结构中
def check_balance():
    print("查询账户余额")


def transfer_money():
    print("转账服务")


def deposit():
    print("存款服务")


def withdraw():
    print("取款服务")


def update_info():
    print("更新个人信息")


# 存储在列表中 - 通过数字选择功能
function_list = [check_balance, transfer_money, deposit, withdraw, update_info]

# 存储在字典中 - 通过命令选择功能
function_dict = {
    'balance': check_balance, 
    'transfer': transfer_money, 
    'deposit': deposit, 
    'withdraw': withdraw, 
    'update': update_info
}


def bank_system():
    print("\n欢迎使用银行服务系统")
    print("您可以通过数字或命令使用服务")
    
    while True:
        print("\n=== 银行服务菜单 ===")
        print("1. 查询账户余额")
        print("2. 转账服务")
        print("3. 存款服务")
        print("4. 取款服务")
        print("5. 更新个人信息")
        print("0. 退出系统")
        print("\n或者输入命令:balance, transfer, deposit, withdraw, update, exit")
        
        choice = input("\n请输入您的选择(数字或命令): ")
        
        if choice == "0" or choice.lower() == "exit":
            print("感谢使用银行服务系统,再见!")
            break
            
        # 通过数字调用列表中的函数
        if choice.isdigit():
            index = int(choice) - 1
            if 0 <= index < len(function_list):
                function_list[index]()
            else:
                print("无效的数字选择,请重新输入")
                
        # 通过命令调用字典中的函数
        elif choice.lower() in function_dict:
            function_dict[choice.lower()]()
        else:
            print("无效的命令,请重新输入")


# 启动银行系统
if __name__ == "__main__":
    bank_system()

高阶函数设计模式

高阶函数是函数式编程的核心概念,它们接受其他函数作为参数或返回函数作为结果。

函数组合模式
def compose(*functions):
    """将多个函数组合成一个函数
    compose(f, g, h)(x) 等价于 f(g(h(x)))
    """
    # 定义一个内部函数,接收一个参数x
    def inner(x):
        # 初始化结果为输入值x
        result = x
        # 反转函数列表,确保执行顺序是从右到左
        # 例如compose(f, g, h)(x)会按h(x),然后g(结果),最后f(结果)的顺序执行
        for f in reversed(functions):
            # 将上一步的结果作为当前函数的输入
            result = f(result)
        # 返回最终结果
        return result
    # 返回内部函数,形成闭包
    return inner

# 示例:文本处理管道
def remove_punctuation(text):
    import string
    # 去除标点符号
    # str.maketrans创建一个转换表,将所有标点符号映射为空
    # string.punctuation包含所有标点符号
    return text.translate(str.maketrans('', '', string.punctuation))

def lowercase(text):
    # 将文本转换为小写
    return text.lower()

def remove_whitespace(text):
    # 先用split()将文本按空白字符分割成列表
    # 然后用join重新连接,确保单词之间只有一个空格
    return ' '.join(text.split())

# 组合函数
# 这里创建了一个处理管道,按照从右到左的顺序执行:
# 1. 首先remove_punctuation去除标点
# 2. 然后lowercase转小写
# 3. 最后remove_whitespace规范化空白
process_text = compose(remove_whitespace, lowercase, remove_punctuation)

# 使用函数管道
text = "Hello, World!  This is an Example."
# 执行过程:
# 1. remove_punctuation: "Hello World  This is an Example"
# 2. lowercase: "hello world  this is an example"
# 3. remove_whitespace: "hello world this is an example"
print(process_text(text))  # "hello world this is an example"
部分应用

部分应用是预先指定一个函数的部分参数,创建一个新的函数。

from functools import partial

def power(base, exponent):
    return base ** exponent

# 创建一个计算平方的函数
square = partial(power, exponent=2)
print(square(4))  # 16

# 创建一个计算立方的函数
cube = partial(power, exponent=3)
print(cube(3))  # 27

# 创建一个计算2的幂的函数
powers_of_two = partial(power, 2)
print(powers_of_two(8))  # 256 (2^8)

8.6 匿名函数(lambda)

 # 基本语法
lambda 参数: 表达式

# 示例
add = lambda x, y: x + y
print(add(5, 3))  # 结果:8

# 在高阶函数中使用
numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x**2, numbers)
print(list(squares))  # 结果:[1, 4, 9, 16, 25]

# 在排序中使用
words = ['apple', 'banana', 'cherry']
sorted_words = sorted(words, key=lambda x: len(x))

8.7 闭包函数

闭包是指内部函数引用了外部函数的变量,并且外部函数返回了内部函数。

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

my_counter = counter()
print(my_counter())  # 1
print(my_counter())  # 2
print(my_counter())  # 3

8.8 递归函数

递归是一种函数在执行过程中调用自身的编程技巧。

# 阶乘递归实现
def factorial(n):
    # 基本情况(递归终止条件)
    if n == 0 or n == 1:
        return 1
    # 递归调用
    else:
        return n * factorial(n-1)
        
print(factorial(5))  # 120
递归的关键要素
  1. 基本情况(终止条件):必须定义何时停止递归
  2. 递归关系:将问题分解为更小的子问题
递归深度限制
import sys
print(sys.getrecursionlimit())  # 默认递归深度(通常为1000)
sys.setrecursionlimit(3000)     # 调整递归深度限制
递归案例:列表扁平化
def flatten_list(nested_list):
    """递归扁平化嵌套列表"""
    result = []
    for item in nested_list:
        if isinstance(item, list):
            # 递归处理子列表
            result.extend(flatten_list(item))
        else:
            # 基本情况:添加非列表元素
            result.append(item)
    return result

# 测试
nested = [1, [2, 3], [4, [5, 6], 7], 8, [9, [10, [11, 12]]]]
print(flatten_list(nested))  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

8.9 装饰器

装饰器是一种特殊的函数,用于修改其他函数的功能。

基本装饰器
import time


def count_time(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        result = func(*args,**kwargs)
        end_time = time.time()
        print("总共耗时:",end_time-start_time)
        return result
    return wrapper

@count_time # 在这里装饰器相当于 count_time = count_time(test)
def test():
    sum = 0
    for i in range(10000000):
        sum += i
    return sum


result = test()
print(result)
带参数的装饰器
import time

def repeat(times):
    def count_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = None
            for i in range(times):
                result = func(*args, **kwargs)
                print(f"第{i+1}次执行完成")
            end_time = time.time()
            print("总共耗时:", end_time - start_time)
            return result
        return wrapper
    return count_time

@repeat(3) # 重复执行3次
def count_sum():
    sum = 0
    for i in range(10000000):
        sum += i
    return sum


result = count_sum()
print(result)
保留原函数元数据
from functools import wraps


def decorator(func):
    @wraps(func)  # 保留原函数的元数据
    def wrapper(*args, **kwargs):
        """包装函数"""
        print("函数执行前")
        result = func(*args, **kwargs)
        print("函数执行后")
        return result

    return wrapper


@decorator
def say_hello(name):
    """打招呼函数"""
    print(f"Hello, {name}!")

# __name__属性为函数的名称,但默认情况下,被装饰器装饰了的函数的名称会变成wrapper,可以通过wraps装饰器保留原函数的元数据来解决这个问题。
print(say_hello.__name__)  # say_hello (而不是wrapper)
# 同理,__doc__属性为函数的文档字符串,可以通过wraps装饰器保留原函数的元数据来解决这个问题。
print(say_hello.__doc__)  # 打招呼函数
装饰器叠加
@decorator1
@decorator2
@decorator3
def function():
    pass

# 等价于
function = decorator1(decorator2(decorator3(function)))
# 执行顺序示例
def decorator1(func):
    def wrapper(*args, **kwargs):
        print("装饰器1开始")
        result = func(*args, **kwargs)
        print("装饰器1结束")
        return result
    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print("装饰器2开始")
        result = func(*args, **kwargs)
        print("装饰器2结束")
        return result
    return wrapper

@decorator1
@decorator2
def greet():
    print("问候函数执行")

greet()
# 输出:
# 装饰器1开始
# 装饰器2开始
# 问候函数执行
# 装饰器2结束
# 装饰器1结束
装饰器工厂

创建能够接受多种配置的通用装饰器。

import functools
import time


def retry(exceptions, tries=4, delay=3, backoff=2, logger=None):
    """可配置的重试装饰器

    Args:
        exceptions: 要捕获的异常类或异常类元组
        tries: 最大尝试次数
        delay: 初始延迟时间(秒)
        backoff: 重试间隔的增长因子
        logger: 用于记录警告的日志对象
    """

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            mtries, mdelay = tries, delay
            last_exception = None

            while mtries > 0:
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    msg = f"{func.__name__}: 重试 {tries - mtries + 1}/{tries} 失败: {e}"
                    if logger:
                        logger.warning(msg)
                    else:
                        print(msg)

                    # 延迟后重试
                    time.sleep(mdelay)
                    mdelay *= backoff
                    mtries -= 1

            # 所有尝试都失败
            raise last_exception

        return wrapper

    return decorator


# 使用示例
import requests

@retry(exceptions=(requests.RequestException, ConnectionError),
       tries=3, delay=1, backoff=2)
def fetch_url(url):
    """获取URL内容,失败时自动重试"""
    response = requests.get(url, timeout=2)
    response.raise_for_status()
    return response.text


# 调用示例
try:
    content = fetch_url("https://this-website-does-not-exist-123456789.com")
    print(content)
except requests.RequestException as e:
    print(f"获取内容失败: {e}")

第九章:迭代器、生成器与推导式

9.1 迭代器

迭代器是一种可以被遍历的对象,它实现了 __iter__()__next__() 方法。

# 获取迭代器的两种方式
# 方法一:使用iter()函数
iter_str = iter("Python") # 将字符串转换为迭代器
# 使用next()函数获取迭代器的元素
print(next(iter_str)) # 输出:'P'
print(next(iter_str)) # 输出:'y'
print(next(iter_str)) # 输出:'t'
print(next(iter_str)) # 输出:'h'
print(next(iter_str)) # 输出:'o'
print(next(iter_str)) # 输出:'n'
# 也可以通过for循环获取迭代器的元素
for char in iter_str:
    print(char) # 在这里不会输出了,因为迭代器的元素已经被上方取完了


# 方法二:使用__iter__()方法
# 在Python中,所有的元素都会有一个默认的__iter__()方法,该方法返回一个迭代器对象,该对象可以用来获取元素。
# 因此,如果一个对象实现了__iter__()方法,那么它就可以被转换为迭代器。
iter_str2 = "Python".__iter__() # 调用字符串的__iter__()方法,将字符串转换为迭代器
print(iter_str2.__next__()) # 输出:'P'

# 迭代完成后会抛出StopIteration异常
try:
    while True:
        print(next(iter_str2))
except StopIteration:
    print("迭代完成")

自定义迭代器
class CountDown:
    def __init__(self, start):
        self.start = start
        
    def __iter__(self):
        return self
        
    def __next__(self):
        if self.start <= 0:
            raise StopIteration
        self.start -= 1
        return self.start + 1
        
# 使用自定义迭代器
counter = CountDown(5)
for num in counter:
    print(num)  # 输出: 5, 4, 3, 2, 1
for 循环的工作原理
# for循环实际上是这样工作的:
def my_for(iterable, callback): # iterable是可迭代对象,callback是一个回调函数,每一次迭代都会调用这个函数
    #通过iter()函数将"abc"转换成一个迭代器
    iterator = iter(iterable)
    while True:
        try:
            # 无限循环,直到迭代器结束
            item = next(iterator)
            # 调用回调函数
            callback(item)
        except StopIteration:
            break


# 例如:
my_for("abc", lambda x: print(x))
# 等价于
for x in "abc":
    print(x)

9.2 生成器

生成器是一种特殊的迭代器,使用 yield 语句而不是 return 语句返回值。每次调用 next() 时,生成器函数从上次离开的地方继续执行

yield 相比较 return 关键字在于 yield 属于“等一下再离开”,每次调用 yield 都会返回不同的值,也就是每一次循环便利都会返回下一个 yield,直到没有

在这里我们简单的了解一下 yield 关键字,到了并发编程中,yiled 还会有一些常见的用途,如:

1、形成生成器

2、协程

3.上下文管理器

4、yield from

# 简单的生成器函数
def simple_generator():
    yield 1
    yield 2
    yield 3
    
gen = simple_generator()
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
# print(next(gen))  # 抛出StopIteration
生成器表达式(推导式)
# 生成器表达式
even_numbers = (x for x in range(10) if x % 2 == 0)

# 使用生成器
for num in even_numbers:
    print(num)  # 输出: 0, 2, 4, 6, 8
生成器的优势
  1. 内存效率:生成器不会一次性生成所有值,而是按需生成,降低内存使用
  2. 延迟计算:只有在请求下一个值时才执行计算
  3. 无限序列:可以表示理论上无限的数据流
# 内存效率示例
# 传统列表方式
def get_lines_list(file_path):
    result = []
    with open(file_path, 'r') as f:
        for line in f:
            result.append(line.strip())
    return result

# 生成器方式
def get_lines_generator(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()
生成器应用场景
# 批量处理示例
def batch_processor(items, batch_size=100):
    """将大列表分成小批次处理"""
    batch = []
    for item in items:
        batch.append(item)
        if len(batch) == batch_size:
            yield batch
            batch = []
    if batch:  # 处理最后不满batch_size的批次
        yield batch

# 使用
items = list(range(1050))
for batch in batch_processor(items, 300):
    print(f"处理批次,大小: {len(batch)}")

9.3 推导式

推导式是创建数据集合的简洁方法。

列表推导式
# 基本语法
[表达式 for 变量 in 可迭代对象 if 条件]

# 示例
squares = [x**2 for x in range(1, 11)]  # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# 带条件过滤
even_squares = [x**2 for x in range(1, 11) if x % 2 == 0]  # [4, 16, 36, 64, 100]

# 多层推导式
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [x for row in matrix for x in row]  # [1, 2, 3, 4, 5, 6, 7, 8, 9]
字典推导式
# 基本语法
{键表达式: 值表达式 for 变量 in 可迭代对象 if 条件}

# 示例
squares_dict = {x: x**2 for x in range(1, 6)}  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# 键值互换
original = {'a': 1, 'b': 2, 'c': 3}
inverted = {v: k for k, v in original.items()}  # {1: 'a', 2: 'b', 3: 'c'}
集合推导式
# 基本语法
{表达式 for 变量 in 可迭代对象 if 条件}

# 示例
squares_set = {x**2 for x in range(1, 11)}  # {1, 4, 9, 16, 25, 36, 49, 64, 81, 100}

# 去除重复元素
words = ['hello', 'world', 'hello', 'python']
unique_lengths = {len(word) for word in words}  # {5, 6}
生成器表达式
# 基本语法
(表达式 for 变量 in 可迭代对象 if 条件)

# 示例
squares_gen = (x**2 for x in range(1, 11))

# 在函数调用中使用(不需要额外括号)
sum_of_squares = sum(x**2 for x in range(1, 11))  # 385
三元表达式
# 基本语法1 if 条件 else2

# 示例
x, y = 10, 20
larger = x if x > y else y  # y

# 在推导式中使用
values = [1, 2, 3, 4, 5]
result = [x*2 if x % 2 == 0 else x*3 for x in values]  # [3, 4, 9, 8, 15]

第十章: 模块与包

模块是 Python 中组织代码的基本单位,本质上是一个包含 Python 定义和语句的文件。本文将深入探讨模块与包的概念、使用方法以及高级应用技巧,结合 PyCharm 中的包管理最佳实践。

10.1 模块分类

在 Python 生态系统中,模块可以分为三大类:

模块类型说明示例
内置模块Python 解释器自带的标准库模块os, sys, math, datetime
第三方模块社区开发者创建的模块numpy, pandas, requests
自定义模块开发者自己创建的模块项目中自定义的.py 文件

重要提示:首次导入自定义模块时,Python 会执行该模块中的所有顶层代码。每个模块都有自己的名称空间,模块中定义的变量属于该模块的名称空间。

10.2 模块导入方式

10.2.1 导入整个模块
import module_name

## 使用模块中的内容
module_name.function_name()
module_name.variable_name
10.2.2 从模块导入特定内容
from module_name import function_name, variable_name

## 直接使用,无需模块名前缀
function_name()
print(variable_name)

工作原理:使用 from 方式导入时,被导入的对象会直接引用模块中对应变量的内存地址,可以直接使用而无需模块前缀。

10.2.3 导入时重命名
## 模块重命名
import module_name as alias
alias.function_name()

## 函数重命名
from module_name import function_name as fn
fn()
10.2.4 导入所有内容(不推荐)
from module_name import *

注意:这种方式可能导致命名冲突,不利于代码可读性和维护性。在大型项目中应避免使用。

10.3 控制模块导入

我们可以在每一个模块的 __init__ 文件中使用如下的操作

可以使用 __all__ 列表来控制 from module import * 语句导入的内容:

## 在模块文件中定义
__all__ = ['function1', 'function2', 'CONSTANT1']

## 不在__all__中的变量和函数,使用from module import *时不会被导入
_private_variable = "这个变量不会被导入"

10.4 模块的特殊属性

10.4.1 __name__ 属性

每个 Python 文件都有一个 __name__ 属性:

  • 当直接运行该文件时,__name__ 的值为 '__main__'
  • 当作为模块被导入时,__name__ 的值为模块名

这个特性可用于编写既可作为模块导入,又可独立运行的代码:

## 模块内的代码
def main_function():
    print("执行主函数逻辑")
    
def helper_function():
    print("辅助函数")

if __name__ == '__main__':
    # 这部分代码只在直接运行文件时执行
    # 作为模块导入时不会执行
    main_function()
    print("运行模块自测试...")
10.4.2 From 模块无法识别问题

Python 在导入模块时会按照一定顺序搜索模块文件,在有些情况下我们自己定义的模块不一定会被检测到
如下列图片:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

例如,当我们的模型层期望用到另外一个 的代码时,往往会这样引入:

from ecommerce_system.ecommerce.interfaces.payment import PaymentProcessor
from ecommerce_system.ecommerce.interfaces.shipping import ShippingMethod, Address

但这样是无法被识别到的,我们应该是需要这样做:

  • 1.标记外层的包为根包
  • 2.去掉 ecommerce_system 前缀

这样 Pycharm 就会检测到我们是在这个包下进行操作的,即可识别到

从我们的根包出发,也就是图片中蓝色的包(这个是需要在 IDEA)手动标注的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.5 包的概念与使用

包是一种特殊的模块,它是一个包含 __init__.py 文件的目录,用于组织相关模块。包可以包含子包和模块,形成层次结构。

10.5.1 包的结构示例
mypackage/
    __init__.py     # 使目录成为包的文件
    module1.py      # 模块1
    module2.py      # 模块2
    subpackage/     # 子包
        __init__.py
        module3.py
10.5.2 __init__.py 文件的作用
  1. 标识目录为包:Python 将包含 __init__.py 的目录视为包
  2. 初始化包:在导入包时执行初始化代码
  3. 定义包的公共接口:通过 __all__ 列表指定 from package import * 时导入的内容
  4. 自动导入子模块:可以在 __init__.py 中导入子模块,使它们在导入包时可用

示例 __init__.py

## mypackage/__init__.py

## 从子模块导入主要函数,使它们在导入包时可用
from .module1 import function1
from .module2 import function2

## 定义包导出的符号
__all__ = ['function1', 'function2']

## 包初始化代码
print("mypackage 已加载")
10.5.3 包的导入方式
10.5.3.1 导入包中的模块
## 完整路径导入
import mypackage.module1
mypackage.module1.function1()

## 导入特定模块
from mypackage import module1
module1.function1()

## 导入子包中的模块
from mypackage.subpackage import module3
module3.function3()
10.5.3.2 导入包中特定内容
from mypackage.module1 import function1
function1()
10.5.4 相对导入与绝对导入
10.5.4.1 绝对导入

从项目的顶级包开始导入:

from package_name.module_name import function_name
10.5.4.2 相对导入

使用点号表示相对位置:

  • .module_name:当前包中的模块
  • ..module_name:父包中的模块
  • ...module_name:祖父包中的模块
## 在mypackage.subpackage.module3中导入同级模块
from . import another_module  # 导入同级模块

## 导入父包中的模块
from .. import module1  # 导入父包中的模块

## 导入父包中模块的特定函数
from ..module2 import function2

注意:相对导入只能在包内使用,不能在顶级模块中使用。相对导入基于当前模块的 __name__ 属性,而直接运行的脚本的 __name__ 总是 '__main__'

10.6 高级应用技巧

10.6.1 动态导入

在运行时根据条件动态导入模块:

## 方法1:使用__import__
def import_module_by_name(module_name):
    return __import__(module_name)

## 方法2:使用importlib(推荐)
import importlib

def get_module(module_name):
    """根据名称动态导入模块"""
    try:
        return importlib.import_module(module_name)
    except ImportError as e:
        print(f"无法导入模块 {module_name}: {e}")
        return None

## 示例:根据条件选择不同的模块
def get_database_module(db_type):
    """根据数据库类型动态选择数据库模块"""
    if db_type.lower() == 'mysql':
        return importlib.import_module('mysql.connector')
    elif db_type.lower() == 'postgresql':
        return importlib.import_module('psycopg2')
    else:
        return importlib.import_module('sqlite3')
10.6.2 延迟导入

推迟导入耗时模块,直到真正需要时才导入,可以加快程序启动速度:

def function_that_needs_numpy():
    """只在函数被调用时导入numpy"""
    import numpy as np
    return np.array([1, 2, 3])

def process_image(image_path):
    """图像处理函数,仅在需要时导入PIL"""
    # 只在需要处理图像时才导入PIL
    from PIL import Image
    
    img = Image.open(image_path)
    # 处理图像...
    return img
10.6.3 使用 __slots__ 优化内存

在模块级别的类中使用 __slots__ 限制属性,提高内存效率:

class DataPoint:
    """使用__slots__优化内存的数据点类"""
    __slots__ = ['x', 'y', 'z']  # 只允许这些属性
    
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        
    def distance_from_origin(self):
        """计算到原点的距离"""
        return (self.x**2 + self.y**2 + self.z**2) ** 0.5

10.7 包的发布与安装

创建自己的包并发布到 PyPI:

10.7.1 创建 setup.py 文件
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="0.1.0",
    author="Your Name",
    author_email="your.email@example.com",
    description="A short description of the package",
    long_description=open("README.md").read(),
    long_description_content_type="text/markdown",
    url="https://github.com/yourusername/mypackage",
    packages=find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.6',
    install_requires=[
        'dependency1>=1.0.0',
        'dependency2>=2.0.0',
    ],
)
10.7.2 打包与上传
## 安装打包工具
pip install --upgrade setuptools wheel twine

## 构建分发包
python setup.py sdist bdist_wheel

## 上传到PyPI
twine upload dist/*
10.7.3 安装包
pip install mypackage

10.8 PyCharm 中的包管理

PyCharm 提供了强大的图形界面来管理 Python 包,让包的安装和管理变得简单高效。

10.8.1 使用 Python Packages 工具

在 PyCharm 中管理包的最简单方法是使用内置的 Python Packages 工具:

PyCharm Python Packages 界面

  1. 点击底部的 Python Packages 标签打开包管理器
  2. 在搜索框中输入要安装的包名称
  3. 点击包右侧的 Install 按钮安装包
  4. 已安装的包会显示在 Installed 标签下,可以查看版本并进行升级或卸载操作
10.8.2 更改 PyCharm 的 pip 源

默认的 PyPI 源在国内访问可能较慢,可以更换为国内镜像以提高下载速度:

PyCharm 更改 pip 源

  1. 在 Python Packages 界面点击左上角的齿轮图标
  2. 点击 “+” 按钮添加新的软件源
  3. 输入国内镜像源地址,例如:
    • 阿里云:https://mirrors.aliyun.com/pypi/simple/
    • 清华:https://pypi.tuna.tsinghua.edu.cn/simple/
    • 中国科技大学:https://pypi.mirrors.ustc.edu.cn/simple/
    • 豆瓣:http://pypi.douban.com/simple/
10.8.3 使用 Project Interpreter 管理包

除了 Python Packages 工具外,还可以通过 Project Interpreter 设置管理包:

PyCharm Project Interpreter

  1. 进入 File > Settings > Project > Python Interpreter
  2. 点击 “+” 按钮添加新包
  3. 在弹出窗口中搜索并安装需要的包
10.8.4 导出和导入项目依赖

在团队开发中,共享项目依赖非常重要。PyCharm 提供了方便的方式来管理 requirements.txt 文件:

10.8.4.1 导出依赖

推荐使用 pipreqs 工具导出仅项目使用的依赖包:

## 安装pipreqs
pip install pipreqs

## 在项目根目录执行
pipreqs . --encoding=utf8 --force

提示--force 参数会强制覆盖已存在的 requirements.txt 文件,--encoding=utf8 确保使用 UTF-8 编码处理文件。

10.8.4.2 导入依赖

在 PyCharm 的 Terminal 中执行:

pip install -r requirements.txt

或者指定国内镜像源:

pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
10.8.5 使用虚拟环境

PyCharm 支持多种虚拟环境管理工具,如 Virtualenv、Pipenv 和 Conda:

PyCharm 虚拟环境设置

  1. 创建新项目时选择虚拟环境类型
  2. 对于现有项目,可以在 File > Settings > Project > Python Interpreter 中配置
  3. 点击齿轮图标,选择 “Add…”,然后选择合适的虚拟环境类型
10.8.5.1 虚拟环境工具对比
工具优点缺点适用场景
Virtualenv轻量级,易于使用需要手动维护 requirements.txt简单项目
Pipenv自动管理依赖,有锁文件比 Virtualenv 慢中型团队项目
Conda同时管理 Python 版本和包占用空间大数据科学项目

10.9 模块开发最佳实践

10.9.1 模块组织
  • 相关功能放在同一个模块中
  • 单个模块不要过大,保持在 1000 行以内
  • 使用子模块和子包组织复杂功能
  • 使用清晰的命名约定,避免与标准库和流行第三方库冲突
10.9.2 导入顺序

遵循 PEP8 建议的导入顺序:

## 1. 标准库导入
import os
import sys

## 2. 相关第三方库导入
import numpy as np
import pandas as pd

## 3. 本地应用/库特定导入
from mypackage import module1
from .utils import helper_function
10.9.3 文档化模块和包

为模块、类和函数编写清晰的文档字符串:

"""
模块名称: data_processing

这个模块提供了处理数据的实用函数。

主要功能:
    * 数据清洗
    * 特征工程
    * 数据转换
"""

def clean_data(df):
    """
    清洗输入的DataFrame。
    
    参数:
        df (pandas.DataFrame): 需要清洗的数据帧
        
    返回:
        pandas.DataFrame: 清洗后的数据帧
    
    示例:
        >>> import pandas as pd
        >>> df = pd.DataFrame({'A': [1, None, 3], 'B': [4, 5, None]})
        >>> clean_data(df)
           A    B
        0  1.0  4.0
        2  3.0  5.0
    """
    # 函数实现...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值