PandHedge

Python 基础知识

2025-09-09
PandHedge

Python

精通Python编程从零基础到实战

8.1 Python初阶:基础语法

Python简介&环境搭建&以及环境变量原理

Python常用IDE以及免安装开发环境(如Anaconda、PyCharm等)

Python基础语法和基本数据类型(Number、String)

一、基础语法
1. **注释**:用于解释代码,不被执行

单行注释:以 `#` 开头
 # 这是一条单行注释
 print("Hello World")  # 这也是注释,在代码后面

多行注释:用三个单引号 ''' 或三个双引号 """ 包裹

'''
这是多行注释第一行
这是多行注释第二行
'''

缩进:Python 用缩进来区分代码块,通常使用 4 个空格

if 5 > 2:
   print("5 大于 2")  # 缩进部分属于 if 代码块
  2. **变量**:用于存储数据,无需声明类型,直接赋值
x = 10  # 整数
name = "Alice"  # 字符串
  3. **关键字**:Python 保留的特殊单词,不能作为变量名,如 `if`、`else`、`for`、`while` 等
二、基本数据类型:Number(数字)

包含整数(int)、浮点数(float)、复数(complex)

  1. **整数(int)**:没有小数部分的数字
a = 10
b = 5
c = 0
  2. **浮点数(float)**:带小数部分的数字
x = 3.14
y = 0.5
z = 2.0  # 虽然值是整数,但类型是 float
  3. **复数(complex)**:由实部和虚部组成,虚部用 `j` 表示
m = 3 + 4j
n = 5j  # 实部为 0 的复数
  4. **数字运算**
# 基本运算
print(5 + 3)  # 加法,输出 8
print(10  4)  # 减法,输出 6
print(3 * 7)  # 乘法,输出 21
print(15 / 4)  # 除法,输出 3.75
print(15 // 4)  # 整除,输出 3
print(15 % 4)  # 取余,输出 3
print(2 **3)  # 幂运算,输出 8(2的3次方)
三、基本数据类型:String(字符串)

由字符组成的序列,用单引号 ' 或双引号 " 包裹

1.字符串定义

s1 = 'Hello'
s2 = "World"
s3 = ''' 多行字符串 ''' # 三个单引号可定义多行字符串

2.字符串访问 通过索引访问单个字符(索引从 0 开始)

s = "Python"
print(s[0])  # 输出 'P'
print(s[1])  # 输出 'n'(1 表示最后一个字符)
切片:获取子字符串 `[起始索引:结束索引:步长]`
s = "Hello World"
print(s[0:5])  # 输出 'Hello'(从索引0到4,不包含5)
print(s[6:])  # 输出 'World'(从索引6到结尾)
print(s[::2])  # 输出 'HloWrd'(步长为2,间隔取字符)

3.字符串常用操作 s = “Hello”

拼接字符串

print (s + “World”) # 输出 ‘Hello World’

重复字符串

print (s * 2) # 输出 ‘HelloHello’

字符串长度

print (len (s)) # 输出 5

转换为大写 / 小写

print (s.upper ()) # 输出 ‘HELLO’ print (s.lower ()) # 输出 ‘hello’

替换字符

print (s.replace (‘l’, ‘x’)) # 输出 ‘Hexxo’

分割字符串

print (“a,b,c”.split (‘,’)) # 输出 [‘a’, ‘b’, ‘c’]

Python高级数据类型(Tuple、List、Set、Dict)

Python 中的高级数据类型用于存储和组织复杂的数据集合,主要包括 Tuple(元组)、List(列表)、Set(集合)和 Dict(字典)。它们各有特点,适用于不同场景。

一、List(列表)

列表是有序、可变的元素集合,元素可以重复,用方括号 [] 表示。

  1. **定义与基本操作**
# 定义列表
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4]
mixed = [1, "hello", 3.14, True]  # 可包含不同类型元素

# 访问元素(通过索引)
print(fruits[0])  # 输出 'apple'
print(fruits[1])  # 输出 'cherry'(最后一个元素)

# 修改元素
fruits[1] = "orange"
print(fruits)  # 输出 ['apple', 'orange', 'cherry']
  2. **常用方法**
fruits = ["apple", "banana", "cherry"]

fruits.append("date")  # 添加元素到末尾
print(fruits)  # ['apple', 'banana', 'cherry', 'date']

fruits.insert(1, "blueberry")  # 在指定位置插入
print(fruits)  # ['apple', 'blueberry', 'banana', 'cherry', 'date']

fruits.remove("banana")  # 删除指定元素
print(fruits)  # ['apple', 'blueberry', 'cherry', 'date']

popped = fruits.pop()  # 删除并返回最后一个元素
print(popped)  # 'date'

print(len(fruits))  # 输出 3(列表长度)
二、Tuple(元组)

元组是有序、不可变的元素集合,元素可以重复,用圆括号 () 表示。

  1. **定义与特点**
# 定义元组
colors = ("red", "green", "blue")
single = (5,)  # 单个元素的元组需加逗号

# 访问元素(与列表相同)
print(colors[1])  # 输出 'green'

# 元组不可修改(会报错)
# colors[0] = "yellow"  # TypeError
  2. **常用操作**
numbers = (1, 2, 3, 4, 3)

print(len(numbers))  # 输出 5(长度)
print(numbers.count(3))  # 输出 2(元素出现次数)
print(numbers.index(2))  # 输出 1(元素首次出现的索引)

适用场景:存储不可变的数据(如坐标、配置信息),或作为字典的键。

三、Set(集合)

集合是无序、不可重复的元素集合,用大括号 {} 表示,或通过 set() 创建。

  1. **定义与去重特性**
# 定义集合
fruits = {"apple", "banana", "cherry", "apple"}  # 自动去重
print(fruits)  # 输出 {'apple', 'banana', 'cherry'}(顺序不固定)

# 从列表创建集合
numbers = set([1, 2, 3, 2, 4])
print(numbers)  # {1, 2, 3, 4}
  2. **常用方法与运算**
a = {1, 2, 3}
b = {3, 4, 5}

a.add(6)  # 添加元素
print(a)  # {1, 2, 3, 6}

a.remove(6)  # 删除元素

print(a.union(b))  # 并集 {1, 2, 3, 4, 5}
print(a.intersection(b))  # 交集 {3}
print(a.difference(b))  # 差集 {1, 2}

适用场景:去重、集合运算(如交集、并集)。

四、Dict(字典)

字典是键值对(keyvalue)的无序集合,键唯一且不可变,用 {key: value} 表示。

  1. **定义与访问**
# 定义字典
person = {
   "name": "Alice",
   "age": 30,
   "is_student": False
}

# 访问值(通过键)
print(person["name"])  # 输出 'Alice'
print(person.get("age"))  # 输出 30(get方法更安全,键不存在时返回None)
  2. **常用操作**
person = {"name": "Alice", "age": 30}

person["gender"] = "female"  # 添加新键值对
print(person)  # {'name': 'Alice', 'age': 30, 'gender': 'female'}

person["age"] = 31  # 修改值

del person["gender"]  # 删除键值对

print(person.keys())  # 所有键:dict_keys(['name', 'age'])
print(person.values())  # 所有值:dict_values(['Alice', 31])
print(person.items())  # 所有键值对:dict_items([('name', 'Alice'), ('age', 31)])

适用场景:存储关联数据(如用户信息、配置参数),通过键快速查找值。

总结对比
类型 有序性 可变性 元素重复性 定义符号
         
List 允许 []
Tuple 允许 ()
Set 不允许 {}
Dict 3.7 + 是 键不允许 {key:val}

根据数据特性选择合适的类型,能让代码更高效、易读。例如:列表适合存储有序序列,字典适合存储键值关联数据,集合适合去重操作。

Python函数和模块基础知识以及高级应用

Python 的函数和模块是构建程序的核心组件,从基础到高级应用涵盖了丰富的编程思想和实践技巧。以下是系统的知识梳理:

一、函数基础

  1. 函数定义与调用

函数通过def关键字定义,使用缩进区分代码块:

def greet(name):
    return f"Hello, {name}!"

print(greet("Python"))  # 调用函数
  1. 参数类型
  • 位置参数:按顺序传递的参数

位置参数是指按参数定义顺序传递的参数,必须按照函数定义时的参数顺序依次提供,否则会导致逻辑错误。

特点:

  • 必须按顺序传递,数量需与函数定义一致(除非有默认值)

  • 调用时直接写参数值,不指定参数名

  • 关键字参数:指定参数名传递,可改变顺序

关键字参数是指通过 “参数名 = 值” 形式传递的参数,可以不按定义顺序传递,因为参数名明确指定了对应的参数。

特点:

  • 传递时需指定参数名(参数名=值
  • 顺序可以任意调整
  • 通常用于可选参数或默认参数

位置参数和关键字参数可以混合使用,但必须遵循 “位置参数在前,关键字参数在后” 的原则。

  • 默认参数

    :定义时指定默认值,调用时可省略

    def power(base, exp=2):
        return base **exp
    
  • 可变参数:接受任意数量参数,用*表示元组,**表示字典

def sum_all(*args, **kwargs):
    total = sum(args)
    for k, v in kwargs.items():
        total += v
    return total

*args**kwargs 的特殊作用

  • *args:收集多余的位置参数,打包成元组(用于可变数量的位置参数)
  • **kwargs:收集多余的关键字参数,打包成字典(用于可变数量的关键字参数)
  1. 返回值
  • return返回结果,可返回多个值(实际是元组)
  • return时默认返回None

二、模块基础

  1. 模块定义与导入
  • 一个.py文件就是一个模块

  • 导入方式:

    import module_name          # 导入整个模块
    from module_name import func  # 导入特定成员
    from module_name import *    # 导入所有成员(不推荐)
    import module_name as mn     # 别名导入
    
  1. 包(Packages

在 Python 中,包(Packages)是一种组织模块的层级结构,用于管理多个相关模块,使代码结构更清晰、可维护性更高。

  • 定义:包是包含多个模块(.py文件)的目录,且必须包含一个特殊文件 __init__.py(Python 3.3+ 后允许省略,但仍推荐保留)
  • 作用:避免模块名冲突,实现代码的模块化和层级化管理
  • 结构:包可以嵌套,形成多层级结构
  • 一个典型的包结构如下:
mypackage/
├── __init__.py
├── module1.py
├── module2.py
└── subpackage/
    ├── __init__.py
    └── module3.py

__init__.py 文件的作用

__init__.py 文件是包的标识,主要功能包括:

  1. 初始化包:当包被导入时,会自动执行该文件中的代码
  2. 控制导出内容:通过 __all__ 变量指定 from package import * 时导入的模块列表
# mypackage/__init__.py
__all__ = ['module1', 'module2']  # 定义可导出的模块

3.简化导入路径:在 __init__.py 中导入子模块,简化外部导入

# mypackage/__init__.py
from . import module1  # 这样外部可以直接 import mypackage.module1
  1. 包级别的变量和函数:定义包级别的常量、函数等

在 Python 中,定义包级别的变量和函数可以让整个包内的模块共享数据或功能,也能对外提供统一的接口。这些变量和函数通常定义在包的 __init__.py 文件中,因为该文件会在包被导入时自动执行,是包的初始化入口。

一、定义包级别的变量

包级变量可以是常量、配置信息、版本号等,供整个包内的模块或外部导入者使用。

示例结构

mypackage/
├── __init__.py
├── module1.py
└── module2.py

定义方式:在 __init__.py 中直接声明变量:

# mypackage/__init__.py

# 包版本号
__version__ = "1.0.2"

# 作者信息
__author__ = "Alice"

# 包级常量(如配置参数)
MAX_CONNECTIONS = 10
DEFAULT_TIMEOUT = 30

# 包级变量(可被修改)
global_counter = 0

使用方式

  1. 外部导入使用

    import mypackage
    print(mypackage.__version__)  # 输出:1.0.2
    print(mypackage.MAX_CONNECTIONS)  # 输出:10
    
  2. 包内部模块使用(如 module1.py):

    # mypackage/module1.py
    from . import __version__, MAX_CONNECTIONS
       
    def get_version():
        return __version__
       
    def check_limit():
        return MAX_CONNECTIONS > 5
    

二、定义包级别的函数

包级函数是为整个包提供通用功能的函数,例如工具函数、初始化函数等,同样定义在 __init__.py 中。

定义方式:在 __init__.py 中定义函数:

# mypackage/__init__.py

# 包级函数:初始化包配置
def init_config(timeout=None):
    global DEFAULT_TIMEOUT
    if timeout:
        DEFAULT_TIMEOUT = timeout
    print(f"配置初始化完成,超时时间:{DEFAULT_TIMEOUT}")

# 包级工具函数
def format_message(msg):
    return f"[{__version__}] {msg}"

使用方式

  1. 外部调用

    import mypackage
    mypackage.init_config(60)  # 调用包级函数修改配置
    print(mypackage.format_message("操作完成"))  # 输出:[1.0.2] 操作完成
    
  2. 内部模块调用(如 module2.py):

    # mypackage/module2.py
    from . import format_message
       
    def process():
        message = format_message("处理中")
        print(message)
    

三、高级技巧:控制包级成员的可见性

通过 __all__ 变量可以控制 from mypackage import * 时对外暴露的成员,避免内部实现细节被意外导入:

# mypackage/__init__.py

# 定义对外暴露的成员(变量、函数、模块)
__all__ = [
    "__version__", 
    "MAX_CONNECTIONS", 
    "init_config", 
    "format_message"
]

# 私有变量(不希望被外部导入)
_internal_secret = "hidden"

# 私有函数
def _helper():
    return _internal_secret

此时使用 from mypackage import * 只会导入 __all__ 中列出的成员,保护内部实现。

四、注意事项

  1. 全局状态管理:包级变量是全局的,修改后会影响整个程序中使用该包的部分,需谨慎操作(尤其在多线程环境)。
  2. 初始化逻辑:复杂的初始化代码(如读取配置文件、连接数据库)可以放在包级函数中,而非直接写在 __init__.py 顶层,避免导入包时就执行耗时操作。
  3. 相对导入:包内部模块引用包级成员时,需使用相对导入(from . import xxx),避免绝对路径导致的可移植性问题。
  4. 命名规范
    • 公开的包级变量 / 函数使用常规命名(如 MAX_SIZEsetup())。
    • 内部私有成员以单下划线开头(如 _internal_var),表示不建议外部使用。

通过这种方式,能够让包的结构更清晰,对外提供统一的接口,同时方便内部模块共享资源,是大型 Python 项目组织代码的常用技巧。

三、包的导入方式

导入包内模块:from package.subpackage import module

1. 导入整个包

import mypackage
# 使用:mypackage.module1.function()

2. 导入包中的模块

from mypackage import module1
# 或
import mypackage.module1 as m1

3. 导入模块中的特定成员

from mypackage.module1 import function1, ClassA

4. 导入子包

from mypackage.subpackage import module3
# 或
import mypackage.subpackage.module3

5. 通配符导入(需 __all__ 支持)

from mypackage import *  # 仅导入 __all__ 中指定的模块

四、相对导入与绝对导入

在包内部模块之间导入时,可以使用:

  1. 绝对导入:从顶层包开始的完整路径

    # 在 subpackage/module3.py 中
    from mypackage.module1 import function1
    
  2. 相对导入:使用 . 表示当前目录,.. 表示父目录(仅在包内部使用)

    # 在 subpackage/module3.py 中
    from ..module1 import function1  # 导入父包中的 module1
    from . import some_module        # 导入同目录下的模块
    

五、包的高级应用

1. 命名空间包

用于将多个目录合并为一个逻辑包,无需 __init__.py,适合大型项目的代码拆分:

project/
├── part1/
│   └── mypackage/
│       └── moduleA.py
└── part2/
    └── mypackage/
        └── moduleB.py

通过设置 PYTHONPATH 包含 part1part2,可以将两个 mypackage 视为一个包。

2. 条件导入

根据不同环境导入不同模块,提高包的兼容性:

# mypackage/__init__.py
import sys
if sys.version_info >= (3, 10):
    from . import new_features
else:
    from . import legacy_support

3. 包的版本管理

__init__.py 中定义版本信息,方便用户查看:

# mypackage/__init__.py
__version__ = '1.0.0'
__author__ = 'Your Name'

用户可通过以下方式获取:

import mypackage
print(mypackage.__version__)  # 输出:1.0.0

4. 包的分发

将包打包为可安装的发行版,需创建 setup.pypyproject.toml,例如:

# setup.py
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="1.0.0",
    packages=find_packages(),
)

六、最佳实践

  1. 始终在包目录中添加 __init__.py,明确标识包并控制导出内容
  2. 避免使用 from package import *,除非是有意设计的公共 API
  3. 包内部优先使用相对导入,外部使用绝对导入
  4. 大型项目采用分层结构,合理划分子包
  5. 通过 __all__ 明确指定公共接口,隐藏内部实现细节

掌握包的使用可以帮助你构建结构清晰、易于维护的大型 Python 项目,是从脚本开发转向工程化开发的重要一步。

  1. 模块搜索路径

Python 按以下顺序查找模块:

  1. 当前目录
  2. 环境变量PYTHONPATH指定的路径
  3. Python 安装目录的标准库路径

可通过sys.path查看和修改搜索路径。

三、函数高级应用

  1. 匿名函数(lambda)

简洁的单行函数,适用于简单逻辑:

add = lambda x, y: x + y
print(add(3, 5))  # 输出:8

​ 在 Python 中,lambda 是用于创建匿名函数的关键字,它允许你快速定义简单的、单行的函数,而无需使用 def 关键字进行完整的函数定义。

lambda 的特点

  1. 匿名性:lambda 函数没有正式名称,通常通过赋值给变量来使用
  2. 简洁性:只能包含一个表达式,适合简单逻辑
  3. 单行性:通常写在一行内
  4. 表达式限制:只能是单个表达式,不能包含复杂结构(如循环、条件语句只能用三元表达式)

与普通函数的对比

上面的 lambda 表达式等价于:

def add(x, y):
    return x + y

常见用途:

  1. 作为简单函数的快捷方式,尤其是在需要临时函数的场景

  2. 与高阶函数(如 map()filter()sorted())配合使用:

    numbers = [(1, 3), (4, 1), (2, 5)]
    # 按元组第二个元素排序
    sorted_numbers = sorted(numbers, key=lambda x: x[1])
    
  3. 作为回调函数或简单的处理逻辑

lambda 适合处理简单的运算或逻辑,对于复杂功能,仍建议使用 def 定义常规函数以保证可读性。

2. 函数式编程工具
  • Python 提供了一系列函数式编程工具,这些工具允许你以函数为核心进行编程,强调使用纯函数、不可变数据和函数组合。以下是主要的函数式编程工具及其应用:

一、核心内置函数

1. map(func, iterable)

  • 功能:将函数 func 应用于可迭代对象 iterable 的每个元素,返回一个迭代器。

    numbers = [1, 2, 3, 4]
    squared = map(lambda x: x** 2, numbers)
    print(list(squared))  # 输出:[1, 4, 9, 16]
    

2. filter(func, iterable)

  • 功能:通过函数 func 过滤可迭代对象,保留 func 返回 True 的元素,返回迭代器。

    numbers = [1, 2, 3, 4, 5, 6]
    even_numbers = filter(lambda x: x % 2 == 0, numbers)
    print(list(even_numbers))  # 输出:[2, 4, 6]
    

3. reduce(func, iterable[, initial])

  • 功能:从左到右累积应用函数 func 到可迭代对象的元素,最终得到单个结果(需从 functools 导入)。

    from functools import reduce
    numbers = [1, 2, 3, 4]
    product = reduce(lambda x, y: x * y, numbers)  # 1*2*3*4
    print(product)  # 输出:24
    

二、functools 模块工具

1. functools.partial

  • 功能:固定函数的部分参数,生成一个新的简化函数(函数柯里化)。

  • from functools import partial
        
    def power(base, exp):
        return base **exp
        
    square = partial(power, exp=2)  # 固定指数为2
    print(square(5))  # 输出:25(等价于 power(5, 2))
    

2. 装饰器(Decorators)

动态增强函数功能,不修改原函数代码:

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(2, 3)  # 会自动添加日志功能

@functools.wraps(func):保留被装饰函数的元信息(如名称、文档字符串)。

from functools import wraps

def decorator(func):
    @wraps(func)  # 保留原函数信息
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator
def add(a, b):
    """返回两数之和"""
    return a + b

print(add.__name__)  # 输出:add(而非 wrapper)
print(add.__doc__)   # 输出:返回两数之和
  • @functools.lru_cache(maxsize):缓存函数调用结果,优化重复计算(如递归、查询)。

    from functools import lru_cache
      
    @lru_cache(maxsize=None)  # 无限制缓存
    def fibonacci(n):
        if n <= 1:
            return n
        return fibonacci(n-1) + fibonacci(n-2)
      
    print(fibonacci(100))  # 快速计算,重复调用直接取缓存
    
  • @functools.singledispatch:实现函数的单分派泛型(根据第一个参数类型动态选择实现)。

    from functools import singledispatch
      
    @singledispatch
    def process(data):
        raise NotImplementedError("未支持的数据类型")
      
    @process.register(int)
    def _(data):
        return f"整数: {data * 2}"
      
    @process.register(str)
    def _(data):
        return f"字符串: {data.upper()}"
      
    print(process(10))    # 输出:整数: 20
    print(process("hello"))  # 输出:字符串: HELLO
    

3. 闭包(Closures)

内层函数引用外层函数变量,形成封装的作用域:

def outer(x):
    def inner(y):
        return x + y
    return inner

add5 = outer(5)
print(add5(3))  # 输出:8

4. 迭代器与生成器函数

itertools 模块

提供了高效处理迭代器的工具函数,如:

  • itertools.chain:拼接多个可迭代对象

    import itertools
    a = [1, 2]
    b = [3, 4]
    combined = itertools.chain(a, b)
    print(list(combined))  # 输出:[1, 2, 3, 4]
    
  • itertools.islice:对迭代器进行切片

与列表的 [start:stop:step] 切片类似,但 islice 直接作用于迭代器(无需先转换为列表),更节省内存(尤其对大迭代器)。 注意:迭代器是一次性的,islice 会消耗迭代器元素。

import itertools

# 生成一个无限迭代器(0,1,2,3,...)
count = itertools.count(start=0, step=1)

# 从迭代器中取第2到第6个元素(左闭右开),步长为2
sliced = itertools.islice(count, 2, 6, 2)
print(list(sliced))  # 输出:[2, 4]

###

  • itertools.groupby:按 key 分组

将迭代器中相邻的、具有相同 key 的元素分组。 注意:分组前需先按 key 排序,否则相同 key 可能被分到不同组。

import itertools

# 待分组的列表(先按key排序)
data = [('a', 1), ('a', 2), ('b', 3), ('b', 4), ('a', 5)]
data_sorted = sorted(data, key=lambda x: x[0])  # 按第一个元素排序

# 按第一个元素分组
groups = itertools.groupby(data_sorted, key=lambda x: x[0])

# 遍历分组结果
for key, group in groups:
    print(f"key: {key}, 元素: {list(group)}")
# 输出:
# key: a, 元素: [('a', 1), ('a', 2)]
# key: b, 元素: [('b', 3), ('b', 4)]
# key: a, 元素: [('a', 5)]  # 未排序的话,这里会被单独分组
  • itertools.product:计算笛卡尔积

生成多个可迭代对象的所有可能组合(类似嵌套循环的结果)。

import itertools

a = [1, 2]
b = ['x', 'y']

# 计算a和b的笛卡尔积
product_result = itertools.product(a, b)
print(list(product_result))  # 输出:[(1, 'x'), (1, 'y'), (2, 'x'), (2, 'y')]

# 可指定repeat参数,计算自身的多次笛卡尔积
c = [0, 1]
product_self = itertools.product(c, repeat=2)  # 等价于product(c, c)
print(list(product_self))  # 输出:[(0, 0), (0, 1), (1, 0), (1, 1)]

这些函数返回的都是迭代器,可直接用于循环或转换为列表,适合处理大型数据集时减少内存占用。

2. 生成器表达式

轻量级迭代器,语法为 (expr for item in iterable),适合惰性计算:

numbers = (x** 2 for x in range(10))  # 生成器表达式
print(next(numbers))  # 输出:0
print(next(numbers))  # 输出:1

使用yield返回迭代器,节省内存:

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for num in fibonacci(5):
    print(num)  # 0, 1, 1, 2, 3
yield 的作用

yield 是 Python 中用于定义生成器函数的关键字,它的作用是:

  • 暂停函数执行,并返回一个值(类似 return)。
  • 下次调用生成器时,从上次暂停的位置继续执行(而不是从头开始)。

生成器的核心优势是惰性计算:不会一次性生成所有结果,而是按需产生,节省内存(尤其适合处理大量数据或无限序列)。

四、函数式编程风格特点

  1. 纯函数优先:函数输出仅由输入决定,无副作用(不修改外部状态)。

  2. 不可变数据:避免修改原有数据,而是返回新数据(如使用元组、冻结集合)。

  3. 函数组合

    :将复杂逻辑拆分为多个简单函数,通过组合实现功能。

    # 函数组合示例
    def add_one(x):
        return x + 1
       
    def multiply_two(x):
        return x * 2
       
    # 组合:先加1,再乘2
    def compose(f, g):
        return lambda x: f(g(x))
       
    result = compose(multiply_two, add_one)(3)  # (3+1)*2=8
    

五、适用场景

  • 数据处理与转换(如列表推导的函数式替代)
  • 并行计算(纯函数无副作用,适合并发)
  • 配置化逻辑(通过函数组合动态构建功能)
  • 简化代码(用内置函数替代循环)

这些工具的核心价值在于减少副作用、提高代码复用性,并使逻辑更简洁清晰。对于复杂场景,也可结合第三方库(如 toolzfn)增强函数式编程能力。

四、模块高级应用

1. 模块重载

使用importlib.reload()重新加载已导入的模块:

import importlib
import my_module
importlib.reload(my_module)  # 重新加载模块

2. 条件导入与延迟导入

根据环境或条件导入模块,优化启动速度:

if platform.system() == "Windows":
    import windows_module as os_module
else:
    import unix_module as os_module

# 延迟导入(仅在需要时导入)
def heavy_operation():
    import heavy_module
    heavy_module.do_work()

3. 模块私有成员

以双下划线__开头的成员为私有,对外不可见(实际是名称修饰):

# my_module.py
def __private_func():
    return "This is private"

def public_func():
    return __private_func()

4. 主模块判断

使用__name__ == "__main__"区分模块直接运行和被导入:

# 当模块直接运行时执行测试代码
if __name__ == "__main__":
    test_function()  # 仅在直接运行模块时执行

5. 分布式模块结构

大型项目中使用命名空间包、相对导入等组织代码:

# 相对导入(同一包内)
from . import module1
from .subpackage import module2

五、实用技巧

1.函数文档 :使用文档字符串"""描述函数功能,可通过help()查看 2. 类型提示 :增强代码可读性和 IDE 支持

def add(a: int, b: int) -> int:
    return a + b

3.缓存装饰器functools.lru_cache缓存函数结果,优化性能

from functools import lru_cache

@lru_cache(maxsize=None)
def factorial(n):
    return n * factorial(n-1) if n else 1

掌握这些知识可以帮助你编写更模块化、高效和可维护的 Python 代码,从简单脚本到大型应用都能应对自如。

Python运算符(算术、关系、赋值、逻辑、位、身份、三目等)

Python 提供了丰富的运算符用于各种操作,按功能可分为以下几类:

一、算术运算符

用于基本数学运算,支持整数、浮点数等数值类型。

运算符 描述 示例 结果
+ 加法 3 + 5 8
- 减法 10 - 4 6
* 乘法 3 * 4 12
/ 除法(返回浮点数) 10 / 3 3.333...
// 整除(向下取整) 10 // 3 3
% 取模(余数) 10 % 3 1
** 幂运算 2 ** 3 8

特殊用法

  • + 可用于字符串拼接:"Hello" + " World""Hello World"
  • * 可用于重复序列:[1] * 3[1, 1, 1]

二、关系运算符(比较运算符)

用于比较两个值,返回布尔值 TrueFalse

运算符 描述 示例 结果
== 等于 5 == 5 True
!= 不等于 5 != 3 True
> 大于 5 > 3 True
< 小于 5 < 3 False
>= 大于等于 5 >= 5 True
<= 小于等于 3 <= 1 False

注意

  • == 比较值是否相等,is 比较身份(见下文 “身份运算符”)
  • 支持链式比较:1 < 3 < 5True

三、赋值运算符

用于给变量赋值,可结合算术运算符简化写法。

运算符 描述 示例(等价于)
= 基础赋值 x = 5
+= 加法赋值 x += 3x = x + 3
-= 减法赋值 x -= 2x = x - 2
*= 乘法赋值 x *= 4x = x * 4
/= 除法赋值 x /= 2x = x / 2
//= 整除赋值 x //= 3x = x // 3
%= 取模赋值 x %= 5x = x % 5
**= 幂运算赋值 x **= 3x = x** 3

多变量赋值

a, b = 1, 2  # 同时给多个变量赋值
x = y = 10   # 链式赋值

四、逻辑运算符

用于组合布尔表达式,返回 TrueFalse

运算符 描述 示例 结果
and 逻辑与(全为真才真) True and False False
or 逻辑或(有一个真就真) True or False True
not 逻辑非(取反) not True False

短路特性

  • and:左侧为 False 时,右侧不执行
  • or:左侧为 True 时,右侧不执行
False and print("不会执行")  # 无输出
True or print("不会执行")   # 无输出

五、位运算符

对整数的二进制位进行操作。

| 运算符 | 描述 | 示例(二进制) | 结果(十进制) | | | | ———————————————————— | —————————— | —————————- | ————– | —- | —- | | & | 按位与(都为 1 则 1) | 0b1010 & 0b11000b1000 | 8 | | | | ` | 按位或(有 1 则 1) | 0b10100b1100→0b1110 | 14 | | | | | | ^ | 按位异或(不同则 1) | 0b1010 ^ 0b11000b0110 | 6 | | | | ~ | 按位取反(0 变 1,1 变 0) | ~0b1010-0b1011 | -11 | | | | « | 左移(高位丢弃,低位补 0) | 0b1010 « 10b10100 | 20 | | | | » | 右移(低位丢弃,高位补符号位) | 0b1010 » 10b101 | 5` | | |

应用场景

  • 权限控制(通过位标记表示不同权限)
  • 数据压缩与加密
  • 高效计算(如 x << 1 等价于 x * 2

六、身份运算符

用于判断两个对象是否为同一个(内存地址相同)。

运算符 描述 示例
is 是同一个对象 a is b
is not 不是同一个对象 a is not b

== 的区别

  • == 比较值是否相等
  • is 比较内存地址是否相同(是否为同一个对象)
a = [1, 2]
b = [1, 2]
print(a == b)  # True(值相等)
print(a is b)  # False(不同对象)

注意:小整数(-5 到 256)和短字符串在 Python 中会被缓存,可能导致 is 返回 True

x = 10
y = 10
print(x is y)  # True(缓存机制)

七、成员运算符

用于判断元素是否在序列(列表、字符串、元组等)中。

运算符 描述 示例
in 元素在序列中 3 in [1, 2, 3]True
not in 元素不在序列中 "a" not in "bc"True

八、三目运算符(条件表达式)

简化 if-else 语句,格式:表达式1 if 条件 else 表达式2

示例

x = 5
result = "正数" if x > 0 else "非正数"
print(result)  # "正数"

嵌套使用

score = 85
grade = "优秀" if score >= 90 else "良好" if score >= 80 else "及格"
print(grade)  # "良好"

九、运算符优先级(简化版)

从高到低排序(可通过括号 () 改变优先级):

  1. 幂运算 **
  2. 正负号 +x-x
  3. 算术运算符(*///% 高于 +-
  4. 位运算符(~ 高于 <<>> 高于 & 高于 ^ 高于 |
  5. 关系运算符(==!=>< 等)
  6. 身份运算符(isis not
  7. 成员运算符(innot in
  8. 逻辑非 not
  9. 逻辑与 and
  10. 逻辑或 or

示例

print(2 + 3 * 4)  # 14(乘法优先于加法)
print((2 + 3) * 4)  # 20(括号改变优先级)

掌握这些运算符及其优先级,能帮助你写出简洁、高效的代码,避免因运算顺序导致的逻辑错误。

Python流程控制之if判断

在 Python 中,if 语句是最基础的流程控制结构之一,用于根据条件判断执行不同的代码块。它允许程序根据特定条件动态选择执行路径,是实现分支逻辑的核心工具。

一、基本语法

if 语句的基本结构由 if、可选的 elif(else if)和 else 组成,语法如下:

if 条件表达式1:
    # 条件1为True时执行的代码块
    语句1
elif 条件表达式2:
    # 条件1为False且条件2为True时执行的代码块
    语句2
elif 条件表达式3:
    # 前序条件都为False且条件3为True时执行的代码块
    语句3
else:
    # 所有条件都为False时执行的代码块
    语句4

关键点

  • 条件表达式的结果必须是布尔值(TrueFalse
  • 代码块必须通过缩进(通常是 4 个空格)区分,缩进是 Python 语法的一部分
  • elifelse 都是可选的,可根据需求组合使用

二、单分支判断(仅 if)

当只需要在条件满足时执行某些操作,不满足时不做处理,可使用单独的 if 语句:

age = 18
if age >= 18:
    print("已成年,可独立办理业务")

# 输出:已成年,可独立办理业务

如果条件不成立,则代码块不执行:

age = 16
if age >= 18:
    print("已成年")  # 条件不成立,此句不执行

三、双分支判断(if-else)

当需要根据条件二选一执行时,使用 if-else 结构:

score = 85
if score >= 60:
    print("考试通过")
else:
    print("考试未通过")

# 输出:考试通过

应用场景:二选一的逻辑,如 “登录成功 / 失败”“数据存在 / 不存在” 等。

四、多分支判断(if-elif-else)

当需要判断多个条件时,使用 if-elif-else 结构,程序会依次检查条件,执行第一个满足的分支:

score = 85
if score >= 90:
    print("优秀")
elif score >= 80:
    print("良好")
elif score >= 60:
    print("及格")
else:
    print("不及格")

# 输出:良好

注意

  • 条件判断是 “短路” 的,一旦某个条件成立,后续条件不再检查
  • 条件顺序很重要,需从严格到宽松排列(如先判断 90 分以上,再判断 80 分以上)

五、条件表达式的类型

if 后的条件表达式不仅可以是比较运算,还可以是任何返回布尔值的表达式:

  1. 比较运算(最常用):

    x = 10
    if x > 5 and x < 15:  # 逻辑运算符组合条件
        print("x在5到15之间")
    
  2. 成员判断in/not in):

    fruits = ["apple", "banana"]
    if "apple" in fruits:
        print("苹果在列表中")
    
  3. 身份判断is/is not):

    a = None
    if a is None:
        print("变量a未赋值")
    
  4. 真值判断(利用 Python 中 “假值” 的特性): Python 中以下值被视为 FalseNone0""[]{}() 等空对象。

    name = ""
    if not name:  # 空字符串视为False,not后变为True
        print("请输入姓名")
    

六、嵌套 if 语句

if 语句内部可以嵌套另一个 if 语句,用于处理更复杂的多层条件判断:

age = 20
has_id = True

if age >= 18:
    print("已成年")
    if has_id:
        print("可进入网吧")
    else:
        print("请出示身份证")
else:
    print("未成年,禁止进入")

# 输出:
# 已成年
# 可进入网吧

注意:嵌套层次越多,代码可读性越差,建议尽量控制嵌套层数(通常不超过 3 层)。

七、常见用法与技巧

  1. 多条件并列: 使用逻辑运算符 and(且)、or(或)组合多个条件:

    # 判断年份是否为闰年(能被4整除且不能被100整除,或能被400整除)
    year = 2020
    if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
        print(f"{year}是闰年")
    
  2. 与三目运算符结合: 简单的 if-else 可简化为三目表达式:

    # 等价于 if-else 结构
    result = "通过" if score >= 60 else "未通过"
    
  3. 提前退出(卫语句): 对于复杂条件,可先判断 “不满足条件” 的情况并提前退出,减少嵌套:

    def calculate(a, b):
        if b == 0:  # 提前判断异常情况
            print("除数不能为0")
            return
        # 正常逻辑(无需嵌套)
        print(a / b)
    

八、常见错误

  1. 缩进错误:忘记缩进或缩进不一致会导致 IndentationError
  2. 条件后缺少冒号ifelifelse 后必须加冒号 :
  3. 误用 = 代替 ==if x = 5 是赋值而非判断,会导致语法错误
  4. 条件顺序错误:如先判断 score >= 60 再判断 score >= 90,会导致高分条件永远不触发

if 语句是 Python 中实现分支逻辑的基础,掌握其用法能让程序根据不同情况做出灵活响应。在实际开发中,合理组织条件判断的结构,能显著提高代码的可读性和可维护性。

Python流程控制之循环语句(While循环&For循环)

在 Python 中,循环语句用于重复执行一段代码,是实现 “批量处理”“迭代遍历” 等逻辑的核心工具。主要分为 while 循环(基于条件重复)和 for 循环(基于可迭代对象遍历),两者适用场景不同,但都支持灵活的流程控制(如 breakcontinue)。

一、while 循环:基于条件的重复

while 循环的核心逻辑是:只要条件表达式为 True,就持续执行循环体,直到条件变为 False 时退出。

1. 基本语法

while 条件表达式:
    # 循环体(缩进代码块)
    语句1
    语句2
    # (可选)更新条件的语句(避免死循环)

执行流程

  1. 判断 条件表达式 的值(True/False)。
  2. 若为 True,执行循环体;若为 False,退出循环,执行后续代码。
  3. 循环体执行完毕后,回到步骤 1,重新判断条件(直到条件为 False)。

2. 典型示例

示例 1:基本计数循环(从 1 到 5)

count = 1  # 初始化计数器
while count <= 5:  # 条件:count不超过5
    print(f"当前计数:{count}")
    count += 1  # 更新计数器(关键:避免死循环)

# 输出:
# 当前计数:1
# 当前计数:2
# 当前计数:3
# 当前计数:4
# 当前计数:5

示例 2:用户输入验证(直到输入正确)

password = ""
while password != "123456":  # 条件:密码不正确则重复
    password = input("请输入密码:")
print("密码正确,登录成功!")

# 执行过程:
# 若输入错误(如"abc"),会反复提示输入;直到输入"123456",退出循环。

3. 注意:避免死循环

while 循环的最大风险是死循环(条件永远为 True,循环永不退出),需确保循环体内有 “更新条件” 的逻辑:

# 错误示例:死循环(count始终为1,条件永远True)
count = 1
while count <= 5:
    print(count)
    # 缺少 count += 1,导致无限循环

# 正确示例:通过用户输入控制退出
while True:  # 条件恒为True,需手动控制退出
    user_input = input("输入'q'退出:")
    if user_input == "q":
        break  # 用break强制退出循环
    print(f"你输入的是:{user_input}")

二、for 循环:基于可迭代对象的遍历

for 循环的核心逻辑是:遍历 “可迭代对象”(如列表、字符串、元组等)中的每个元素,执行一次循环体,元素遍历完毕后自动退出。

Python 的 for 循环与其他语言(如 C/C++)的 “计数式 for 循环” 不同,更强调 “迭代遍历”,语法更简洁。

1. 基本语法

for 变量名 in 可迭代对象:
    # 循环体:变量名依次代表可迭代对象的每个元素
    语句1
    语句2

可迭代对象:指能被 for 循环遍历的对象,常见类型包括:

  • 序列:列表(list)、字符串(str)、元组(tuple
  • 集合:集合(set)、字典(dict,默认遍历键)
  • 生成器:range()、生成器表达式等

2. 典型示例

示例 1:遍历列表(批量处理元素)

fruits = ["apple", "banana", "orange"]
for fruit in fruits:  # fruit依次代表列表中的每个元素
    print(f"我喜欢吃:{fruit}")

# 输出:
# 我喜欢吃:apple
# 我喜欢吃:banana
# 我喜欢吃:orange

示例 2:遍历字符串(逐个字符)

text = "Python"
for char in text:  # char依次代表字符串的每个字符
    print(f"字符:{char}")

# 输出:
# 字符:P
# 字符:y
# 字符:t
# 字符:h
# 字符:o
# 字符:n

示例 3:遍历字典(键、值、键值对)

字典默认遍历 “键”,可通过 keys()values()items() 分别遍历键、值、键值对:

student = {"name": "Alice", "age": 18, "grade": "A"}

# 遍历键(默认)
for key in student:
    print(f"键:{key}")

# 遍历值
for value in student.values():
    print(f"值:{value}")

# 遍历键值对(用元组解包)
for key, value in student.items():
    print(f"{key}: {value}")

示例 4:range() 生成数字序列(模拟计数循环)

range() 是 Python 内置函数,生成一个 “不可见的数字序列”,常用于 for 循环的计数场景:

  • range(n):生成 0 ~ n-1 的整数(如 range(3) → 0,1,2)
  • range(start, end):生成 start ~ end-1 的整数(如 range(1,4) → 1,2,3)
  • range(start, end, step):指定步长(如 range(1,10,2) → 1,3,5,7,9)
# 示例:计算1~10的和
total = 0
for num in range(1, 11):  # 1到10(end=11,所以包含10)
    total += num
print(f"1~10的和:{total}")  # 输出:55

三、循环控制:breakcontinue

breakcontinue 是用于 “精细控制循环流程” 的关键字,可在循环体内根据条件调整执行逻辑。

1. break:强制退出循环

break立即终止当前循环,跳出循环体,执行循环后的代码(无论循环条件是否满足)。

示例:查找列表中的目标元素

numbers = [10, 20, 30, 40, 50]
target = 30

for num in numbers:
    if num == target:
        print(f"找到目标:{num}")
        break  # 找到后立即退出循环,不再遍历后续元素
    print(f"当前元素:{num}")

# 输出:
# 当前元素:10
# 当前元素:20
# 找到目标:30
# (40、50未被遍历)
  1. continue:跳过当前迭代,进入下一次

continue跳过当前循环体的剩余代码,直接回到循环开头,判断下一次迭代是否执行(不终止整个循环)。

示例:打印 1~10 中的奇数

for num in range(1, 11):
    if num % 2 == 0:  # 若为偶数,跳过后续打印
        continue
    print(f"奇数:{num}")  # 仅奇数会执行此句

# 输出:
# 奇数:1
# 奇数:3
# 奇数:5
# 奇数:7
# 奇数:9

3. breakcontinue 的区别

关键字 作用 对循环的影响
break 强制退出 终止整个循环,不再执行后续迭代
continue 跳过当前迭代 仅跳过当前次,继续下一次迭代

四、嵌套循环

循环内部可以嵌套另一个循环(whilefor 均可),用于处理 “多层结构” 的数据(如二维列表、矩阵等)。

示例:打印九九乘法表(for 嵌套)

# 外层循环控制行数(1~9)
for i in range(1, 10):
    # 内层循环控制每行的列数(1~i)
    for j in range(1, i+1):
        print(f"{j}×{i}={i*j}", end="\t")  # end="\t" 用制表符分隔
    print()  # 每行结束后换行

# 输出:
# 1×1=1	
# 1×2=2	2×2=4	
# 1×3=3	2×3=6	3×3=9	
# ...(直到9×9=81)

注意:嵌套循环的执行效率会随层数增加而降低,建议尽量控制嵌套层数(通常不超过 3 层)。

五、while 循环 vs for 循环:适用场景对比

循环类型 核心逻辑 适用场景 优点
while 基于条件重复,条件为 True 则执行 1. 循环次数不确定(如用户输入验证) 2. 需手动控制循环变量(如自定义计数逻辑) 灵活性高,可处理动态条件
for 基于可迭代对象遍历,次数由对象长度决定 1. 遍历已知序列(如列表、字符串) 2. 循环次数固定(如 range(10)) 3. 批量处理元素(如字典键值对) 语法简洁,无需手动管理循环变量,不易死循环

六、常见错误与注意事项

  1. while 循环的死循环:忘记更新循环条件(如 count 未递增),导致条件永远为 True
  2. for 循环修改可迭代对象:遍历列表时直接修改列表(如 delappend),可能导致遍历异常(建议遍历副本,如 for x in list.copy())。
  3. 缩进错误:循环体必须缩进(4 个空格),否则会报 IndentationError,或导致代码逻辑错误(如循环体外的代码被误判为循环内)。
  4. range() 的边界问题range(start, end) 包含 start 但不包含 end,需注意边界值(如 range(1,5) 是 1,2,3,4,而非 5)。

总结

  • while 循环:适合 “条件驱动” 的重复场景,需手动控制循环变量和退出条件。
  • for 循环:适合 “遍历驱动” 的场景,尤其对序列、集合等可迭代对象,语法更简洁高效。
  • 循环控制break 终止循环,continue 跳过当前迭代,可灵活调整执行逻辑。

掌握两种循环的用法和适用场景,能高效实现批量处理、数据遍历等核心需求,是编写 Python 程序的基础技能。

Python流程控制之 try-except

try-except 是 Python 中用于异常处理的核心结构,它允许程序在运行时捕获并处理错误,而不是直接崩溃。这种机制能显著提高程序的健壮性,尤其常用于处理不可预见的情况(如用户输入错误、文件操作失败等)。

一、基本语法结构

try-except 的基本框架如下:

try:
    # 可能发生异常的代码块(尝试执行)
    危险操作
except 异常类型1:
    # 当发生“异常类型1”时执行的代码
    处理方式1
except 异常类型2:
    # 当发生“异常类型2”时执行的代码
    处理方式2
else:
    # 当 try 块无异常常时执行的代码(可选)
    正常逻辑
finally:
    # 无论是否发生异常,都会执行的代码(可选)
    清理操作

执行流程

  1. 执行 try 块中的代码。
  2. 如果发生异常,立即跳转到对应的 except 块处理。
  3. 如果无异常,执行 else 块(若有)。
  4. 最后一定会执行 finally 块(若有)。

二、核心组件解析

  1. try
  • 包含可能引发异常的代码(如文件操作、网络请求、数据转换等)。
  • 一旦某行代码引发异常,后续代码不再执行,直接进入异常处理流程。
try:
    print("尝试转换数字...")
    num = int("abc")  # 这里会引发 ValueError
    print("转换成功")  # 此行不会执行(因为上一行已出错)
  1. except 块:捕获指定异常

except 用于捕获特定类型的异常,并定义处理逻辑。

  • 捕获单一异常

    try:
        num = int("abc")
    except ValueError:  # 只处理 ValueError 类型的异常
        print("转换失败:输入不是有效数字")
    
  • 捕获多个异常: 可以用元组指定多种异常类型,共用同一种处理逻辑:

    try:
        result = 10 / 0  # 可能引发 ZeroDivisionError
        # 或 num = int("abc")  # 可能引发 ValueError
    except (ZeroDivisionError, ValueError) as e:  # 用 as e 获取异常对象
        print(f"发生错误:{e}")  # 打印错误详情
    
  • 捕获所有异常(不推荐): 用 except Exception 可捕获几乎所有非系统退出的异常,但可能掩盖未知错误:

    try:
        risky_operation()
    except Exception as e:  # 捕获所有常规异常
        print(f"发生未知错误:{e}")
    

    注意:永远不要用空的 except 块(except:),这会隐藏所有错误,包括键盘中断(Ctrl+C)。

  1. else 块(可选)

try没有发生任何异常时执行,用于分离 “正常逻辑” 和 “异常处理逻辑”:

try:
    num = int(input("请输入数字:"))
except ValueError:
    print("输入错误")
else:
    # 只有转换成功时才执行
    print(f"你输入的数字是:{num}")

4. finally 块(可选)

无论是否发生异常,一定会执行的代码块,常用于资源清理(如关闭文件、释放连接等):

file = None
try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("文件不存在")
finally:
    # 确保文件无论如何都会关闭
    if file:
        file.close()
        print("文件已关闭")

现代替代方案:对于文件、网络连接等资源,更推荐用 with 语句(自动管理资源),但 finally 仍适用于其他场景。

三、异常的传递性

如果异常在当前 try-except 中未被捕获,会向上层调用栈传递,直到被捕获或导致程序终止:

def func1():
    func2()  # 调用 func2,可能传递异常

def func2():
    num = int("abc")  # 引发 ValueError

try:
    func1()  # 调用链的顶层
except ValueError:
    print("在顶层捕获到异常")  # 会执行此句

四、主动引发异常(raise

除了捕获系统自动引发的异常,也可以用 raise 主动抛出异常,用于验证输入或标记错误状态:

def withdraw(amount):
    if amount < 0:
        # 主动引发 ValueError,并附带错误信息
        raise ValueError("取款金额不能为负数")
    print(f"取款 {amount} 元成功")

try:
    withdraw(-100)
except ValueError as e:
    print(f"操作失败:{e}")  # 输出:操作失败:取款金额不能为负数

五、常见异常类型

Python 内置了多种异常类型,常见的有:

  • ValueError:值无效(如 int("abc")
  • TypeError:类型错误(如 "2" + 2
  • ZeroDivisionError:除零错误
  • FileNotFoundError:文件不存在
  • PermissionError:权限不足
  • IndexError:列表索引越界
  • KeyError:字典键不存在
  • AttributeError:对象属性不存在

可以通过官方文档查看完整的异常层级结构。

六、最佳实践

  1. 捕获具体异常:避免用 except Exception 捕获所有异常,应针对性处理已知错误。
  2. 提供有用信息:在异常处理中说明错误原因,便于调试(如 print(f"错误:{e}"))。
  3. 清理资源优先:用 finallywith 语句确保资源(文件、网络连接等)被正确释放。
  4. 不滥用异常:简单的条件判断(如 if x < 0)比 try-except 更高效,不应替代 if 使用。
  5. 避免空 except:至少打印错误信息,否则难以排查问题。

示例:综合应用

def process_data(input_str):
    try:
        # 尝试转换为整数
        num = int(input_str)
    except ValueError as e:
        print(f"数据转换失败:{e}")
        return None  # 转换失败返回 None
    else:
        # 转换成功则处理数据
        result = num * 2
        print(f"处理后的数据:{result}")
        return result
    finally:
        # 无论成功失败,都记录日志
        print(f"处理结束,输入内容:{input_str}")

# 测试
process_data("10")   # 正常情况
process_data("abc")  # 异常情况

输出:

处理后的数据:20
处理结束,输入内容:10
数据转换失败:invalid literal for int() with base 10: 'abc'
处理结束,输入内容:abc

try-except 是编写健壮 Python 程序的必备工具,它让程序在面对错误时能优雅处理,而不是直接崩溃。合理使用异常处理,能显著提升代码的可靠性和用户体验。

Python:函数和类

在 Python 中,函数(Function)类(Class) 是组织代码的核心工具,分别对应函数式编程面向对象编程两种主流范式。理解它们的设计目的、核心特性和适用场景,是掌握 Python 编程的基础。以下从整体框架到细节特性进行系统介绍:

一、整体认知:代码组织的两种核心范式

编程的本质是 “用代码解决问题”,而函数和类是两种不同的代码组织方式:

  • 函数式编程 **:用函数 **封装独立功能,通过函数调用和参数传递实现逻辑(强调 “过程和操作”)。
  • 面向对象编程(OOP:用**类 ** 封装数据和操作数据的方法,通过 “对象”(类的实例)交互实现逻辑(强调 “实体和关系”)。

举个生活例子:

  • 函数像 “工具”(如 “计算器”),输入参数(如两个数字),输出结果(如和),专注于 “做什么”。
  • 类像 “模板”(如 “汽车模板”),包含属性(如颜色、排量)和方法(如启动、刹车),实例化后的 “对象”(如一辆具体的车)既有数据又有行为,专注于 “是什么” 和 “能做什么”。

二、函数(Function):封装独立功能的代码块

函数是一段实现特定功能的可重用代码,通过函数名调用,可接收输入(参数)并返回输出(结果)。

1. 函数的基础:定义与调用

def 关键字定义函数,基本语法:

def 函数名(参数列表):
    """文档字符串(描述函数功能、参数、返回值)"""
    # 函数体(实现功能的代码)
    return 返回值  # 可选,无return则默认返回None

示例:定义一个计算面积的函数

def calculate_area(radius):
    """计算圆的面积
    参数:
        radius: 圆的半径(正数)
    返回:
        圆的面积(π×radius²)
    """
    if radius <= 0:
        return 0  # 无效半径返回0
    return 3.14 * radius** 2

# 调用函数:传入参数,获取返回值
area = calculate_area(5)
print(area)  # 输出:78.5

2. 函数的核心特性:参数与返回值

函数的灵活性主要体现在参数传递和返回值处理上。

(1)参数类型:适配不同调用场景

  • 必选参数:必须传递的参数,按位置匹配。

    def subtract(a, b):  # a和b是必选参数
        return a - b
    subtract(10, 3)  # 输出:7(a=10, b=3)
    
  • 默认参数:定义时指定默认值,调用时可省略(简化常见场景)。

    def power(base, exp=2):  # exp默认值为2(平方)
        return base **exp
    power(3)  # 省略exp,用默认值 → 3²=9
    power(3, 3)  # 传递exp=3 → 3³=27
    
  • 关键字参数:调用时用 参数名=值 传递,可打乱顺序(增强可读性)。

def greet(name, message):
    return f"{message}, {name}!"
greet(message="Hello", name="Alice")  # 顺序无关 → "Hello, Alice!"
  • **不定长参数 **:接收任意数量的参数(参数数量不确定时使用)。

  • *args:收集所有位置参数为元组。
  • **kwargs:收集所有关键字参数为字典。
def print_info(*args, **kwargs):
    print("位置参数:", args)    # 元组形式
    print("关键字参数:", kwargs)  # 字典形式

print_info(1, 2, name="Bob", age=20)
# 输出:
# 位置参数: (1, 2)
# 关键字参数: {'name': 'Bob', 'age': 20}

(2)返回值:函数执行的结果

  • 可返回单个值、多个值(自动打包为元组),或无返回值(默认返回none

    def divide(a, b):
        quotient = a // b    # 商
        remainder = a % b    # 余数
        return quotient, remainder  # 返回多个值(元组)
      
    q, r = divide(10, 3)  # 解包返回值
    print(f"商:{q},余数:{r}")  # 输出:商:3,余数:1
    

3. 函数的作用域:变量的可见范围

函数内部的变量与外部变量相互隔离,避免命名冲突:

  • **局部变量 **:函数内定义的变量,仅在函数内有效(函数执行结束后销毁)
  • **全局变量 **:函数外定义的变量,可在函数内访问(需用 global 声明才能修改)。
  • **非局部变量 **:嵌套函数中,内部函数修改外部函数的变量需用 nonlocal 声明。
x = 10  # 全局变量

def outer():
    y = 20  # 外部函数的局部变量
    def inner():
        nonlocal y  # 声明修改外部函数的变量
        y = 200
        global x    # 声明修改全局变量
        x = 100
    inner()
    print(y)  # 输出:200(被inner修改)

outer()
print(x)  # 输出:100(被inner修改)

4. 特殊函数类型:扩展函数的能力

  • 匿名函数(lambda):用 lambda 定义的单行短函数,无名称,适用于简单逻辑(作为参数传递时常用)。
# 等价于 def add(x,y): return x+y
add = lambda x, y: x + y
print(add(2, 3))  # 输出:5

# 作为sorted的排序键
students = [("Alice", 20), ("Bob", 18)]
sorted_students = sorted(students, key=lambda s: s[1])  # 按年龄排序

-**递归函数 **:函数调用自身,用于分治问题(将复杂问题分解为同类子问题)。

def factorial(n):
    if n == 1:  # 基线条件(终止递归)
        return 1
    return n * factorial(n-1)  # 递归调用(n! = n × (n-1)!)

print(factorial(5))  # 输出:120(5×4×3×2×1)
  • **生成器函数 **:用 yield 关键字返回迭代器,惰性生成数据(节省内存,适合大数据场景)。
def generate_even_numbers(n):
    for i in range(n):
        if i % 2 == 0:
            yield i  # 每次返回一个偶数,暂停执行

# 迭代生成器(逐个获取值)
for num in generate_even_numbers(10):
    print(num, end=" ")  # 输出:0 2 4 6 8
  • **高阶函数 **:接收函数作为参数,或返回函数(实现函数的灵活组合)。
def apply(func, x, y):
    """接收函数作为参数并调用"""
    return func(x, y)

# 传递lambda函数作为参数
print(apply(lambda a, b: a * b, 3, 4))  # 输出:12

三、类(Class):面向对象的核心载体

类是描述 “对象” 共同属性和行为的模板,而 “对象” 是类的具体实例。面向对象编程通过类和对象实现 “封装、继承、多态” 三大特性,更适合描述复杂实体(如 “用户”“订单”“汽车”)。

1. 类的基础:定义与实例化

class 关键字定义类,通过类创建对象(实例化):

class 类名:
    # 类属性(所有实例共享的变量)
    类变量 = 
    
    def __init__(self, 参数):  # 构造方法(初始化对象)
        # 实例属性(每个实例独有的变量,用self.xxx定义)
        self.实例变量1 = 参数1
        self.实例变量2 = 参数2
    
    def 方法名(self, 参数):  # 实例方法(操作实例的行为)
        # 方法体(可访问self.实例变量)
        return 结果

示例:定义一个 “学生” 类并创建对象

class Student:
    # 类属性:所有学生共享的学校名称
    school = "阳光中学"
    
    # 构造方法:初始化学生的姓名和年龄
    def __init__(self, name, age):
        self.name = name  # 实例属性:姓名(每个学生不同)
        self.age = age    # 实例属性:年龄(每个学生不同)
    
    # 实例方法:学生打招呼
    def greet(self):
        return f"大家好,我是{self.name},今年{self.age}岁,来自{self.school}。"

# 实例化:创建两个Student对象(具体学生)
student1 = Student("小明", 15)
student2 = Student("小红", 14)

# 访问对象的属性和方法
print(student1.name)       # 输出:小明(实例属性)
print(student1.school)     # 输出:阳光中学(类属性)
print(student1.greet())    # 输出:大家好,我是小明,今年15岁,来自阳光中学。

2. 面向对象的三大特性

(1)封装:隐藏内部细节,暴露安全接口

将数据(属性)和操作数据的方法捆绑在类中,通过 “访问控制” 保护数据(Python 用命名规范实现):

  • 公开属性 / 方法:直接访问(如 self.name)。
  • 私有属性 / 方法:用 ___ 前缀(约定不直接访问,通过公开方法操作)。
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # 私有属性(双下划线,强制隐藏)
    
    # 公开方法:安全操作私有属性
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
    
    def get_balance(self):
        return self.__balance

account = BankAccount(1000)
account.deposit(500)
print(account.get_balance())  # 输出:1500
# print(account.__balance)  # 报错:无法直接访问私有属性
(2)继承:复用代码,扩展功能

子类(派生类)继承父类(基类)的属性和方法,并可添加新属性 / 方法或重写父类方法(避免重复代码)。

# 父类:Person
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def greet(self):
        return f"我是{self.name}{self.age}岁。"

# 子类:Student(继承Person)
class Student(Person):
    def __init__(self, name, age, grade):
        # 调用父类的构造方法(复用初始化逻辑)
        super().__init__(name, age)
        self.grade = grade  # 新增属性:年级
    
    # 重写父类的greet方法(扩展功能)
    def greet(self):
        return f"我是{self.name}{self.age}岁,读{self.grade}年级。"

student = Student("小李", 13, 7)
print(student.greet())  # 输出:我是小李,13岁,读7年级。(调用重写后的方法)
(3)多态:同一接口,不同实现

不同类的对象调用同名方法时,表现出不同行为(通过方法重写实现),提高代码灵活性。

# 定义一个通用函数(接收Person或其子类对象)
def introduce(person):
    print(person.greet())  # 调用greet(),行为取决于对象类型

p = Person("老王", 40)
s = Student("小张", 16, 10)

introduce(p)  # 输出:我是老王,40岁。(调用Person的greet)
introduce(s)  # 输出:我是小张,16岁,读10年级。(调用Student的greet)

3. 类的特殊成员

  • 特殊方法(魔术方法):以 __ 开头和结尾,用于实现类的特殊行为(如初始化、字符串表示、运算符重载)。

    class Book:
        def __init__(self, title, price):
            self.title = title
            self.price = price
          
        def __str__(self):  # 打印对象时调用(友好显示)
            return f"《{self.title}》,价格:{self.price}元"
          
        def __add__(self, other):  # 重载+运算符
            return self.price + other.price
      
    book1 = Book("Python入门", 50)
    book2 = Book("Python进阶", 70)
      
    print(book1)        # 输出:《Python入门》,价格:50元(调用__str__)
    print(book1 + book2)  # 输出:120(调用__add__)
    
  • 类方法与静态方法

    • 类方法(@classmethod:操作类属性,第一个参数为 cls(代表类本身)。
    • 静态方法(@staticmethod:与类和实例无关,无默认参数(类似独立函数,只是放在类中组织)。
    class MathUtil:
        pi = 3.14  # 类属性:圆周率
          
        @classmethod
        def circle_area(cls, radius):  # 类方法:用cls访问类属性
            return cls.pi * radius **2
          
        @staticmethod
        def add(a, b):  # 静态方法:与类属性无关
            return a + b
      
    # 调用类方法(无需创建对象)
    print(MathUtil.circle_area(2))  # 输出:12.56
    # 调用静态方法
    print(MathUtil.add(2, 3))      # 输出:5
    

这段代码展示了 Python 中类的两种特殊方法:类方法(@classmethod静态方法(@staticmethod,它们与普通的实例方法(def method(self):)在功能和使用场景上有显著区别。以下是详细解析:

一、类方法(@classmethod

类方法是与类本身绑定的方法,而非与类的实例绑定。它的核心特点是:

1. 定义与参数

  • @classmethod 装饰器声明。
  • 第一个参数必须是 cls(约定俗成的名称,代表类本身,类似实例方法的 self)。
  • 通过 cls 可以访问和修改类属性(所有实例共享的属性),但不能直接访问实例属性(需通过实例对象)。

2. 代码解析(circle_area 方法)

class MathUtil:
    pi = 3.14  # 类属性(所有实例共享)
    
    @classmethod
    def circle_area(cls, radius):
        # 通过 cls 访问类属性 pi
        return cls.pi * radius **2
  • **调用方式 **:可通过类名直接调用(无需创建实例),也可通过实例调用:
# 类名直接调用(推荐)
print(MathUtil.circle_area(2))  # 输出:12.56(3.14 × 2²)

# 实例调用(也可行,但逻辑上没必要创建实例)
mu = MathUtil()
print(mu.circle_area(2))  # 输出:12.56
  • **核心作用 **:操作类属性或实现与类相关的逻辑(如创建 “替代构造函数”)。 例如,扩展一个根据直径计算面积的类方法:
@classmethod
def circle_area_by_diameter(cls, diameter):
    radius = diameter / 2
    return cls.circle_area(radius)  # 调用同类的其他类方法

MathUtil.circle_area_by_diameter(4)  # 直径4 → 半径2 → 面积12.56
二、静态方法(@staticmethod

静态方法是定义在类中的普通函数,它与类和实例都没有绑定关系。核心特点是:

1. 定义与参数

  • @staticmethod 装饰器声明。
  • 没有默认参数(既不需要 self,也不需要 cls)。
  • 不能直接访问类属性或实例属性(除非通过参数传递类或实例对象)。

2. 代码解析(add 方法)

class MathUtil:
    @staticmethod
    def add(a, b):
        # 仅依赖输入参数,与类属性/实例属性无关
        return a + b
  • **调用方式 **:同样可通过类名或实例调用,但逻辑上与类无关:
# 类名调用(推荐)
print(MathUtil.add(2, 3))  # 输出:5

# 实例调用(可行,但无意义)
mu = MathUtil()
print(mu.add(2, 3))  # 输出:5
  • **核心作用 **:封装与类相关但不依赖类状态的工具逻辑(相当于 “放在类里的普通函数”,仅为了代码组织)。 例如,添加一个判断数字是否为偶数的静态方法:
@staticmethod
def is_even(num):
    return num % 2 == 0

MathUtil.is_even(4)  # 输出:True
三、类方法 vs 静态方法 vs 实例方法

为了更清晰区分,对比三种方法的核心差异:

方法类型 装饰器 第一个参数 可访问的属性 典型用途
实例方法 self 实例属性、类属性 操作实例状态(如 student.greet()
类方法 @classmethod cls 类属性(不能直接访问实例属性) 操作类状态、替代构造函数
静态方法 @staticmethod 无(需显式传递参数) 工具函数(与类相关但无状态依赖)
四、典型应用场景

1.**类方法的典型场景 **:

  • 访问 / 修改类属性(如 circle_area 中使用 cls.pi)。

  • 实现 “替代构造函数”(通过不同参数创建实例):

    class Date:
        def __init__(self, year, month, day):
            self.year = year
            self.month = month
            self.day = day
          
        @classmethod
        def from_string(cls, date_str):  # 从字符串创建日期(如 "2023-10-01")
            year, month, day = map(int, date_str.split("-"))
            return cls(year, month, day)  # 返回新实例
      
    date = Date.from_string("2023-10-01")  # 替代构造函数
    

2.**静态方法的典型场景 **:

  • 封装与类相关的工具函数(如数学计算、数据验证)。

  • 避免创建独立的工具模块,将相关函数组织在类中:

    class StringUtil:
        @staticmethod
        def is_email(s):  # 验证邮箱格式(与类状态无关)
            return "@" in s and "." in s
    
总结
  • 类方法(@classmethod:依赖类本身(通过 cls 访问类属性),用于类级别的操作。

  • 静态方法(@staticmethod:与类和实例均无关,仅为代码组织而放在类中,功能上等价于独立函数。

选择原则:

  • 需要访问类属性 → 用类方法。
  • 不需要访问类 / 实例属性,但逻辑上属于该类 → 用静态方法。
  • 需要访问实例属性 → 用普通实例方法(self 参数)。
四、函数与类的区别与适用场景
维度 函数(Function) 类(Class)
核心定位 封装单一功能的代码块 封装数据和相关功能的模板
状态管理 无状态(每次调用独立,不保存数据) 有状态(通过实例属性保存数据)
代码组织 适合线性流程、工具逻辑 适合复杂实体、多属性多行为的场景
复用方式 通过函数调用复用 通过继承、实例化复用
典型场景 数据计算(如求和)、格式转换(如字符串处理) 实体建模(如用户、订单)、框架设计(如组件)
五、相关扩展概念
  • 模块(Module):以 .py 为扩展名的文件,用于组织函数、类和变量(通过 import 导入复用)。
  • 包(Package:包含多个模块的目录(需有 __init__.py),用于大型项目的代码分层(如 src/utils/)。
  • 装饰器(Decorator):动态给函数或类添加功能(如日志、缓存),本质是高阶函数(或类)。
总结
  • 函数是 “功能的封装”,专注于 “做什么”,适合实现独立、无状态的操作
  • **类 ** 是 “实体的模板”,专注于 “是什么” 和 “能做什么”,适合描述复杂对象及其行为,支持封装、继承、多态。

在实际开发中,函数和类并非对立关系:类中可以包含函数(方法),函数也可以操作类的对象。合理结合两者,才能写出简洁、可维护的代码。例如:用类建模 “用户”,用函数实现 “用户数据的批量导入”(工具逻辑)。

8.2 Python初阶:函数和模块

Python函数(def)以及函数的分类

在 Python 中,函数(用 def 关键字定义)是封装可重用代码的基本单元,它将一系列操作打包成一个独立模块,通过函数名调用。函数不仅能简化代码、提高复用性,还能让程序结构更清晰。

一、函数的基本定义与调用

使用 def 关键字定义函数的基本语法:

def 函数名(参数列表):
    """函数文档字符串(可选,用于描述功能)"""
    # 函数体(缩进代码块)
    执行语句
    return 返回值  # 可选,无return则默认返回None

示例:定义一个计算两数之和的函数

def add(a, b):
    """返回两个数的和"""
    return a + b

# 调用函数
result = add(3, 5)
print(result)  # 输出:8

二、函数的分类

根据函数的定义者、参数形式和功能,可分为以下几类:

  1. 按定义者分类

(1)内置函数(Built-in Functions)

Python 解释器自带的函数,无需定义可直接使用,如 print()len()max() 等。 示例

print(len([1, 2, 3]))  # 内置函数 len() 计算列表长度,输出:3

(2)自定义函数(User-defined Functions)

由开发者使用 def 关键字定义的函数,用于实现特定业务逻辑。 示例

def calculate_area(radius):
    """计算圆的面积"""
    return 3.14 * radius **2

(3)第三方库函数

来自第三方库(如 numpypandas)提供的函数,需先安装并导入库才能使用。 示例

import numpy as np
print(np.mean([1, 2, 3, 4]))  # 第三方库 numpy 的均值函数,输出:2.5
  1. 按参数形式分类

(1)无参函数

函数定义时没有参数,调用时也无需传递参数,适用于固定逻辑的操作。 示例

def say_hello():
    print("Hello, World!")

say_hello()  # 调用时无需传参,输出:Hello, World!

(2)有参函数

函数定义时包含参数,调用时需按要求传递参数,灵活性更高。 示例

def greet(name):  # 单个参数
    print(f"Hello, {name}!")

greet("Alice")  # 传递参数,输出:Hello, Alice!

(3)默认参数函数

参数在定义时指定默认值,调用时可省略该参数(使用默认值)。 示例

def power(base, exp=2):  # exp 的默认值为 2
    return base** exp

print(power(3))    # 省略 exp,使用默认值 2 → 3²=9
print(power(3, 3)) # 传递 exp=3 → 3³=27

(4)可变参数函数

通过 *args(接收任意数量的位置参数)和 **kwargs(接收任意数量的关键字参数)实现,适用于参数数量不确定的场景。 示例

def sum_all(*args, **kwargs):
    """计算所有位置参数的和,忽略关键字参数"""
    return sum(args)

print(sum_all(1, 2, 3))  # 位置参数:1+2+3=6
print(sum_all(10, 20, x=30, y=40))  # 关键字参数 x、y 被忽略,结果:30

3. 按返回值分类

(1)无返回值函数

函数体内没有 return 语句,或 return 后无值,默认返回 None,通常用于执行打印、修改全局变量等操作。 示例

def log(message):
    print(f"[LOG] {message}")  # 仅打印,无返回值

result = log("操作完成")
print(result)  # 输出:None

(2)有返回值函数

函数通过 return 语句返回一个或多个值(多个值以元组形式返回)。 示例

def divide(a, b):
    quotient = a // b
    remainder = a % b
    return quotient, remainder  # 返回多个值(元组)

q, r = divide(10, 3)  # 解包返回值
print(f"商:{q},余数:{r}")  # 输出:商:3,余数:1
  1. 特殊类型函数

(1)匿名函数(lambda 函数)

lambda 关键字定义的单行函数,无函数名,适用于简单逻辑。 示例

add = lambda x, y: x + y  # 等价于 def add(x,y): return x+y
print(add(2, 3))  # 输出:5

(2)递归函数

在函数体内调用自身的函数,适用于分治问题(如阶乘、斐波那契数列)。 示例

def factorial(n):
    if n == 1:  # 基线条件(终止递归)
        return 1
    return n * factorial(n-1)  # 递归调用自身

print(factorial(5))  # 输出:120(5×4×3×2×1)

(3)高阶函数

接受函数作为参数,或返回函数的函数,是函数式编程的核心。 示例

def apply_func(func, x, y):
    """接受函数作为参数并调用"""
    return func(x, y)

# 传递 lambda 函数作为参数
result = apply_func(lambda a, b: a * b, 3, 4)
print(result)  # 输出:12

(4)生成器函数

使用 yield 关键字返回迭代器的函数,可惰性生成数据(节省内存)。 示例

def generate_numbers(n):
    for i in range(n):
        yield i  # 每次调用返回一个值,暂停执行

# 迭代生成器
for num in generate_numbers(3):
    print(num)  # 输出:0、1、2

三、函数的核心特性

1.** 封装性 :将逻辑隐藏在函数内部,外部只需关注输入输出,无需了解实现细节。 2. 复用性 :定义一次可在多处调用,减少重复代码。 3. 作用域 **:函数内定义的变量为局部变量,仅在函数内有效;函数外的变量为全局变量(需用 global 声明才能在函数内修改)。

global_var = 10  # 全局变量

def modify_var():
    local_var = 20  # 局部变量
    global global_var  # 声明使用全局变量
    global_var = 100

modify_var()
print(global_var)  # 输出:100(已被修改)
# print(local_var)  # 报错:局部变量在函数外不可访问

总结

函数是 Python 编程的基础组件,通过 def 定义的自定义函数可根据参数形式、返回值和功能分为多种类型。合理使用不同类型的函数能显著提升代码的可读性、复用性和可维护性。在实际开发中,应根据场景选择合适的函数类型(如用生成器处理大数据,用递归解决分治问题),并遵循 “单一职责” 原则(一个函数只做一件事)。

Python函数参数分类(必选参数&关键字参数&默认参数&不定长参数)

Python 函数的参数体系灵活且强大,根据传递方式和特性可分为四大类:必选参数、默认参数、关键字参数和不定长参数。掌握这些参数类型的用法,能让函数定义更灵活,调用更清晰。

一、必选参数(Positional Arguments)

定义:必须按顺序传递的参数,函数调用时必须提供对应的值,否则会报错。 特点

  • 定义在参数列表最前面
  • 调用时数量和顺序必须与定义一致

示例

def add(a, b):  # a 和 b 都是必选参数
    return a + b

add(2, 3)  # 正确:传递 2 个参数
add(2)     # 错误:缺少必选参数 b(TypeError: add() missing 1 required positional argument: 'b')

二、默认参数(Default Arguments)

定义:在函数定义时指定默认值的参数,调用时可省略(使用默认值)。 特点

  • 定义时用 参数名=默认值 形式
  • 必须放在必选参数后面(否则语法错误)
  • 调用时若提供值,则覆盖默认值;若省略,则使用默认值

示例

def greet(name, message="Hello"):  # message 是默认参数
    return f"{message}, {name}!"

greet("Alice")          # 省略默认参数,使用默认值 → "Hello, Alice!"
greet("Bob", "Hi")      # 提供值,覆盖默认值 → "Hi, Bob!"
greet(message="Hi", name="Charlie")  # 结合关键字参数,顺序可调整 → "Hi, Charlie!"

注意:默认参数的默认值**只在函数定义时计算一次,若默认值是可变对象(如列表、字典),可能导致意外行为:

def add_item(item, lst=[]):  # 危险:默认值是可变列表
    lst.append(item)
    return lst

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2](而非预期的 [2],因为默认列表被复用)

推荐:默认值为可变对象时,用 None 初始化,在函数内创建新对象:

def add_item(item, lst=None):
    if lst is None:
        lst = []  # 每次调用创建新列表
    lst.append(item)
    return lst

三、关键字参数(Keyword Arguments)

定义:调用函数时通过 参数名=值 形式传递的参数,与定义顺序无关。 特点

  • 本质是传递方式的分类(而非定义方式),可用于任何参数类型
  • 必须放在位置参数后面(调用时)
  • 增强代码可读性,明确参数含义

示例

def describe_person(name, age, city):
    return f"{name} is {age} years old, from {city}."

# 纯位置参数(顺序必须严格对应)
describe_person("Alice", 30, "Beijing")

# 混合位置参数和关键字参数(关键字参数必须在后)
describe_person("Bob", city="Shanghai", age=25)  # 顺序无关

# 纯关键字参数
describe_person(name="Charlie", age=35, city="Guangzhou")

四、不定长参数(Variable-Length Arguments)

用于处理参数数量不确定的场景,分为两种:接收任意数量的位置参数(*args)和关键字参数(**kwargs)。

1. *args:接收不定长位置参数

定义:在参数名前加 *,用于收集所有未匹配的位置参数,打包成元组(tuple)。 特点

  • 名称 args 是约定俗成,可改为其他名称(如 *params),但推荐用 args
  • 必须放在必选参数和默认参数后面

示例

def sum_all(*args):  # 收集所有位置参数到元组 args
    return sum(args)

sum_all(1, 2, 3)        # args = (1,2,3) → 6
sum_all(10, 20, 30, 40) # args = (10,20,30,40) → 100

2. **kwargs:接收不定长关键字参数

定义:在参数名前加 **,用于收集所有未匹配的关键字参数,打包成字典(dict)。 特点

  • 名称 kwargs 是约定俗成,可改为其他名称(如 **opts),但推荐用 kwargs
  • 必须放在参数列表的最后(所有参数之后)

示例

def print_info(**kwargs):  # 收集所有关键字参数到字典 kwargs
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="Beijing")
# 输出:
# name: Alice
# age: 30
# city: Beijing

3. 组合使用:*args**kwargs

可同时使用 *args**kwargs,顺序必须是:必选参数 → 默认参数 → *args → **kwargs示例

def func(a, b=0, *args, **kwargs):
    print(f"必选参数 a: {a}")
    print(f"默认参数 b: {b}")
    print(f"不定长位置参数 args: {args}")
    print(f"不定长关键字参数 kwargs: {kwargs}")

func(1, 2, 3, 4, x=5, y=6)
# 输出:
# 必选参数 a: 1
# 默认参数 b: 2
# 不定长位置参数 args: (3, 4)
# 不定长关键字参数 kwargs: {'x': 5, 'y': 6}

五、参数定义的顺序规则

函数定义时,参数必须按以下顺序排列,否则会报语法错误: 必选参数 → 默认参数 → *args → **kwargs

正确示例

def correct_order(a, b=0, *args, **kwargs):
    pass  # 顺序正确

错误示例

def wrong_order(a, *args, b=0, **kwargs):
    pass  # 错误:默认参数 b 放在了 *args 后面

六、参数解包(Unpacking)

调用函数时,可通过 *** 解包可迭代对象,传递给不定长参数:

  • * 用于解包元组、列表等可迭代对象(作为位置参数)
  • ** 用于解包字典(作为关键字参数)

示例

def sum_numbers(a, b, c):
    return a + b + c

# 解包列表作为位置参数
nums = [1, 2, 3]
print(sum_numbers(*nums))  # 等价于 sum_numbers(1, 2, 3) → 6

# 解包字典作为关键字参数
params = {"a": 10, "b": 20, "c": 30}
print(sum_numbers(**params))  # 等价于 sum_numbers(a=10, b=20, c=30) → 60

总结

参数类型 定义方式 调用方式 作用
必选参数 def f(a, b): f(1, 2) 强制传递,保证基本输入
默认参数 def f(a, b=0): f(1)f(1, 3) 提供默认值,简化常见调用
关键字参数 (无特殊定义) f(a=1, b=2) 明确参数含义,允许调整顺序
不定长位置参数 def f(*args): f(1, 2, 3) 接收任意数量的位置参数
不定长关键字参数 def f(**kwargs): f(a=1, b=2) 接收任意数量的关键字参数

灵活运用这些参数类型,能让函数既满足固定逻辑需求,又能适应多变的调用场景,写出更简洁、通用的代码。

Python函数的嵌套、函数递归、匿名函数

Python 中函数的嵌套、递归和匿名函数是函数式编程的重要特性,它们分别解决了代码封装、复杂问题分解和简单逻辑简化的需求。以下是详细解析:

一、函数的嵌套(Nested Functions)

函数的嵌套指在一个函数(外部函数)内部定义另一个函数(内部函数)。内部函数只能在外部函数的作用域内被调用,外部无法直接访问,从而实现逻辑封装和作用域隔离。

1. 基本语法
def outer_function(outer_param):
    # 外部函数的变量/逻辑
    outer_var = "外部变量"
    
    # 内部函数定义(嵌套在外部函数内)
    def inner_function(inner_param):
        # 内部函数可访问外部函数的变量和参数
        return f"内部函数:{inner_param},外部参数:{outer_param},外部变量:{outer_var}"
    
    # 外部函数可以调用内部函数,或返回内部函数
    return inner_function  # 返回内部函数对象
2. 核心特性
  • 作用域隔离:内部函数只能在外部函数内部被调用,外部无法直接访问(保护内部逻辑)。

    # 错误示例:外部直接调用内部函数
    inner_function(1)  # NameError: name 'inner_function' is not defined
    
  • 访问外部变量:内部函数可以访问外部函数的参数和局部变量(但默认不能修改,需用 nonlocal 声明)。

    def outer():
        x = 10
        def inner():
            nonlocal x  # 声明修改外部函数的变量
            x += 5
            print(x)
        inner()  # 调用内部函数
        print(x)  # 外部变量已被修改
      
    outer()  # 输出:15  15
    
  • 返回内部函数:外部函数可以返回内部函数,使内部函数在外部被调用(形成 “闭包”)。

    # 调用外部函数,获取内部函数对象
    inner = outer_function("外部参数值")
    # 调用返回的内部函数
    print(inner("内部参数值"))  
    # 输出:内部函数:内部参数值,外部参数:外部参数值,外部变量:外部变量
    

示例:

def outer_function(outer_param):
    # 外部函数的变量/逻辑
    outer_var = "外部变量"
    
    # 内部函数定义(嵌套在外部函数内)
    def inner_function(inner_param):
        # 内部函数可访问外部函数的变量和参数
        return f"内部函数:{inner_param},外部参数:{outer_param},外部变量:{outer_var}"
    
    # 外部函数可以调用内部函数,或返回内部函数
    return inner_function  # 返回内部函数对象

def outer():
    x = 10
    def inner():
        nonlocal x  # 声明修改外部函数的变量
        x += 5
        print(x)
    inner()  # 调用内部函数
    print(x)  # 外部变量已被修改

outer()  # 输出:15  15

# 调用外部函数,获取内部函数对象
inner = outer_function("外部参数值")
# 调用返回的内部函数
print(inner("内部参数值"))  
# 输出:内部函数:内部参数值,外部参数:外部参数值,外部变量:外部变量

inner = outer_function("123")

print(inner(321))

输出结果:

15
15
内部函数:内部参数值,外部参数:外部参数值,外部变量:外部变量
内部函数:321,外部参数:123,外部变量:外部变量
3. 典型应用:闭包(Closure)

当外部函数返回内部函数,且内部函数引用了外部函数的变量时,就形成了闭包。闭包可以 “记住” 外部函数的变量状态,常用于装饰器、工厂函数等场景。

示例:计数器工厂(通过闭包保存计数状态

def make_counter(initial=0):
    count = initial  # 外部函数的变量,被内部函数引用
    
    def counter():
        nonlocal count
        count += 1
        return count
    
    return counter  # 返回闭包

# 创建两个独立的计数器(状态隔离)
counter1 = make_counter()
counter2 = make_counter(10)

print(counter1())  # 1(counter1 的 count 变为1)
print(counter1())  # 2(counter1 的 count 变为2)
print(counter2())  # 11(counter2 的 count 变为11)

二、函数递归(Recursion)

函数递归指函数直接或间接调用自身的行为,通过将复杂问题分解为与原问题相似的子问题,简化代码逻辑(如分治算法、树结构遍历等)。

1. 核心要素

递归必须满足两个条件,否则会导致无限递归(栈溢出):

  • 基线条件(Base Case):递归终止的条件(当问题足够简单时,直接返回结果,不再递归)。
  • 递归条件(Recursive Case):将问题分解为更小的子问题,调用自身解决。
2. 基本示例:计算阶乘

阶乘定义:n! = n × (n-1) × ... × 1,且 0! = 1(基线条件)。

def factorial(n):
    # 基线条件:n=0 时返回1,终止递归
    if n == 0:
        return 1
    # 递归条件:n! = n × (n-1)!
    return n * factorial(n - 1)

print(factorial(5))  # 输出:120(5×4×3×2×1×1)

执行过程

factorial(5) → 5 × factorial(4)
factorial(4) → 4 × factorial(3)
factorial(3) → 3 × factorial(2)
factorial(2) → 2 × factorial(1)
factorial(1) → 1 × factorial(0)
factorial(0) → 1(基线条件)
# 反向计算:1×1 → 2×1 → 3×2 → 4×6 → 5×24 → 120
3. 常见应用场景
  • 数学问题:斐波那契数列、阶乘、幂运算等。
  • 数据结构:树的遍历(前序 / 中序 / 后序)、图的深度优先搜索(DFS)。
  • 算法:分治算法(快速排序、归并排序)、汉诺塔问题等。

示例:斐波那契数列(第 n 项)

def fibonacci(n):
    if n <= 1:  # 基线条件:n=0返回0,n=1返回1
        return n
    # 递归条件:第n项 = 第n-1项 + 第n-2项
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(5))  # 输出:5(数列:0,1,1,2,3,5... 第5项为5)
4. 优缺点与注意事项
  • 优点:代码简洁,符合人类思维(直接翻译数学定义或递归逻辑)。
  • 缺点:
    • 效率低:重复计算(如斐波那契数列的递归会重复计算大量子问题)。
    • 栈溢出风险:Python 递归深度有限(默认约 1000 层),过深会报错(RecursionError)。
  • 优化方案:
    • 用缓存(如 functools.lru_cache)避免重复计算。
    • 改为迭代实现(效率更高,无栈溢出风险)。
    • 尾递归优化(Python 不支持,但理论上可将递归转为迭代)。

三、匿名函数(Lambda Functions)

匿名函数是用 lambda 关键字定义的 “一次性” 短函数,没有函数名,仅包含一个表达式,适用于简单逻辑的临时需求。

1. 基本语法
lambda 参数列表: 表达式  # 自动返回表达式的结果
  • 参数列表:与普通函数的参数规则一致(可包含必选参数、默认参数、*args 等)。
  • 表达式:只能有一个表达式(不能包含循环、条件语句等复杂结构,除非用三元表达式)。
2. 与普通函数的对比
特性 匿名函数(lambda) 普通函数(def)
名称 无名称(匿名) 有名称
定义关键字 lambda def
函数体 仅一个表达式(自动返回结果) 可包含多个语句(需 return
适用场景 简单逻辑(一行可完成) 复杂逻辑(多行代码)
可复用性 通常临时使用,不适合复用 可多次调用,适合复用

示例:等价的 lambda 与 def 函数

# 普通函数
def add(a, b):
    return a + b

# 匿名函数(赋值给变量后可调用,等价于上面的 add)
add_lambda = lambda a, b: a + b

print(add(2, 3))        # 5
print(add_lambda(2, 3)) # 5
3. 典型应用场景

匿名函数的核心价值是作为 “临时函数” 传递给高阶函数(如 sorted()map()filter() 等),简化代码。

  • 示例 1:作为 sorted() 的排序键(key)

    students = [("Alice", 20), ("Bob", 18), ("Charlie", 22)]
      
    # 按年龄排序(用 lambda 定义排序规则)
    sorted_by_age = sorted(students, key=lambda x: x[1])
    print(sorted_by_age)  # [('Bob', 18), ('Alice', 20), ('Charlie', 22)]
    
  • 示例 2:与 map() 配合实现批量转换

    numbers = [1, 2, 3, 4]
    # 用 lambda 定义“平方”逻辑,批量应用到列表
    squared = map(lambda x: x** 2, numbers)
    print(list(squared))  # [1, 4, 9, 16]
    
  • 示例 3:三元表达式实现简单条件判断

    # lambda 中用三元表达式实现二选一逻辑
    is_positive = lambda x: "正数" if x > 0 else "非正数"
    print(is_positive(5))  # 正数
    print(is_positive(-3)) # 非正数
    
4. 局限性
  • 只能包含一个表达式,无法实现复杂逻辑(如循环、异常处理)。
  • 可读性有限:复杂的 lambda 表达式(如嵌套三元表达式)会降低代码可读性,此时应改用普通函数。
  • 不能包含文档字符串(docstring),不利于维护和说明功能。
总结
  • 函数嵌套:通过内部函数实现逻辑封装和作用域隔离,核心应用是闭包(保存外部变量状态)。
  • 函数递归:通过调用自身分解问题,需严格定义基线条件,适合数学问题和分治算法,但要注意效率和栈溢出风险。
  • 匿名函数(lambda):简化简单逻辑的定义,适合作为高阶函数的临时参数,复杂逻辑仍需用普通函数。

这三种特性分别从代码组织、问题分解和简洁性三个维度扩展了 Python 函数的能力,合理使用能显著提升代码质量。

Python函数装饰器(用例讲解)

函数装饰器(Decorator)是 Python 中一种强大的语法,它允许你在不修改原函数代码的前提下,为函数添加额外功能(如日志记录、性能测试、权限验证等)。装饰器本质上是一个高阶函数(接收函数作为参数,并返回新函数)。

一、装饰器的基本原理

装饰器的核心逻辑是:

  1. 定义一个 “装饰器函数”,接收被装饰的函数作为参数。
  2. 在装饰器内部定义一个 “包装函数”(wrapper),用于添加额外功能。
  3. 包装函数中调用原函数,并返回其结果。
  4. 装饰器返回包装函数,替代原函数。

通过 @装饰器名 语法,可以简化装饰器的使用(语法糖)。

二、装饰器用例详解

用例 1:基础装饰器(日志记录)

为函数添加调用日志,记录函数名、参数和返回值。

def log_decorator(func):
    # 定义包装函数,接收任意参数
    def wrapper(*args, **kwargs):
        # 调用前:记录函数名和参数
        print(f"调用函数:{func.__name__},参数:{args}{kwargs}")
        # 调用原函数
        result = func(*args, **kwargs)
        # 调用后:记录返回值
        print(f"函数 {func.__name__} 返回:{result}")
        return result  # 返回原函数结果
    return wrapper  # 返回包装函数

# 使用装饰器(等价于 add = log_decorator(add))
@log_decorator
def add(a, b):
    return a + b

# 测试
add(2, 3)
# 输出:
# 调用函数:add,参数:(2, 3),{}
# 函数 add 返回:5

执行流程

  • @log_decorator 等价于 add = log_decorator(add),即 add 被替换为 wrapper 函数。
  • 调用 add(2, 3) 时,实际执行的是 wrapper(2, 3),先打印日志,再调用原 add 函数。

用例 2:带参数的装饰器(条件日志)

装饰器本身可以接收参数,实现更灵活的功能(如控制日志级别)。

# 外层函数:接收装饰器参数
def log_level(level):
    # 中层函数:接收被装饰函数
    def decorator(func):
        # 内层包装函数:根据参数添加功能
        def wrapper(*args, **kwargs):
            print(f"[{level}] 调用函数:{func.__name__}")
            result = func(*args, **kwargs)
            print(f"[{level}] 函数 {func.__name__} 执行完成")
            print(result)
            return result
        return wrapper
    return decorator

# 使用带参数的装饰器(指定日志级别)
@log_level("INFO")
def multiply(a, b):
    return a * b

@log_level("DEBUG")
def divide(a, b):
    return a / b

# 测试
multiply(3, 4)
# 输出:
# [INFO] 调用函数:multiply
# [INFO] 函数 multiply 执行完成

divide(10, 2)
# 输出:
# [DEBUG] 调用函数:divide
# [DEBUG] 函数 divide 执行完成

原理

  • @log_level("INFO") 先执行 log_level("INFO"),返回 decorator 函数。
  • 再执行 multiply = decorator(multiply),完成装饰。

用例 3:装饰器保留原函数信息(functools.wraps

默认情况下,装饰后的函数会丢失原函数的元信息(如函数名、文档字符串),需用 functools.wraps 修复。

import functools

def my_decorator(func):
    # 用 wraps 装饰 wrapper,继承原函数的元信息
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """包装函数的文档字符串"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def original_func():
    """原函数的文档字符串"""
    pass

# 对比:未用 wraps 时,以下会输出 wrapper 的信息
print(original_func.__name__)  # 输出:original_func(而非 wrapper)
print(original_func.__doc__)   # 输出:原函数的文档字符串(而非包装函数的)

为什么需要? 调试时(如打印函数名)或生成文档时,保留原函数信息能避免混淆。

用例 4:装饰器嵌套(多功能组合)

一个函数可以被多个装饰器装饰,执行顺序为从下到上(靠近函数的装饰器先执行)。

import functools

# 装饰器1:计时
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"函数 {func.__name__} 耗时:{end - start:.4f}秒")
        return result
    return wrapper

# 装饰器2:日志
def logger(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"执行函数:{func.__name__}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行结束")
        return result
    return wrapper

# 嵌套装饰:先执行 logger,再执行 timer(顺序从下到上)
@timer
@logger
def slow_function(seconds):
    import time
    time.sleep(seconds)  # 模拟耗时操作
    return "完成"

# 测试
slow_function(1)
# 输出:
# 执行函数:slow_function (logger 的前置操作)
# 函数 slow_function 执行结束 (logger 的后置操作)
# 函数 slow_function 耗时:1.0012秒 (timer 的后置操作)

执行顺序@timer 装饰 @logger 装饰后的函数,因此实际执行流程是: timer.wrapperlogger.wrapper → 原函数 slow_function

用例 5:类装饰器(面向对象风格)

除了函数,类也可以作为装饰器(需实现 __call__ 方法,使类实例可调用)。

class CountCalls:
    def __init__(self, func):
        # 初始化:接收被装饰函数
        self.func = func
        self.count = 0  # 记录调用次数

    def __call__(self, *args, **kwargs):
        # 使类实例可调用(模拟函数行为)
        self.count += 1
        print(f"函数 {self.func.__name__} 已调用 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls  # 等价于:add = CountCalls(add)
def add(a, b):
    return a + b

# 测试
add(1, 2)  # 输出:函数 add 已调用 1 次
add(3, 4)  # 输出:函数 add 已调用 2 次

优势:类装饰器可通过实例变量(如 self.count)更方便地保存状态(相比函数装饰器的闭包更直观)。

三、装饰器的常见应用场景
  1. 日志记录:自动记录函数调用、参数、返回值。
  2. 性能测试:统计函数执行时间。
  3. 权限验证:调用函数前检查用户权限(如 Web 框架中的登录验证)。
  4. 缓存结果:缓存函数调用结果,避免重复计算(如 functools.lru_cache)。
  5. 输入验证:检查函数参数是否符合要求。
总结

装饰器是 Python 中 “开闭原则”(对扩展开放,对修改关闭)的典型实现,其核心价值在于:

  • 不修改原函数代码,即可添加功能。
  • 代码复用性高,同一装饰器可应用于多个函数。
  • 逻辑清晰,将 “业务逻辑” 与 “辅助功能” 分离。

掌握装饰器能让你写出更简洁、灵活、可维护的代码,尤其在框架开发和工具库设计中应用广泛。

Python模块和包的分类

在 Python 中,模块(Module)包(Package) 是代码组织的核心单元,用于将函数、类、变量等按功能拆分,实现代码复用和结构化管理。它们的分类可以从来源功能结构等多个维度划分,以下是详细分类:

一、模块(Module)的分类

模块是一个以 .py 为扩展名的文件,包含 Python 代码(函数、类、变量等)。其分类主要基于来源功能加载方式

1. 按来源分类(最常用)

根据模块的开发主体,可分为三类:

(1)内置模块(Built-in Modules)

  • 定义:Python 解释器自带的模块,无需安装,直接通过 import 导入即可使用。

  • 特点:由 C 语言或 Python 编写,性能高效,覆盖基础功能。

  • 示例

    • 系统交互:sys(解释器信息)、os(操作系统接口)。
    • 数据处理:json(JSON 编解码)、datetime(日期时间处理)。
    • 基础工具:math(数学运算)、random(随机数生成)。
    import sys
    print(sys.version)  # 打印 Python 版本(内置模块用法)
    

(2)第三方模块(Third-party Modules)

  • 定义:由社区或第三方开发者开发的模块,需通过包管理工具(如 pip)安装后使用。

  • 特点:覆盖各类细分领域(如 Web 开发、数据分析、人工智能等),是 Python 生态丰富性的核心。

  • 示例

    • 网络请求:requests(HTTP 客户端)。
    • 数据分析:pandas(数据处理)、numpy(数值计算)。
    • Web 框架:flask(轻量 Web 框架)、django(全栈 Web 框架)。
    # 安装第三方模块
    pip install requests
    
    import requests
    response = requests.get("https://www.baidu.com")  # 第三方模块用法
    

(3)自定义模块(User-defined Modules)

  • 定义:由开发者根据自身需求编写的 .py 文件,用于封装项目中复用的逻辑。

  • 特点:按需设计,与项目业务强相关,是项目代码组织的基础。

  • 示例

    若创建

    my_utils.py
    

    文件:

    # my_utils.py(自定义模块)
    def add(a, b):
        return a + b
    

    可在其他文件中导入使用:

    import my_utils
    print(my_utils.add(2, 3))  # 输出:5(自定义模块用法)
    
2. 按功能分类

根据模块实现的功能,可分为:

  • 系统基础模块:处理与操作系统交互的功能,如 os(文件操作)、sys(解释器配置)、pathlib(路径处理)。
  • 数据处理模块:处理数据编解码、转换等,如 jsoncsv(CSV 文件)、pickle(Python 对象序列化)。
  • 网络模块:处理网络通信,如 socket(底层网络接口)、http(HTTP 协议)、urllib(URL 处理)。
  • 数学与科学计算模块:如 math(基础数学)、statistics(统计函数)、numpy(第三方,数值计算)。
  • Web 与 IO 模块:如 flask(第三方 Web 框架)、logging(日志处理)、argparse(命令行参数解析)。
3. 按加载方式分类

根据模块的编译和加载形式,可分为:

  • 源码模块(Source Modules):以 .py 为扩展名的纯 Python 代码文件,加载时由解释器动态编译。
  • 字节码模块(Bytecode Modules):以 .pyc.pyo 为扩展名的编译后文件(由 .py 编译生成),加载速度更快(避免重复编译)。
  • 扩展模块(Extension Modules):由 C/C++ 等编译型语言编写的二进制文件(如 .pyd.so),用于提升性能或调用底层系统接口(如 _socketsocket 模块的 C 实现)。

二、包(Package)的分类

包是包含多个模块的目录,必须包含 __init__.py 文件(Python 3.3+ 允许省略,但仍推荐保留以明确标识为包)。其分类主要基于来源结构功能

1. 按来源分类(与模块对应)

(1)内置包(Built-in Packages)

  • 定义:Python 标准库中的包,由多个相关模块组成,无需安装。

  • 示例

    • os.pathos 包下的 path 子包,处理路径相关功能。
    • xml:包含 xml.etreexml.dom 等模块,处理 XML 数据。
    • http:包含 http.clienthttp.server 等模块,处理 HTTP 相关功能。
    from os.path import join  # 导入内置包的子模块
    print(join("a", "b"))  # 输出:a/b(路径拼接)
    

(2)第三方包(Third-party Packages)

  • 定义:由社区开发的包含多个模块的功能集合,需通过 pip 安装,通常比单个模块更复杂(可能包含多级子包)。

  • 示例

    • pandas:数据分析包,包含 pandas.corepandas.io 等子包。
    • flask:Web 框架包,包含 flask.appflask.routing 等子包。
    • scikit-learn:机器学习包,包含 sklearn.model_selectionsklearn.ensemble 等子包。
    pip install pandas  # 安装第三方包
    
    from pandas import DataFrame  # 导入第三方包的类
    df = DataFrame({"name": ["Alice"]})  # 使用第三方包
    

(3)自定义包(User-defined Packages)

  • 定义:开发者为项目创建的包,用于组织多个相关模块(通常按功能拆分)。

  • 结构示例

    :一个处理用户管理的自定义包

    user
    

    user/                  # 包目录
    ├── __init__.py        # 包标识文件(可空,或定义导出内容)
    ├── models.py          # 模块:用户数据模型
    ├── services.py        # 模块:用户业务逻辑
    └── utils.py           # 模块:用户相关工具函数
    

    使用方式:

    from user.models import User  # 导入自定义包的模块
    user = User(name="Alice")
    
2. 按结构分类

根据包的嵌套层级,可分为:

(1)单级包(Flat Package)

  • 定义:仅包含一个目录和若干模块,无嵌套子包。
  • 示例:上述 user 包(仅 user/ 一级目录,内部是模块文件)。

(2)多级包(Nested Package)

  • 定义:包内部包含子包,形成层级结构(如 a.b.c),适合大型项目。

  • 示例

    :一个电商项目的包结构:

    ecommerce/                # 一级包
    ├── __init__.py
    ├── user/                 # 子包(二级)
    │   ├── __init__.py
    │   └── models.py
    ├── order/                # 子包(二级)
    │   ├── __init__.py
    │   └── services.py
    └── payment/              # 子包(二级)
        ├── __init__.py
        └── utils.py
    

    使用方式:

    from ecommerce.order.services import create_order  # 多级包导入
    
3. 按功能分类

根据包的业务领域,可分为:

  • 框架包:提供完整开发框架,如 django(Web 开发)、tensorflow(深度学习)。
  • 工具包:提供通用工具功能,如 utils(自定义工具包)、python-dateutil(日期工具)。
  • 领域包:针对特定领域,如 pandas(数据分析)、pygame(游戏开发)、sqlalchemy(数据库 ORM)。

三、模块与包的核心区别

维度 模块(Module) 包(Package)
形式 单个 .py 文件 包含多个模块的目录(含 __init__.py
作用 封装单一或相关功能的代码块 组织多个相关模块,形成功能集合
导入方式 import 模块名 import 包名.模块名from 包名 import 模块名

总结

  • 模块按来源可分为内置、第三方、自定义模块,是代码复用的基础单元。
  • 是模块的集合,按来源同样分为内置、第三方、自定义包,按结构可分为单级和多级包,适合大型项目的代码组织。

理解模块和包的分类,有助于高效使用 Python 生态(如选择合适的第三方库)和设计清晰的项目结构(如合理拆分自定义模块和包)。

Python模块的导入(import&from…import…)

Python 中模块的导入是代码复用的核心机制,通过 importfrom...import... 语句可以使用其他模块(或包)中的函数、类、变量等。两种方式各有特点,适用于不同场景,以下是详细讲解:

一、基本导入方式:import 模块名

语法import 模块名 [as 别名] 作用:导入整个模块,使用时需通过 “模块名。成员” 的形式访问模块内的函数、类或变量。

1. 基础用法
# 导入内置模块 math
import math

# 使用模块中的函数(模块名.函数名)
print(math.sqrt(16))  # 输出:4.0(调用math模块的sqrt函数)
print(math.pi)        # 输出:3.141592653589793(访问math模块的变量pi)
2. 别名(as):简化模块名

当模块名较长或存在命名冲突时,可用 as 指定别名:

# 导入第三方模块 pandas,并指定别名为 pd(行业惯例)
import pandas as pd

# 使用别名访问
df = pd.DataFrame({"name": ["Alice"]})  # 等价于 pandas.DataFrame
3. 一次导入多个模块

可在一行导入多个模块(按惯例用空格分隔,或换行对齐):

import math, sys, os  # 一行导入多个模块(不推荐,可读性差)

# 推荐:换行对齐,更清晰
import math
import sys
import os

特点

  • 优点:避免命名冲突(通过模块名隔离),适合需要使用模块中多个成员的场景。
  • 缺点:每次使用需带模块名,代码稍长。

二、部分导入:from...import...

语法from 模块名 import 成员名 [as 别名] 作用:仅导入模块中指定的成员(函数、类、变量),使用时可直接通过成员名访问,无需带模块名。

1. 导入单个成员
# 从 math 模块中仅导入 sqrt 函数
from math import sqrt

# 直接使用成员名,无需带模块名
print(sqrt(25))  # 输出:5.0
2. 导入多个成员
# 从 math 模块中导入 sqrt 和 pi 两个成员
from math import sqrt, pi

print(sqrt(9))  # 3.0
print(pi)       # 3.141592653589793
3. 别名(as):避免命名冲突

若导入的成员与当前作用域的变量 / 函数重名,可用 as 重命名:

# 当前作用域已有名为 sum 的变量
sum = 100

# 从 math 模块导入 sum 函数时指定别名
from math import sum as math_sum

print(math_sum([1, 2, 3]))  # 输出:6(调用math的sum函数)
print(sum)                  # 输出:100(当前作用域的sum变量)
4. 导入所有成员(*

* 可导入模块中所有公开成员(不推荐,易导致命名冲突):

# 导入 math 模块的所有公开成员
from math import *

print(sqrt(36))  # 6.0
print(cos(0))    # 1.0(math模块的cos函数)

注意:模块可通过 __all__ 变量控制 from...import * 导入的成员(仅包含在 __all__ 中的成员会被导入): 例如,my_module.py 中定义:

# my_module.py
__all__ = ["add", "PI"]  # 控制 from...import * 仅导入 add 和 PI

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

PI = 3.14

则导入时:

from my_module import *
add(2, 3)  # 正常使用(在__all__中)
PI         # 正常使用(在__all__中)
subtract(5, 2)  # 报错:未被导入(不在__all__中)

特点

  • 优点:使用成员时无需带模块名,代码更简洁,适合仅需模块中少数成员的场景。
  • 缺点:可能导致命名冲突(不同模块的成员同名时覆盖),from...import * 会污染命名空间,不推荐在大型项目中使用。

三、从包中导入模块 / 成员

包是包含多个模块的目录,导入包中的模块或成员时,需指定完整路径(包名。模块名)。

1. 导入包中的模块(import
# 假设有包结构:mypackage/utils.py,其中包含函数 format_data
import mypackage.utils

# 使用:包名.模块名.函数名
mypackage.utils.format_data("test")

可用 as 简化:

import mypackage.utils as utils
utils.format_data("test")  # 简化后
2. 从包中导入模块的成员(from...import...
# 从包的模块中导入指定函数
from mypackage.utils import format_data

format_data("test")  # 直接使用

也可先导入模块,再从模块中导入成员:

from mypackage import utils  # 先导入模块
from utils import format_data  # 再从模块中导入成员

四、导入的搜索路径

Python 导入模块时,会按以下顺序搜索路径(可通过 sys.path 查看):

  1. 当前执行脚本所在目录
  2. 系统环境变量 PYTHONPATH 中的路径
  3. Python 标准库目录
  4. 第三方库安装目录(如 site-packages

若模块不在这些路径中,导入会报错(ModuleNotFoundError),需手动将模块所在目录添加到 sys.path

import sys
sys.path.append("/path/to/module/directory")  # 添加路径
import my_module  # 现在可导入

五、常见问题与最佳实践

  1. 避免循环导入:模块 A 导入模块 B,同时模块 B 导入模块 A,会导致报错。解决方法:重构代码,将共享逻辑抽离到新模块。
  2. 慎用 from...import \*:易引发命名冲突,推荐显式导入所需成员。
  3. 优先使用相对导入(包内部):在包内部模块间导入时,可用相对路径(如 from . import module. 表示当前目录)。
  4. 遵循命名规范:模块名用小写字母,多个单词用下划线连接(如 data_process.py),避免与标准库模块重名(如不要命名为 json.py)。

总结

导入方式 语法示例 适用场景 优点 缺点
import 模块名 import math 使用模块中多个成员,避免命名冲突 隔离命名空间 需带模块名,代码较长
from 模块 import 成员 from math import sqrt 使用模块中少数成员,追求代码简洁 调用方便 可能引发命名冲突
from 模块 import * from math import * 临时测试或快速使用(不推荐正式代码) 无需指定成员 命名空间污染,冲突风险高

根据实际需求选择合适的导入方式,既能保证代码简洁,又能避免潜在的命名冲突问题。

Python标准库(date, datetime, os模块, json模块, jsonpath模块, 文件处理)

Python 标准库包含了大量实用模块,涵盖了日常开发中常见的功能需求。以下重点介绍与日期时间处理、系统交互、JSON 操作及文件处理相关的核心模块,包括 datetimeosjson 等,并补充说明非标准库但常用的 jsonpath

一、datetime 模块:日期与时间处理

datetime 模块提供了处理日期和时间的类,比内置的 time 模块更直观,主要包含 date(日期)、time(时间)、datetime(日期时间)、timedelta(时间差)等类。

1. 核心类及用法
  • date:处理年、月、日

    from datetime import date
      
    # 创建日期对象(年, 月, 日)
    d = date(2023, 10, 1)
    print(d.year)   # 2023
    print(d.month)  # 10
    print(d.day)    # 1
    print(d.weekday())  # 6(周一为0,周日为6)
      
    # 获取当前日期
    today = date.today()
    print(today)  # 2023-10-01(格式:YYYY-MM-DD)
    
  • time:处理时、分、秒、微秒

    from datetime import time
      
    # 创建时间对象(时, 分, 秒, 微秒)
    t = time(15, 30, 45, 100000)
    print(t.hour)   # 15
    print(t.minute) # 30
    print(t.second) # 45
    
  • datetime:同时处理日期和时间(最常用)

    from datetime import datetime
      
    # 创建日期时间对象(年, 月, 日, 时, 分, 秒)
    dt = datetime(2023, 10, 1, 15, 30, 45)
    print(dt)  # 2023-10-01 15:30:45
      
    # 获取当前日期时间
    now = datetime.now()
    print(now)  # 2023-10-01 15:30:45.123456(包含微秒)
    
  • timedelta:计算时间差(用于日期加减)

    from datetime import datetime, timedelta
      
    now = datetime.now()
    # 3天后
    future = now + timedelta(days=3)
    # 2小时前
    past = now - timedelta(hours=2)
      
    print(f"现在:{now}")
    print(f"3天后:{future}")
    print(f"2小时前:{past}")
    
2. 格式化与解析(strftimestrptime
  • strftime:将日期时间对象转为字符串(格式化)

    now = datetime.now()
    # 格式化为 "年-月-日 时:分:秒"
    print(now.strftime("%Y-%m-%d %H:%M:%S"))  # 2023-10-01 15:30:45
    

    常用格式符:%Y(4 位年)、%m(2 位月)、%d(2 位日)、%H(24 小时制时)、%M(分)、%S(秒)。

  • strptime:将字符串解析为日期时间对象

    date_str = "2023-10-01"
    # 解析字符串为date对象
    d = datetime.strptime(date_str, "%Y-%m-%d").date()
    print(d)  # 2023-10-01
    

二、os 模块:与操作系统交互

os 模块提供了访问操作系统功能的接口,主要用于文件 / 目录操作、环境变量、进程管理等。

1. 常用功能
  • 获取 / 切换当前目录

    import os
      
    # 获取当前工作目录
    cwd = os.getcwd()
    print(cwd)  # 例如:/home/user/project
      
    # 切换工作目录
    os.chdir("/home/user")
    
  • 目录操作

    # 列出目录下的文件和子目录
    print(os.listdir("."))  # 当前目录内容
      
    # 创建单级目录(目录已存在会报错)
    os.mkdir("test_dir")
      
    # 创建多级目录(递归创建)
    os.makedirs("a/b/c", exist_ok=True)  # exist_ok=True:目录存在时不报错
      
    # 删除空目录(非空目录会报错)
    os.rmdir("test_dir")
    
  • 文件操作

    # 删除文件
    os.remove("test.txt")  # 文件不存在会报错
      
    # 重命名文件/目录
    os.rename("old.txt", "new.txt")
    
  • 路径处理(os.path 子模块)

    from os.path import join, exists, isfile, isdir
      
    # 拼接路径(自动处理不同系统的路径分隔符)
    path = join("a", "b", "file.txt")  # Windows 会转为 "a\b\file.txt"
      
    # 判断路径是否存在
    print(exists(path))  # True/False
      
    # 判断是否为文件/目录
    print(isfile(path))  # True(如果是文件)
    print(isdir("a"))    # True(如果是目录)
    

三、json 模块:JSON 数据处理

json 模块用于实现 Python 数据类型与 JSON 格式的相互转换(序列化与反序列化)。

1. 核心函数
  • 序列化(Python → JSON)json.dumps()(转为字符串)、json.dump()(写入文件)

    import json
      
    # Python 字典(对应 JSON 对象)
    data = {
        "name": "Alice",
        "age": 30,
        "hobbies": ["reading", "coding"]
    }
      
    # 转为 JSON 字符串
    json_str = json.dumps(data, indent=2)  # indent:格式化输出(美观)
    print(json_str)
    # 输出:
    # {
    #   "name": "Alice",
    #   "age": 30,
    #   "hobbies": [
    #     "reading",
    #     "coding"
    #   ]
    # }
      
    # 直接写入 JSON 文件
    with open("data.json", "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)  # ensure_ascii=False:保留中文
    
  • 反序列化(JSON → Python)json.loads()(从字符串读取)、json.load()(从文件读取)

    # 从 JSON 字符串解析
    json_str = '{"name": "Bob", "age": 25}'
    data = json.loads(json_str)
    print(data["name"])  # Bob(Python 字典)
      
    # 从 JSON 文件读取
    with open("data.json", "r", encoding="utf-8") as f:
        data = json.load(f)
    print(data["hobbies"])  # ['reading', 'coding']
    
2. 注意事项
  • JSON 支持的数据类型有限(字符串、数字、布尔、数组、对象、null),Python 中的 tuple 会被转为 JSON 数组,None 会转为 JSON null
  • 自定义对象序列化需通过 default 参数指定转换函数(如 json.dumps(obj, default=lambda x: x.__dict__))。

四、jsonpath 模块:JSON 数据解析(非标准库)

jsonpath 不是 Python 标准库(需通过 pip install jsonpath 安装),用于从复杂 JSON 数据中提取指定内容,类似 XPath 之于 XML。

基本用法

假设有 JSON 数据:

data = {
    "store": {
        "book": [
            {"title": "Python Guide", "price": 50},
            {"title": "Java Guide", "price": 60}
        ],
        "bicycle": {"color": "red", "price": 300}
    }
}

使用 jsonpath 提取数据:

from jsonpath import jsonpath

# 提取所有书的标题($ 表示根节点,.. 表示递归查找)
titles = jsonpath(data, "$..book[*].title")
print(titles)  # ['Python Guide', 'Java Guide']

# 提取价格大于50的物品
expensive = jsonpath(data, "$..[?(@.price > 50)]")
print(expensive)  # [{'title': 'Java Guide', 'price': 60}, {'color': 'red', 'price': 300}]

常用语法:$(根)、.[index](数组索引)、.*(所有子节点)、..key(递归查找 key)、?()(过滤条件)。

五、文件处理(内置 open 函数 + 标准库)

Python 通过内置的 open 函数结合 osshutil 等模块实现文件读写和管理。

1. 文件读写基础(open 函数)

open(file, mode='r', encoding=None) 用于打开文件,返回文件对象,常用模式:

  • r:只读(默认);w:写入(覆盖原有内容);a:追加(在末尾添加)。
  • b:二进制模式(如 rbwb,用于非文本文件如图片)。

示例:文本文件读写

# 写入文件
with open("test.txt", "w", encoding="utf-8") as f:  # with 语句自动关闭文件
    f.write("Hello, Python!\n")  # 写入一行
    f.writelines(["First line\n", "Second line\n"])  # 写入多行

# 读取文件
with open("test.txt", "r", encoding="utf-8") as f:
    content = f.read()  # 读取全部内容
    # line = f.readline()  # 读取一行
    # lines = f.readlines()  # 读取所有行(列表)
print(content)
2. 高级文件操作(shutil 模块)

shutil 是标准库,提供更高级的文件操作(复制、移动、删除目录等):

import shutil

# 复制文件
shutil.copy("source.txt", "dest.txt")  # 复制内容和权限
shutil.copy2("source.txt", "dest.txt")  # 保留元数据(如创建时间)

# 移动文件/目录(类似剪切)
shutil.move("old_dir", "new_dir")

# 删除非空目录(递归删除,谨慎使用!)
shutil.rmtree("dir_to_delete")

总结

模块 / 功能 核心作用 关键函数 / 类
datetime 日期时间处理 datedatetimetimedeltastrftime
os 操作系统交互(文件 / 目录) os.getcwd()os.makedirs()os.path.join()
json JSON 序列化与反序列化 dumps()loads()dump()load()
jsonpath 提取 JSON 数据(非标准库) jsonpath()
文件处理 读写文件、高级操作 open()shutil.copy()shutil.move()

这些模块是 Python 日常开发的基础工具,掌握它们能高效处理日期、系统交互、数据格式转换和文件管理等常见需求。

六、资源管理

在 Python 中,with 语句是一种优雅的资源管理机制,主要用于处理需要 “获取 - 使用 - 释放” 流程的资源(如文件、网络连接、数据库连接等)。它能确保资源在使用完毕后自动释放,即使过程中发生异常也不会导致资源泄漏,比手动调用 close() 等方法更安全、简洁。

一、with 语句的核心作用

解决 “资源管理” 痛点:

  • 许多资源(如文件、网络连接)在使用后必须手动释放(否则会占用系统资源,甚至导致程序异常)。
  • 手动释放时,若代码中发生异常,可能跳过释放步骤(如 close() 语句未执行)。

with 语句的核心价值:自动管理资源生命周期,无论代码块正常执行还是发生异常,都能保证资源被正确释放。

二、基本语法与执行流程
  1. R
with 上下文管理器表达式 as 变量:
    # 资源使用代码块(缩进部分)
    对资源的操作如读写文件
  • 上下文管理器:一个实现了 __enter__()__exit__() 方法的对象(如文件对象、open() 函数返回的对象),负责资源的获取和释放。
  • as 变量:可选,将 __enter__() 方法的返回值赋值给变量(方便在代码块中使用资源)。

2. 执行流程(以文件操作为例)

with open("test.txt", "r") as f:  # 打开文件(获取资源)
    content = f.read()            # 使用资源
# 代码块结束后,自动关闭文件(释放资源),无需手动调用 f.close()

具体步骤:

  1. 执行 open("test.txt", "r"),返回一个文件对象(上下文管理器)。
  2. 调用该对象的 __enter__() 方法:打开文件,返回文件对象本身(赋值给 f)。
  3. 执行缩进的代码块(f.read()):使用资源。
  4. 无论代码块是否正常执行(或发生异常),自动调用对象的 __exit__() 方法:关闭文件(释放资源)。
三、为什么要用 with 语句?(对比手动管理)

以文件操作为例,对比 “手动管理” 和 “with 语句管理” 的差异:

  1. 手动管理资源(繁琐且危险)
# 手动打开文件
f = open("test.txt", "r")
try:
    content = f.read()  # 使用资源
finally:
    f.close()  # 确保关闭(即使发生异常)
  • 缺点:必须用 try...finally 保证 close() 执行,代码冗长;若忘记写 finallyclose(),会导致文件句柄泄漏。

2. with 语句管理(简洁且安全)

with open("test.txt", "r") as f:
    content = f.read()  # 自动处理关闭,无需手动操作
  • 优点:无需显式调用 close(),代码更简洁;无论是否发生异常(如读取时文件损坏),文件都会被自动关闭。
四、常见应用场景

with 语句适用于所有需要 “获取 - 使用 - 释放” 的资源,最典型的场景包括:

1. 文件操作(最常用)

# 写入文件
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello, with语句!")  # 自动关闭文件

# 读取文件
with open("output.txt", "r", encoding="utf-8") as f:
    print(f.read())  # 输出:Hello, with语句!
  1. 数据库连接

操作数据库时,连接需要及时关闭以释放资源,with 语句可简化管理:

import sqlite3

# 连接SQLite数据库
with sqlite3.connect("test.db") as conn:  # 获取连接
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE IF NOT EXISTS users (name TEXT)")  # 使用连接
# 代码块结束后,自动关闭连接(无需 conn.close())
  1. 线程锁(避免资源竞争)

多线程中,锁的获取和释放需成对出现,with 语句可确保锁被正确释放:

import threading

lock = threading.Lock()

with lock:  # 获取锁
    # 临界区代码(如操作共享变量)
    print("线程安全的操作")
# 自动释放锁(即使代码块中发生异常)
五、上下文管理器:with 语句的底层原理

with 语句的功能依赖于上下文管理器(Context Manager)—— 即实现了 __enter__()__exit__() 两个 “魔术方法” 的对象。

1. 上下文管理器的两个核心方法

  • __enter__(self):进入 with 代码块时调用,返回资源对象(赋值给 as 后的变量)。

  • __exit__(self, exc_type, exc_val, exc_tb)
    

    :离开

    with
    

    代码块时调用(无论是否有异常),负责释放资源。

    • 参数 exc_typeexc_valexc_tb 分别表示异常类型、异常值、异常追踪信息(无异常时均为 None)。

2. 自定义上下文管理器(示例)

若要让自定义类支持 with 语句,只需实现上述两个方法。例如,自定义一个简单的 “文件管理器”:

class MyFileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None  # 存储文件对象

    # 获取资源:打开文件
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file  # 返回文件对象,供 as 变量使用

    # 释放资源:关闭文件
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        # 若有异常,可在此处理(返回 True 表示已处理,不会向外传播)
        return False  # 不处理异常,向外传播

# 使用自定义上下文管理器
with MyFileManager("test.txt", "w") as f:
    f.write("自定义上下文管理器")  # 自动调用 __enter__ 和 __exit__

3. 简化自定义:contextlib 装饰器

对于简单场景,可使用标准库 contextlibcontextmanager 装饰器,无需手动实现 __enter____exit__

from contextlib import contextmanager

@contextmanager
def my_file_manager(filename, mode):
    # __enter__ 部分:获取资源
    file = open(filename, mode)
    yield file  # 返回资源对象(相当于 __enter__ 的 return)
    # __exit__ 部分:释放资源(yield 后代码在离开 with 时执行)
    file.close()

# 使用方式相同
with my_file_manager("test.txt", "r") as f:
    print(f.read())
六、总结
  • 核心价值with 语句通过上下文管理器,自动完成资源的 “获取 - 释放” 流程,避免资源泄漏,简化代码。
  • 适用场景:文件操作、数据库连接、网络连接、线程锁等需要手动释放的资源。
  • 底层原理:依赖上下文管理器的 __enter__()(获取资源)和 __exit__()(释放资源)方法。

使用 with 语句是 Python 中 “编写健壮代码” 的最佳实践之一,尤其在资源管理场景中,应优先使用而非手动调用 close() 等方法。

Python输入输出(print&input及基础输出优化)

Python 的输入输出(I/O)是程序与用户交互的基础,核心通过 print() 函数(输出)和 input() 函数(输入)实现。掌握它们的基础用法及输出优化技巧,能让程序交互更友好、输出更规范。

一、输出函数:print()

 `print()` 用于将信息输出到控制台(或指定文件),是最常用的输出工具。其基本语法为:
 `print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

1. 基础用法

  • 打印单个对象:直接传入字符串、数字、变量等。

    name = "Alice"
    print("Hello")  # 输出字符串
    print(123)      # 输出数字
    print(name)     # 输出变量
    
  • 打印多个对象:用逗号分隔,默认用空格(sep)分隔多个对象。

  age = 30
  print("Name:", name, "Age:", age)  # 输出:Name: Alice Age: 30

2. 核心参数详解

  • sep:指定多个对象的分隔符(默认是空格)。
print("Name", name, "Age", age, sep="|")  # 输出:Name|Alice|Age|30
  • end:指定输出结束时的字符(默认是换行符 \n)。

     #不换行输出(用空格结束)
    print("Hello", end=" ")
    print("World")  # 输出:Hello World
    
  • file:指定输出目标(默认是控制台 sys.stdout,可改为文件)。

      # 输出到文件(而非控制台)
      with open("output.txt", "w", encoding="utf-8") as f:
      print("Hello, File!", file=f)  # 内容会写入 output.txt
    
  • flush:是否立即刷新缓冲区(默认 False,缓冲区满后再输出)。 常用于实时显示进度(如下载进度条):

    import time
    for i in range(5):
        print(f"进度:{i+1}/5", end="\r", flush=True)  # \r 回到行首覆盖输出
        time.sleep(1)
    

二、输入函数:input()

input()` 用于获取用户从控制台的输入,返回值始终是**字符串类型**。基本语法:
`variable = input(prompt)
  • prompt:可选参数,输入前显示的提示信息(字符串)。

1. 基础用法

# 获取用户输入(显示提示信息)
name = input("请输入你的名字:")
print(f"你好,{name}!")  # 输出:你好,Alice!(假设输入 Alice)

2. 类型转换

input() 返回的是字符串,若需要数字(整数 / 浮点数),需手动转换:

# 获取整数
age_str = input("请输入年龄:")
age = int(age_str)  # 转换为整数
print(f"明年你将是 {age + 1} 岁")

# 简写:直接在输入后转换
height = float(input("请输入身高(米):"))  # 输入 1.75 → 转换为 1.75(浮点数)

3. 注意事项

  • 输入过程会阻塞程序:input() 执行时,程序会暂停等待用户输入,按回车后继续。

  • 处理异常输入:若用户输入不符合预期(如输入字母却要转换为整数),会报错,需用

    try-except
    

    处理:

    try:
        num = int(input("请输入一个数字:"))
    except ValueError:
        print("输入错误!请输入整数。")
    

三、输出优化:格式化输出技巧

默认的 print() 输出可能不够美观或清晰,需要通过格式化控制输出格式。Python 提供了 3 种主流格式化方式:

1. 百分号(%)格式(传统方式)

类似 C 语言的 printf,用 % 作为占位符,语法:"格式字符串" % (值1, 值2, ...) 常用占位符:%s(字符串)、%d(整数)、%f(浮点数)。

	name = "Bob"
	age = 25
	height = 1.80

	# 基本用法
	print("姓名:%s,年龄:%d,身高:%f" % (name, age, height))
	# 输出:姓名:Bob,年龄:25,身高:1.800000

	# 控制浮点数精度(保留2位小数)
	print("身高:%.2f 米" % height)  # 输出:身高:1.80 米

	# 整数补零(宽度为3,不足补零)
	print("编号:%03d" % 5)  # 输出:编号:005
  1. str.format() 方法(Python 3.0+)

{} 作为占位符,通过 .format() 传递参数,功能比百分号更灵活。

# 按位置传递参数
print("姓名:{},年龄:{}".format(name, age))  # 输出:姓名:Bob,年龄:25

# 按索引传递(可重复使用)
print("身高:{1} 米,姓名:{0}".format(name, height))  # 输出:身高:1.8 米,姓名:Bob

# 按关键字传递
print("姓名:{n},年龄:{a}".format(n=name, a=age))  # 输出:姓名:Bob,年龄:25

# 格式化数字(保留2位小数,千位分隔符)
print("工资:{:.2f} 元".format(12345.678))  # 输出:工资:12345.68 元
print("人口:{:,}".format(1400000000))     # 输出:人口:1,400,000,000
3. f-string(Python 3.6+,推荐)

在字符串前加 fF,用 {变量/表达式} 直接嵌入值,简洁高效,是目前最推荐的方式。

# 直接嵌入变量
print(f"姓名:{name},年龄:{age}")  # 输出:姓名:Bob,年龄:25

# 嵌入表达式(自动计算)
print(f"明年年龄:{age + 1}")  # 输出:明年年龄:26

# 格式化数字(与 format 语法一致)
print(f"身高:{height:.1f} 米")  # 输出:身高:1.8 米

# 调用函数(字符串大写)
print(f"姓名大写:{name.upper()}")  # 输出:姓名大写:BOB
4. 对齐与填充(格式化进阶)

通过格式化控制文本对齐方式(左对齐、右对齐、居中),适合输出表格等场景。

# f-string 对齐示例(宽度为10,不足用指定字符填充)
print(f"左对齐:{name:<10}!")  # 左对齐,宽度10 → 输出:左对齐:Bob       !
print(f"右对齐:{name:>10}!")  # 右对齐 → 输出:右对齐:       Bob!
print(f"居中:{name:^10}!")    # 居中 → 输出:居中:   Bob    !
print(f"填充:{name:#^10}!")   # 居中,用#填充 → 输出:填充:###Bob####!

四、总结与最佳实践

功能 工具 / 方法 特点与推荐场景
输出基础 print() 简单输出,通过 sep/end 控制分隔和结尾
获取输入 input() + 类型转换 交互输入,注意异常处理
格式化输出 百分号(% 兼容旧代码,功能有限
  str.format() 功能全面,适合复杂格式化
  f-string 简洁高效,支持表达式,推荐优先使用

实际开发中,f-string 因简洁性和可读性成为首选;复杂表格或旧代码可考虑 str.format();输入时务必做好类型转换和异常处理,确保程序健壮性。

Python异常处理(捕获异常try…except…finally&抛出异常raise)

在 Python 中,异常是程序运行时发生的错误(如除以零、文件不存在、类型不匹配等),若不处理会导致程序崩溃。异常处理机制通过 try...except...finally 捕获并处理异常,通过 raise 主动抛出异常,让程序在错误发生时仍能优雅运行并给出合理反馈。

一、异常的基本概念

1. 什么是异常?

异常是 Python 解释器在检测到错误时触发的 “信号”,例如:

  • ZeroDivisionError:除以零
  • ValueError:值无效(如 int("abc")
  • FileNotFoundError:文件不存在
  • TypeError:类型不匹配(如 "2" + 2

未处理的异常会导致程序终止并打印错误信息(Traceback):

print(10 / 0)  # 触发 ZeroDivisionError,程序崩溃
# 输出:ZeroDivisionError: division by zero

2. 异常处理的目的

  • 避免程序崩溃,让程序继续执行(如用户输入错误时提示重新输入)。
  • 记录错误信息,便于调试(如日志记录异常详情)。
  • 给用户友好反馈(如 “文件不存在,请检查路径” 而非技术错误栈)。

二、捕获异常:try...except...finally

try...except...finally 是 Python 捕获异常的核心结构,用于 “监控” 可能出错的代码,并定义错误处理逻辑。

1. 基本结构与执行流程
try:
    # 1. 尝试执行的代码(可能触发异常)
    危险操作如文件读写数据转换
except 异常类型1:
    # 2. 若触发“异常类型1”,执行此块
    处理逻辑1
except (异常类型2, 异常类型3):
    # 3. 若触发“异常类型2”或“异常类型3”,执行此块
    处理逻辑2
else:
    # 4. 若 try 块无异常,执行此块(可选)
    正常逻辑
finally:
    # 5. 无论是否有异常,都会执行此块(可选,用于资源清理)
    清理操作如关闭文件释放连接

执行顺序总结

  • 无异常:tryelsefinally
  • 有异常:try(出错行后停止)→ 匹配的 exceptfinally
2. 核心组件解析

(1)try 块:监控风险代码

try 块包裹可能触发异常的代码,一旦某行代码出错,立即跳转到 except 块,try 块后续代码不再执行。

try:
    print("尝试转换数字...")
    num = int("abc")  # 触发 ValueError,后续代码不执行
    print("转换成功")  # 此句不会执行
except ValueError:
    print("转换失败:输入不是有效数字")

# 输出:
# 尝试转换数字...
# 转换失败:输入不是有效数字

(2)except 块:捕获并处理异常

except 块用于指定 “要捕获的异常类型” 和 “对应的处理逻辑”,支持多种写法:

  • 捕获单个异常:针对特定异常类型(推荐,避免捕获无关错误)

    try:
        result = 10 / 0  # 触发 ZeroDivisionError
    except ZeroDivisionError:
        print("错误:除数不能为零")
    
  • 捕获多个异常:用元组指定多个异常类型,共用处理逻辑

    try:
        # 可能触发 ValueError 或 ZeroDivisionError
        num = int(input("请输入除数:"))
        result = 10 / num
    except (ValueError, ZeroDivisionError) as e:
        # 用 as e 获取异常对象,打印错误详情
        print(f"操作失败:{e}")  # 输出具体错误信息(如“操作失败:division by zero”)
    
  • 捕获所有异常:用 Exception 捕获几乎所有非系统级异常(谨慎使用,避免掩盖未知错误)

    try:
        risky_operation()
    except Exception as e:
        print(f"发生未知错误:{e}")
    

    ❌ 禁止使用空 exceptexcept:):会捕获包括 KeyboardInterrupt(Ctrl+C)在内的所有异常,导致程序无法正常终止。

(3)else 块:无异常时执行

else 块是 try 块无异常时的 “补充逻辑”,用于分离 “风险代码” 和 “正常逻辑”,可读性更高。

try:
    num = int(input("请输入数字:"))
except ValueError:
    print("输入错误")
else:
    # 只有无异常时才执行(num 已成功转换为整数)
    print(f"你输入的数字是:{num}")

(4)finally 块:强制清理资源

finally无论是否有异常,都会执行,核心用途是 “资源清理”(如关闭文件、释放网络连接、解锁资源等),避免资源泄漏。

file = None
try:
    file = open("data.txt", "r")  # 尝试打开文件
    content = file.read()
except FileNotFoundError:
    print("文件不存在")
finally:
    # 无论是否打开成功,都确保文件关闭
    if file:
        file.close()
        print("文件已关闭")

# 输出(文件不存在时):
# 文件不存在
# 文件已关闭

💡 现代替代方案:对于文件、网络连接等资源,推荐用 with 语句(自动管理资源,无需手动 close),但 finally 仍适用于 with 无法覆盖的场景(如自定义锁资源)。

三、抛出异常:raise

raise 用于主动触发异常,通常在 “检测到非法逻辑” 时使用(如参数不合法、业务规则违反等),让错误在合适的时机暴露。

1. 基本用法:抛出内置异常

语法:raise 异常类型(错误信息)

def withdraw(amount):
    # 业务规则:取款金额不能为负
    if amount < 0:
        # 主动抛出 ValueError,附带错误信息
        raise ValueError("取款金额不能为负数")
    print(f"取款 {amount} 元成功")

# 调用函数,触发异常
try:
    withdraw(-100)
except ValueError as e:
    print(f"操作失败:{e}")  # 输出:操作失败:取款金额不能为负数

2. 重新抛出异常:传递错误

有时需要 “捕获异常后,向上传递错误”(如底层函数捕获异常,记录日志后,让上层函数处理),此时可在 except 块中直接 raise(不指定异常类型,保留原异常信息)。

def read_file():
    try:
        with open("data.txt", "r") as f:
            return f.read()
    except FileNotFoundError as e:
        # 记录日志(底层操作),然后重新抛出异常(让上层处理)
        print(f"日志:{e}")
        raise  # 重新抛出原异常,不改变异常类型

# 上层函数捕获重新抛出的异常
try:
    read_file()
except FileNotFoundError:
    print("用户提示:文件不存在,请检查路径")

# 输出:
# 日志:[Errno 2] No such file or directory: 'data.txt'
# 用户提示:文件不存在,请检查路径

3. 自定义异常类

若内置异常类型无法满足业务需求(如 “用户权限不足”“订单状态错误”),可自定义异常类(继承 Exception 类)。

# 自定义异常类(继承 Exception)
class PermissionError(Exception):
    """自定义异常:用户权限不足"""
    pass

def access_resource(user_role):
    if user_role != "admin":
        # 抛出自定义异常
        raise PermissionError("只有管理员可访问此资源")

# 捕获自定义异常
try:
    access_resource("user")
except PermissionError as e:
    print(f"访问失败:{e}")  # 输出:访问失败:只有管理员可访问此资源

四、异常处理的最佳实践

  1. 捕获具体异常,避免过宽:优先捕获 ValueErrorFileNotFoundError 等具体异常,而非直接用 Exception,防止掩盖未知错误(如 KeyboardInterrupt)。

  2. 提供明确的错误信息:在 raiseexcept 中附带具体原因(如 “取款金额不能为负数” 而非 “参数错误”),便于调试和用户理解。

  3. 清理资源优先:用 finallywith 确保资源(文件、连接)被释放,避免泄漏。

  4. 不滥用异常:简单的条件判断(如 if x < 0)比 try...except 更高效,异常仅用于 “不可预见的错误”(如文件突然被删除、网络中断)。

  5. 记录异常日志:在 except 块中用 logging 模块记录异常详情(而非仅打印),便于后续排查问题。

    import logging
    logging.basicConfig(level=logging.ERROR)
       
    try:
        10 / 0
    except ZeroDivisionError as e:
        logging.error("除数为零", exc_info=True)  # exc_info=True 记录完整错误栈
    

五、总结

  • try...except...finally:用于 “被动捕获” 异常,核心是 “处理已发生的错误”,确保程序不崩溃并清理资源。
  • raise:用于 “主动抛出” 异常,核心是 “暴露非法逻辑”,让错误在合适的时机被处理。

合理的异常处理是编写健壮 Python 程序的关键,它能让程序在面对错误时更优雅,同时降低调试难度。

8.3 Python高阶:面向对象编程

面向对象编程(类的创建,init,self,del)

面向对象编程(OOP)的核心是通过类(Class)对象(Object) 组织代码,其中类是 “模板”,对象是类的 “实例”。在 Python 中,__init__self__del__ 是类定义中的核心要素,分别负责对象初始化、实例引用和对象销毁。以下从基础到细节详细讲解:

一、类的创建:定义 “模板”

类是对 “具有相同属性和行为的事物” 的抽象描述(如 “人”“汽车”)。用 class 关键字定义,基本语法:

class 类名:
    # 类属性(所有实例共享的变量)
    类变量 = 
    
    # 实例方法(对象的行为)
    def 方法名(self, 参数):
        # 方法体(通过 self 访问实例属性)
        pass

示例:定义一个 Person 类(描述 “人” 的模板)

class Person:
    # 类属性:所有“人”共享的特征(如物种)
    species = "人类"
    
    # 实例方法:“人”的行为(如说话)
    def speak(self):
        # self 代表实例本身,后续详解
        print(f"我叫{self.name},是{self.species}")
  • 类名规范:通常用首字母大写的驼峰式命名(如 PersonCar),与函数 / 变量的小写风格区分。
  • 类的作用:作为模板,用于创建具体的 “对象”(实例)。

二、对象的创建:实例化类

对象是类的具体 “实例”(如 “张三” 是 “人” 类的一个实例)。通过类名加括号(类似函数调用)创建,称为 “实例化”。

示例:用 Person 类创建两个对象

# 实例化:创建对象(自动调用 __init__ 方法,若未定义则使用默认)
person1 = Person()  # person1 是 Person 类的一个对象
person2 = Person()  # person2 是另一个对象

此时对象已创建,但还没有 “个性化属性”(如姓名、年龄),需要通过 __init__ 方法初始化。

三、__init__ 方法:初始化对象属性

__init__构造方法,在对象被创建(实例化)时自动调用,用于初始化对象的 “实例属性”(每个对象独有的特征,如姓名、年龄)。

1. 基本语法

class 类名:
    def __init__(self, 参数1, 参数2, ...):
        # 定义实例属性:self.属性名 = 参数
        self.属性1 = 参数1
        self.属性2 = 参数2
  • __init__ 方法名固定(前后各两个下划线,称为 “魔术方法”)。
  • 第一个参数必须是 self(代表当前创建的对象)。
  • 后续参数用于接收初始化对象时传入的值。

2. 示例:用 __init__ 初始化 Person 对象

class Person:
    species = "人类"  # 类属性(共享)
    
    # 定义 __init__ 方法,初始化实例属性 name 和 age
    def __init__(self, name, age):
        self.name = name  # 实例属性:姓名(每个对象不同)
        self.age = age    # 实例属性:年龄(每个对象不同)
    
    def speak(self):
        # 通过 self 访问实例属性和类属性
        print(f"我叫{self.name},今年{self.age}岁,是{self.species}")

# 实例化对象时,必须传入 __init__ 要求的参数(除 self 外)
person1 = Person("张三", 20)  # 创建对象时自动调用 __init__,传递 name="张三", age=20
person2 = Person("李四", 30)

# 访问对象的实例属性
print(person1.name)  # 输出:张三
print(person2.age)   # 输出:30

# 调用对象的方法
person1.speak()  # 输出:我叫张三,今年20岁,是人类
person2.speak()  # 输出:我叫李四,今年30岁,是人类
  • 关键__init__ 不是 “创建对象”,而是 “初始化对象”。对象在调用类名时已被创建,__init__ 只是给它添加属性。
  • 参数传递:实例化时的参数(如 "张三"20)会传递给 __init__nameage 参数(self 由 Python 自动传入,无需手动指定)。

四、self:引用实例本身

self 是类中实例方法的第一个参数,代表 “当前对象的引用”(类似其他语言的 this)。通过 self 可以:

  1. 访问当前对象的实例属性(如 self.nameself.age)。
  2. 调用当前对象的其他实例方法(如 self.speak())。

为什么需要 self

类是模板,可创建多个对象(如 person1person2)。self 用于区分 “当前操作的是哪个对象”。例如: 当调用 person1.speak() 时,self 自动指向 person1,因此 self.name 就是 person1.name; 当调用 person2.speak() 时,self 自动指向 person2,因此 self.name 就是 person2.name

注意事项

  • self
    

    不是关键字,只是约定俗成的命名(可用其他名称替代,但强烈不推荐,会降低代码可读性)。

    # 不推荐:用 my_obj 代替 self(功能相同,但不符合规范)
    class Person:
        def __init__(my_obj, name):
            my_obj.name = name
    
  • 只有实例方法需要 self 作为第一个参数(类方法用 cls,静态方法无默认参数)。

五、__del__ 方法:销毁对象时的清理

__del__析构方法,在对象被销毁(垃圾回收)时自动调用,通常用于 “资源清理”(如关闭文件、释放网络连接等)。

基本语法

class 类名:
    def __del__(self):
        # 清理操作
        print(f"对象{self}被销毁")

示例:用 __del__ 跟踪对象销毁

class FileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename, "w")  # 打开文件(占用资源)
        print(f"文件{filename}已打开")
    
    def write(self, content):
        self.file.write(content)
    
    def __del__(self):
        # 销毁对象时关闭文件(释放资源)
        self.file.close()
        print(f"文件{self.filename}已关闭(对象被销毁)")

# 创建对象(打开文件)
handler = FileHandler("test.txt")
handler.write("Hello")

# 手动删除对象(触发垃圾回收,调用 __del__)
del handler  # 输出:文件test.txt已关闭(对象被销毁)

注意事项

  • 调用时机不确定:

    __del__
    

    的调用由 Python 的 垃圾回收机制 决定(当对象不再被引用时),而非手动控制。例如:

    handler = FileHandler("test.txt")
    handler = None  # 原对象不再被引用,可能触发 __del__(但时机不确定)
    
  • 避免过度依赖:现代 Python 中,资源管理更推荐用 with 语句(自动释放资源),__del__ 作为补充。

六、综合示例:完整流程

class Student:
    # 类属性:所有学生的学校
    school = "阳光中学"
    
    # 初始化:设置姓名和分数
    def __init__(self, name, score):
        self.name = name
        self.score = score
        print(f"学生{name}创建成功,分数:{score}")
    
    # 实例方法:显示学生信息
    def show_info(self):
        print(f"{self.name}{self.school}):分数{self.score}")
    
    # 析构方法:学生对象销毁时调用
    def __del__(self):
        print(f"学生{self.name}的记录已删除")

# 1. 创建对象(自动调用 __init__)
stu1 = Student("小明", 90)  # 输出:学生小明创建成功,分数:90

# 2. 调用方法(self 指向 stu1)
stu1.show_info()  # 输出:小明(阳光中学):分数90

# 3. 销毁对象(自动/手动调用 __del__)
del stu1  # 输出:学生小明的记录已删除

总结

  • :用 class 定义的 “模板”,包含类属性(共享)和实例方法(行为)。
  • 对象:类的实例,通过 类名() 创建,拥有独立的实例属性。
  • __init__:构造方法,对象创建时自动调用,用于初始化实例属性(必须有 self 参数)。
  • self:实例方法的第一个参数,代表当前对象,用于访问实例属性和方法。
  • __del__:析构方法,对象销毁时自动调用,用于资源清理(调用时机不确定)。

这些是面向对象编程的基础,掌握后可进一步学习封装、继承、多态等高级特性。

面向对象编程(类属性、实例属性、内置属性)

在面向对象编程中,属性(Attribute) 是类或对象存储数据的变量。根据归属和用途,可分为类属性实例属性内置属性三大类。理解它们的区别和用法,是掌握类设计的核心。

一、类属性:类级别的共享数据

类属性是属于类本身的属性,所有类的实例(对象)共享同一个值,不随实例变化而变化。

1. 定义与访问

类属性定义在类的顶层(不在 __init__ 等实例方法中),通过类名实例均可访问。

示例:定义一个 Car 类,用类属性记录所有汽车的 “制造商”(共享属性)

class Car:
    # 类属性:所有实例共享的制造商
    manufacturer = "全球汽车集团"  # 定义在类的顶层,不属于任何实例方法

    def __init__(self, color):
        # 实例属性(后续讲解)
        self.color = color

# 1. 通过类名访问类属性
print(Car.manufacturer)  # 输出:全球汽车集团

# 2. 通过实例访问类属性(所有实例共享同一个值)
car1 = Car("红色")
car2 = Car("蓝色")
print(car1.manufacturer)  # 输出:全球汽车集团
print(car2.manufacturer)  # 输出:全球汽车集团
2. 修改类属性

类属性只能通过类名修改,通过实例修改类属性会创建一个同名的 “实例属性”(覆盖类属性,但不影响类本身和其他实例)。

# 正确:通过类名修改类属性(影响所有实例)
Car.manufacturer = "星际汽车集团"
print(Car.manufacturer)   # 输出:星际汽车集团
print(car1.manufacturer)  # 输出:星际汽车集团(所有实例共享修改后的值)

# 错误:通过实例“修改”类属性(实际是创建实例属性)
car1.manufacturer = "本地汽车厂"  # 给 car1 新增一个实例属性 manufacturer
print(car1.manufacturer)  # 输出:本地汽车厂(实例属性覆盖类属性)
print(car2.manufacturer)  # 输出:星际汽车集团(其他实例不受影响)
print(Car.manufacturer)   # 输出:星际汽车集团(类属性本身未变)
3. 适用场景

类属性适合存储所有实例共享的常量或统计信息,例如:

  • 固定常量(如上述的制造商、物种类型);

  • 计数器(统计类的实例数量):

    class Student:
        count = 0  # 类属性:记录实例数量
      
        def __init__(self, name):
            self.name = name
            Student.count += 1  # 每次创建实例,计数器+1
      
    s1 = Student("张三")
    s2 = Student("李四")
    print(Student.count)  # 输出:2(共创建2个实例)
    

二、实例属性:对象独有的个性化数据

实例属性是属于每个实例(对象)的属性,每个对象的实例属性值独立,随对象不同而变化。

1. 定义与访问

实例属性通常在 __init__ 方法中通过 self.属性名 定义,只能通过实例对象访问或修改。

示例:为 Person 类定义 “姓名” 和 “年龄” 实例属性

class Person:
    def __init__(self, name, age):
        # 实例属性:每个对象独有的数据,用 self. 定义
        self.name = name  # 姓名(每个对象不同)
        self.age = age    # 年龄(每个对象不同)

# 创建两个实例(对象)
p1 = Person("Alice", 20)
p2 = Person("Bob", 25)

# 访问实例属性(通过实例对象)
print(p1.name)  # 输出:Alice
print(p2.age)   # 输出:25
2. 修改实例属性

实例属性是对象独有的,修改一个实例的属性不会影响其他实例

# 修改 p1 的年龄
p1.age = 21
print(p1.age)  # 输出:21(p1 被修改)
print(p2.age)  # 输出:25(p2 不受影响)
3. 动态添加实例属性

Python 允许在实例创建后动态添加实例属性(不推荐,破坏类的封装性,但体现灵活性)。

p1 = Person("Alice", 20)
p1.gender = "女"  # 动态添加实例属性 gender
print(p1.gender)  # 输出:女

p2 = Person("Bob", 25)
# print(p2.gender)  # 报错:p2 没有 gender 属性(动态属性仅属于 p1)
4. 适用场景

实例属性适合存储每个对象独有的数据,例如:

  • 实体的个性化特征(如人的姓名、年龄,汽车的颜色、里程);
  • 对象的状态(如用户的登录状态、订单的支付状态)。

三、内置属性:Python 自带的特殊属性

内置属性是 Python 为类和对象预定义的特殊属性(以双下划线 __ 开头和结尾),用于获取对象的元信息(如类名、属性字典、文档等),无需手动定义。

常用内置属性及说明
内置属性 作用 示例(基于上述 Person 类)
__dict__ 存储对象 / 类的属性字典(键值对形式) print(p1.__dict__){'name': 'Alice', 'age': 20}
__class__ 返回对象所属的类 print(p1.__class__)<class '__main__.Person'>
__doc__ 类的文档字符串("""注释""" 部分) class Person: """人类类"""print(Person.__doc__) → “人类类”
__module__ 类所在的模块名(默认 __main__ print(Person.__module__)__main__
__name__ 类的名称 print(Person.__name__)Person
__bases__ 类的父类元组(继承相关) print(Person.__bases__)(<class 'object'>,)(默认继承 object)

示例:内置属性的实际应用

class Person:
    """此类用于描述人类的基本信息"""  # __doc__ 文档字符串
    species = "人类"  # 类属性

    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Charlie", 30)

# 1. __dict__:查看属性键值对(调试常用)
print(p.__dict__)        # 实例属性:{'name': 'Charlie', 'age': 30}
print(Person.__dict__)   # 类属性及方法:包含 'species'、'__init__' 等

# 2. __class__:判断对象类型(比 type() 更直观)
print(p.__class__ is Person)  # 输出:True(p 是 Person 类的实例)

# 3. __doc__:查看类的文档说明
print(Person.__doc__)  # 输出:此类用于描述人类的基本信息

四、类属性 vs 实例属性:核心区别

维度 类属性 实例属性
归属 属于类本身 属于每个实例(对象)
定义位置 类的顶层(不在 __init__ 中) __init__ 方法中(self.属性
共享性 所有实例共享同一个值 每个实例的值独立
访问方式 类名。属性 或 实例。属性 仅实例。属性
修改方式 仅类名。属性 = 新值 实例。属性 = 新值
典型用途 共享常量、统计信息 对象个性化数据

五、总结

  • 类属性:类级别的共享数据,所有实例共用,适合存储常量或统计信息。
  • 实例属性:对象独有的个性化数据,每个实例独立,适合描述对象的特征。
  • 内置属性:Python 预定义的特殊属性,用于获取类 / 对象的元信息(如文档、属性字典),辅助调试和反射编程。

合理使用这三类属性,能让类的设计更清晰:用类属性管理共享数据,用实例属性描述对象个性,用内置属性辅助开发。

面向对象编程(类方法、实例方法、内置方法、静态方法)

在面向对象编程中,方法(Method) 是类中定义的函数,用于描述对象的行为。根据绑定对象、参数特点和用途的不同,可分为实例方法类方法静态方法内置方法(魔术方法) 四大类。理解它们的设计目的和使用场景,是掌握类功能设计的关键。

一、实例方法:绑定到对象的方法

实例方法是最常用的方法类型,直接绑定到类的实例(对象),用于操作实例的属性和行为,是对象 “个性化功能” 的核心载体。

1. 定义与特点
  • 定义:在类中直接定义,第一个参数必须是 self(代表当前实例对象,类似其他语言的 this)。
  • 绑定对象:属于实例,必须通过实例对象调用(调用时 self 由 Python 自动传入,无需手动指定)。
  • 访问权限:可直接访问实例属性self.属性)和类属性self.类属性类名.类属性),也可调用其他实例方法、类方法和静态方法。
2. 示例:Person 类的实例方法
class Person:
    species = "人类"  # 类属性

    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性

    # 实例方法:描述对象的行为(说话)
    def speak(self):
        # 访问实例属性(self.name)和类属性(self.species)
        return f"我叫{self.name},今年{self.age}岁,是{self.species}。"

    # 实例方法:修改实例属性(增长年龄)
    def grow(self, years=1):
        self.age += years  # 操作实例属性
        return f"现在{self.age}岁了。"

# 创建实例
p = Person("张三", 20)

# 调用实例方法(通过实例对象)
print(p.speak())  # 输出:我叫张三,今年20岁,是人类。
print(p.grow())   # 输出:现在21岁了。
3. 适用场景

实例方法用于实现与对象状态相关的行为,即依赖实例属性的功能,例如:

  • 操作对象的属性(如修改年龄、更新姓名);
  • 实现对象的核心行为(如 “说话”“行走”“计算面积” 等)。

二、类方法:绑定到类的方法

类方法绑定到类本身,而非实例,主要用于操作类属性或实现与类相关的功能(不依赖实例状态)。

1. 定义与特点
  • 定义:用 @classmethod 装饰器声明,第一个参数必须是 cls(代表当前类本身,约定俗成的命名)。
  • 绑定对象:属于类,可通过类名实例对象调用(调用时 cls 由 Python 自动传入)。
  • 访问权限:可直接访问类属性cls.属性),但不能直接访问实例属性(需通过实例对象参数传递)。
2. 示例:Student 类的类方法
class Student:
    school = "阳光中学"  # 类属性
    count = 0  # 类属性:统计学生数量

    def __init__(self, name):
        self.name = name  # 实例属性
        Student.count += 1  # 每次创建实例,计数器+1

    # 类方法:修改类属性(更新学校名称)
    @classmethod
    def update_school(cls, new_school):
        cls.school = new_school  # 操作类属性
        return f"学校已更改为:{cls.school}"

    # 类方法:创建“替代构造函数”(从字符串解析姓名)
    @classmethod
    def from_str(cls, name_str):
        # 处理输入(如去除空格)
        name = name_str.strip()
        return cls(name)  # 返回新实例(等价于 Student(name))

# 通过类名调用类方法(推荐)
print(Student.update_school("星光中学"))  # 输出:学校已更改为:星光中学

# 创建实例(验证计数器)
s1 = Student("李四")
s2 = Student.from_str(" 王五 ")  # 通过类方法创建实例
print(Student.count)  # 输出:2(共创建2个实例)

# 通过实例调用类方法(可行,但逻辑上不推荐)
print(s1.update_school("未来中学"))  # 输出:学校已更改为:未来中学
3. 适用场景

类方法用于实现与类状态相关的行为,即依赖类属性的功能,例如:

  • 操作类属性(如更新共享配置、统计实例数量);
  • 实现 “替代构造函数”(提供多种创建实例的方式,如从字符串、字典解析参数);
  • 定义与类相关的工具函数(不依赖实例属性)。

三、静态方法:与类和实例无关的方法

静态方法是定义在类中的普通函数,既不绑定到实例,也不绑定到类,仅为了代码组织(将相关函数放在类中)而存在。

1. 定义与特点
  • 定义:用 @staticmethod 装饰器声明,没有默认参数(既不需要 self,也不需要 cls)。
  • 绑定对象:与类和实例均无关,可通过类名实例对象调用(调用时无需传递额外参数)。
  • 访问权限不能直接访问类属性或实例属性(除非通过参数显式传递类或实例对象)。
2. 示例:MathUtil 类的静态方法
class MathUtil:
    # 静态方法:计算两数之和(与类/实例属性无关)
    @staticmethod
    def add(a, b):
        return a + b

    # 静态方法:判断是否为偶数(工具函数)
    @staticmethod
    def is_even(num):
        return num % 2 == 0

# 通过类名调用静态方法(推荐)
print(MathUtil.add(2, 3))      # 输出:5
print(MathUtil.is_even(4))     # 输出:True

# 通过实例调用静态方法(可行,但无意义)
mu = MathUtil()
print(mu.add(5, 6))            # 输出:11
3. 适用场景

静态方法用于实现与类和实例状态均无关的工具函数,例如:

  • 通用计算(如加减乘除、数据验证);
  • 与类逻辑相关但不依赖类 / 实例属性的辅助功能(如格式转换、条件判断)。

本质上,静态方法等价于 “放在类里的普通函数”,仅为了代码组织更清晰(避免创建独立的工具模块)。

四、内置方法(魔术方法):特殊功能的预定义方法

内置方法(Magic Methods)是 Python 预定义的特殊方法,以双下划线 __ 开头和结尾(如 __init____str__),由 Python 解释器在特定场景下自动调用,用于实现类的特殊行为(如初始化、字符串表示、运算符重载等)。

1. 特点与核心作用
  • 自动调用:无需手动调用,在特定操作时由 Python 自动触发(如创建实例时调用 __init__,打印对象时调用 __str__)。
  • 功能特殊:覆盖 Python 的默认行为(如自定义对象的字符串格式、实现对象的比较逻辑)。
2. 常用内置方法示例
内置方法 触发时机 作用示例
__init__ 创建实例时(类名() 初始化实例属性(构造方法)
__str__ 调用 str(对象)print(对象) 定义对象的 “友好字符串表示”
__repr__ 调用 repr(对象) 或交互式环境输出 定义对象的 “官方字符串表示”(用于调试)
__len__ 调用 len(对象) 定义对象的 “长度”(如列表的长度)
__add__ 调用 对象 + 其他对象 重载 “+” 运算符
__del__ 对象被销毁时 资源清理(析构方法)
3. 示例:自定义 Book 类的内置方法
class Book:
    def __init__(self, title, price):
        self.title = title
        self.price = price

    # 自定义字符串表示(print() 时调用)
    def __str__(self):
        return f"《{self.title}》(价格:{self.price}元)"

    # 自定义官方表示(调试时调用)
    def __repr__(self):
        return f"Book(title='{self.title}', price={self.price})"

    # 重载“+”运算符(合并两本书的价格)
    def __add__(self, other):
        return self.price + other.price

# 创建对象
book1 = Book("Python入门", 50)
book2 = Book("Python进阶", 70)

# 触发 __str__(print 时)
print(book1)  # 输出:《Python入门》(价格:50元)

# 触发 __repr__(交互式环境或直接输出)
print(repr(book2))  # 输出:Book(title='Python进阶', price=70)

# 触发 __add__(使用 + 运算符)
print(book1 + book2)  # 输出:120(50 + 70)

五、四种方法的核心区别对比

方法类型 装饰器 第一个参数 绑定对象 可访问属性 调用方式 典型用途
实例方法 self 实例 实例属性、类属性 实例。方法 () 操作实例状态(如 speak()
类方法 @classmethod cls 类属性(无实例属性) 类名。方法 () / 实例。方法 () 操作类状态、替代构造函数
静态方法 @staticmethod 无(需显式传递) 类名。方法 () / 实例。方法 () 工具函数(如 add()
内置方法 无(双下划线) 通常有 self 实例 / 类 实例属性、类属性 自动触发(如 print() 实现特殊功能(如 __str__

六、总结

  • 实例方法:绑定实例,依赖实例属性,实现对象的核心行为(最常用)。
  • 类方法:绑定类,依赖类属性,实现类级别的功能(如统计、替代构造)。
  • 静态方法:无绑定,独立于类和实例,仅为代码组织的工具函数。
  • 内置方法:预定义特殊方法,自动触发,实现类的特殊行为(如初始化、运算符重载)。

在类设计中,应根据功能是否依赖实例状态、类状态或完全独立,选择合适的方法类型,使代码逻辑更清晰、职责更明确。

面向对象三大特性:封装、继承(继承的好处和多继承)、多态

面向对象编程(OOP)的三大核心特性是封装继承多态,它们共同支撑了 OOP 的灵活性、可复用性和可扩展性。以下从概念、作用和实例三个维度详细解析:

一、封装(Encapsulation):隐藏细节,暴露接口

封装是指将对象的属性和方法捆绑在一起,隐藏内部实现细节,只通过公共接口与外部交互。其核心思想是 “数据隐藏”,防止外部随意修改对象内部状态,同时简化外部使用。

1. 核心目的
  • 安全性:限制外部对对象内部属性的直接访问,避免非法修改(如年龄不能为负数)。
  • 易用性:外部只需调用公共方法,无需关心内部实现(如使用手机时无需知道芯片工作原理)。
2. 实现方式(Python)

Python 通过命名约定访问控制实现封装:

  • 私有属性 / 方法:以双下划线 __ 开头(如 __age),Python 会对其进行 “名称修饰”(变为 _类名__属性),阻止外部直接访问(并非绝对私有,是一种约定)。
  • 公共接口:定义公开方法(如 get_age()set_age()),用于安全地访问和修改私有属性。
3. 示例:封装 Person 类的年龄属性
class Person:
    def __init__(self, name, age):
        self.name = name  # 公开属性(可直接访问)
        self.__age = age  # 私有属性(禁止外部直接访问)

    # 公共接口:获取年龄(读操作)
    def get_age(self):
        return self.__age

    # 公共接口:修改年龄(写操作,带校验)
    def set_age(self, new_age):
        if new_age < 0 or new_age > 150:  # 限制年龄范围
            raise ValueError("年龄必须在0-150之间")
        self.__age = new_age

    # 公开方法:展示信息
    def show(self):
        return f"{self.name}{self.__age}岁"

# 使用封装的类
p = Person("张三", 20)

# 正确:通过公共接口访问/修改私有属性
print(p.get_age())  # 输出:20
p.set_age(21)
print(p.show())     # 输出:张三,21岁

# 错误:直接访问私有属性(会触发异常或返回错误结果)
# print(p.__age)  # 报错:AttributeError: 'Person' object has no attribute '__age'

二、继承(Inheritance):复用代码,扩展功能

继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在此基础上添加新属性 / 方法或重写父类方法。父类(超类)是被继承的类,子类(派生类)是继承的类。

1. 继承的好处
  • 代码复用:子类无需重复编写父类已有的代码(如所有动物都有 “姓名” 和 “吃” 的方法,子类直接继承)。
  • 功能扩展:子类可以在父类基础上添加新功能(如 “狗” 继承 “动物” 后,新增 “吠叫” 方法)。
  • 层级清晰:通过继承构建类的层级关系(如 “生物→动物→哺乳动物→狗”),符合现实世界的分类逻辑。
2. 基本实现(单继承)

Python 中用 class 子类名(父类名): 表示继承,子类通过 super() 调用父类的方法。

示例:动物类(父类)与狗类(子类)

# 父类:动物(基础属性和方法)
class Animal:
    def __init__(self, name):
        self.name = name  # 所有动物都有姓名

    def eat(self):  # 所有动物都能吃
        return f"{self.name}在吃东西"

# 子类:狗(继承自动物,扩展新功能)
class Dog(Animal):
    def bark(self):  # 子类新增方法(狗会吠叫)
        return f"{self.name}在汪汪叫"

    def eat(self):  # 重写父类方法(狗的吃的行为)
        return f"{self.name}在啃骨头"  # 覆盖父类的"吃东西"逻辑

# 使用子类
dog = Dog("旺财")
print(dog.name)    # 继承父类的属性:旺财
print(dog.eat())   # 调用重写后的方法:旺财在啃骨头
print(dog.bark())  # 调用子类新增方法:旺财在汪汪叫
3. 多继承:一个子类继承多个父类

Python 允许一个子类同时继承多个父类(class 子类(父类1, 父类2):),从而复用多个类的功能。但多继承可能导致方法名冲突(多个父类有同名方法时,子类该调用哪个?)。

(1)多继承的语法与问题

# 父类1:能跑
class Runnable:
    def move(self):
        return "正在跑步"

# 父类2:能游
class Swimmable:
    def move(self):
        return "正在游泳"

# 子类:既能跑又能游(多继承)
class Amphibian(Runnable, Swimmable):
    pass  # 未重写move方法

# 调用move():到底用Runnable还是Swimmable的方法?
a = Amphibian()
print(a.move())  # 输出:正在跑步(优先使用第一个父类的方法)

(2)解决冲突:方法解析顺序(MRO)

Python 通过MRO(Method Resolution Order) 解决多继承的方法冲突,即子类会按特定顺序(从左到右、深度优先)搜索父类的方法。可通过 子类名.__mro__ 查看顺序:

print(Amphibian.__mro__)
# 输出:(<class '__main__.Amphibian'>, <class '__main__.Runnable'>, <class '__main__.Swimmable'>, <class 'object'>)

顺序为:Amphibian → Runnable → Swimmable → objectobject 是所有类的根父类),因此 a.move() 优先调用 Runnablemove()

(3)多继承的使用原则

  • 谨慎使用:多继承会增加代码复杂度,若非必要(如类确实需要多个父类的功能),优先用单继承 + 组合。
  • 明确 MRO:当多继承不可避免时,需清楚方法搜索顺序,避免隐式错误。

三、多态(Polymorphism):同一接口,不同实现

多态是指不同对象对同一方法(接口)有不同的实现,调用时无需关心对象具体类型,只需通过统一接口调用,自动适配不同实现。其核心是 “接口统一,行为多样”。

1. 实现条件
  • 继承:多态基于继承,子类继承父类的方法。
  • 重写:子类重写父类的方法(提供不同实现)。
  • 向上转型:将子类对象赋值给父类类型的变量(Python 自动支持,无需显式声明)。
2. 示例:动物叫声的多态
# 父类:动物(定义统一接口make_sound)
class Animal:
    def make_sound(self):
        # 父类方法:可定义默认行为或抽象(无实现)
        raise NotImplementedError("子类必须重写此方法")

# 子类1:猫(重写make_sound)
class Cat(Animal):
    def make_sound(self):
        return "喵喵喵"

# 子类2:狗(重写make_sound)
class Dog(Animal):
    def make_sound(self):
        return "汪汪汪"

# 子类3:鸭(重写make_sound)
class Duck(Animal):
    def make_sound(self):
        return "嘎嘎嘎"

# 统一接口:接收Animal类型对象,调用其make_sound
def animal_sound(animal):
    print(animal.make_sound())  # 无需关心animal是猫/狗/鸭,自动调用对应实现

# 测试多态:传入不同子类对象,接口一致,结果不同
animal_sound(Cat())  # 输出:喵喵喵
animal_sound(Dog())  # 输出:汪汪汪
animal_sound(Duck()) # 输出:嘎嘎嘎
3. 多态的好处
  • 灵活性:新增子类(如 “猪”)时,无需修改 animal_sound 函数,直接传入新对象即可(符合 “开闭原则”:对扩展开放,对修改关闭)。
  • 简洁性:调用者只需关注统一接口(make_sound),无需记忆不同类的具体方法名。

四、三大特性的关系总结

  • 封装是基础:通过隐藏细节保证数据安全,为继承和多态提供可靠的 “原子单元”。
  • 继承是骨架:通过复用代码构建类的层级,为多态提供 “接口统一” 的基础(父类定义接口,子类继承)。
  • 多态是灵魂:通过统一接口适配不同实现,最大化发挥继承的灵活性,使代码更易扩展。

三者协同工作,使面向对象编程能够构建出模块化、可复用、易维护的大型系统。

Python常用第三方库的应用

Python 的强大之处在于其丰富的第三方库生态,这些库覆盖了 Web 开发、数据分析、自动化、机器学习等几乎所有领域。以下按常用场景分类介绍核心第三方库的应用及基础示例:

一、Web 开发

1. Flask:轻量级 Web 框架

用途:快速开发小型 Web 应用、API 接口,灵活轻量,适合初学者。 核心特点:无固定目录结构,可按需扩展,依赖 Werkzeug(WSGI 工具)和 Jinja2(模板引擎)。

示例:创建一个简单的 Hello World 接口

from flask import Flask

app = Flask(__name__)  # 初始化 Flask 应用

# 定义路由:访问根路径时执行
@app.route('/')
def hello():
    return "Hello, Flask!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)  # 启动服务器,端口 5000

运行后访问 http://localhost:5000 即可看到响应。

2. Django:全栈 Web 框架

用途:开发大型 Web 应用(如电商平台、内容管理系统),内置 ORM、Admin 后台、用户认证等功能。 核心特点:“batteries-included”(开箱即用),遵循 MVC 架构(Django 中称为 MVT)。

示例:创建一个简单的视图(需先通过 django-admin startproject 初始化项目)

# myapp/views.py
from django.http import HttpResponse

def home(request):
    return HttpResponse("Hello, Django!")

# myproject/urls.py 中配置路由
from django.urls import path
from myapp import views

urlpatterns = [
    path('', views.home),  # 根路径映射到 home 视图
]
3. FastAPI:高性能 API 框架

用途:构建高性能、自动生成文档的 API(支持异步),适合后端服务、微服务。 核心特点:基于 Python 类型提示,自动生成 Swagger 文档,性能接近 Node.js。

示例:创建一个接收参数的 API

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):  # 自动校验参数类型
    return {"item_id": item_id, "query": q}

# 运行:uvicorn main:app --reload,访问 http://localhost:8000/docs 查看自动生成的文档

二、数据分析与科学计算

1. pandas:数据处理核心库

用途:处理结构化数据(CSV、Excel、数据库表等),支持清洗、筛选、分组、合并等操作。 核心特点:基于 DataFrame(二维表)和 Series(一维数组)数据结构,操作简洁高效。

示例:读取 CSV 并分析数据

import pandas as pd

# 读取 CSV 文件
df = pd.read_csv("data.csv")

# 查看前 5 行
print(df.head())

# 筛选年龄 > 30 的数据
filtered = df[df["age"] > 30]

# 按性别分组统计平均年龄
grouped = df.groupby("gender")["age"].mean()
print(grouped)
2. numpy:数值计算基础库

用途:处理多维数组(矩阵)、数学运算(线性代数、傅里叶变换等),是 pandasmatplotlib 等库的基础。 核心特点:用 C 语言实现,运算速度远快于 Python 原生列表。

示例:数组运算

import numpy as np

# 创建数组
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 元素级运算
print(a + b)  # 输出:[5 7 9]

# 矩阵乘法
mat1 = np.array([[1, 2], [3, 4]])
mat2 = np.array([[5, 6], [7, 8]])
print(mat1 @ mat2)  # 输出:[[19 22], [43 50]]

三、数据可视化

1. matplotlib:基础可视化库

用途:绘制折线图、柱状图、散点图等基础图表,支持自定义样式。 核心特点:功能全面,可控制图表每一个细节,是其他可视化库的基础。

示例:绘制折线图

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)  # 0-10 之间生成 100 个点
y = np.sin(x)  # 计算正弦值

plt.plot(x, y, label="sin(x)")  # 绘制折线
plt.xlabel("x")  # x 轴标签
plt.ylabel("y")  # y 轴标签
plt.title("Sine Wave")  # 标题
plt.legend()  # 显示图例
plt.show()  # 展示图表
2. seaborn:统计可视化库

用途:基于 matplotlib,简化统计图表绘制(如热力图、箱线图、小提琴图),样式更美观。

示例:绘制鸢尾花数据集的散点图矩阵

import seaborn as sns
import matplotlib.pyplot as plt

# 加载内置数据集
iris = sns.load_dataset("iris")

# 绘制散点图矩阵(不同特征间的关系)
sns.pairplot(iris, hue="species")  # 按 species 分组着色
plt.show()

四、自动化与工具

1. requests:HTTP 网络请求库

用途:发送 HTTP 请求(GET、POST 等),爬取网页数据、调用 API 接口。 核心特点:API 简洁,比内置 urllib 更易用。

示例:获取网页内容并调用 API

import requests

# 发送 GET 请求获取网页
response = requests.get("https://www.baidu.com")
print(response.text[:100])  # 输出前 100 个字符

# 发送 POST 请求调用 API
data = {"name": "Alice", "age": 30}
response = requests.post("https://api.example.com/user", json=data)
print(response.json())  # 解析 JSON 响应
2. python-docx:操作 Word 文档

用途:创建、修改 Word 文档(.docx),支持添加文本、表格、图片等。

示例:创建一个含表格的 Word 文档

from docx import Document
from docx.shared import Inches

doc = Document()  # 新建文档
doc.add_heading("成绩单", level=1)  # 添加标题

# 添加表格(3 行 3 列)
table = doc.add_table(rows=3, cols=3)
table.cell(0, 0).text = "姓名"
table.cell(0, 1).text = "科目"
table.cell(0, 2).text = "成绩"

# 填充数据
table.cell(1, 0).text = "张三"
table.cell(1, 1).text = "数学"
table.cell(1, 2).text = "90"

doc.save("成绩单.docx")  # 保存文档
3. PyPDF2/pdfplumber:PDF 处理
  • PyPDF2:合并、拆分、加密 PDF 文件。
  • pdfplumber:提取 PDF 中的文本和表格(精度更高)。

示例:用 pdfplumber 提取文本

import pdfplumber

with pdfplumber.open("example.pdf") as pdf:
    page = pdf.pages[0]  # 获取第一页
    text = page.extract_text()  # 提取文本
    print(text)

五、机器学习与人工智能

1. scikit-learn:机器学习入门库

用途:实现分类、回归、聚类等经典机器学习算法,提供数据预处理、模型评估工具。

示例:用决策树分类 Iris 数据集

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target  # 特征和标签

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 训练模型
model = DecisionTreeClassifier()
model.fit(X_train, y_train)

# 评估准确率
print(f"准确率:{model.score(X_test, y_test):.2f}")
2. tensorflow/pytorch:深度学习框架
  • tensorflow(Google):适合生产环境,支持分布式训练,Keras 接口简洁。
  • pytorch(Facebook):动态计算图,调试友好,科研常用。

示例:用 pytorch 实现简单神经网络

import torch
import torch.nn as nn

# 定义神经网络
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(20, 2)  # 输入 20 维,输出 2 维

    def forward(self, x):
        return self.fc(x)

model = SimpleNet()
x = torch.randn(32, 20)  # 32 个样本,每个 20 维
output = model(x)  # 前向传播
print(output.shape)  # 输出:torch.Size([32, 2])

六、安装第三方库的通用方法

所有第三方库均可通过 pip 安装:

pip install 库名  # 安装最新版
pip install 库名==版本号  # 安装指定版本(如 pip install pandas==1.5.3)

总结

Python 第三方库极大扩展了其应用范围:

  • Web 开发:Flask(轻量)、Django(全栈)、FastAPI(高性能)。
  • 数据处理:pandas(结构化数据)、numpy(数值计算)。
  • 可视化:matplotlib(基础)、seaborn(统计)。
  • 自动化:requests(网络)、python-docx(Word)、PyPDF2(PDF)。
  • 机器学习:scikit-learn(传统算法)、tensorflow/pytorch(深度学习)。

根据具体场景选择合适的库,可大幅提升开发效率。

Python面向对象和异常以及应用

Python 中,面向对象编程(OOP)异常处理是构建健壮、可维护程序的两大核心技术。面向对象通过封装、继承、多态实现代码的模块化与复用;异常处理则通过捕获和处理错误,确保程序在意外情况下仍能优雅运行。二者结合能构建出既灵活又可靠的系统,以下通过具体场景和实例详解其应用。

一、面向对象与异常处理的协同设计

面向对象的核心是 “抽象实体”,而异常处理则是 “定义实体操作中的错误规则”。例如,设计一个 “银行账户” 系统时:

  • 用类封装账户的属性(余额、账号)和行为(存款、取款);
  • 用异常处理定义 “取款金额为负”“余额不足” 等错误场景。

二、实例:图书管理系统(面向对象 + 异常处理)

假设我们需要设计一个简单的图书管理系统,包含以下功能:

  • 图书(Book):有编号、名称、状态(可借 / 已借);
  • 图书馆(Library):管理图书的添加、借阅、归还;
  • 自定义异常:处理 “图书不存在”“图书已借出” 等错误。
1. 定义自定义异常(错误类型)

先定义系统中可能出现的异常类型,使错误处理更清晰:

# 自定义异常:图书不存在
class BookNotFoundError(Exception):
    def __init__(self, book_id):
        self.book_id = book_id
        super().__init__(f"图书编号 {book_id} 不存在")

# 自定义异常:图书已借出
class BookAlreadyBorrowedError(Exception):
    def __init__(self, book_id):
        self.book_id = book_id
        super().__init__(f"图书编号 {book_id} 已借出")

# 自定义异常:图书未借出(归还时错误)
class BookNotBorrowedError(Exception):
    def __init__(self, book_id):
        self.book_id = book_id
        super().__init__(f"图书编号 {book_id} 未借出,无法归还")
2. 设计 Book 类(封装图书信息)

用类封装图书的属性和状态,通过方法控制状态变更(确保状态合法性):

class Book:
    def __init__(self, book_id, name):
        self.book_id = book_id  # 图书编号(唯一)
        self.name = name        # 图书名称
        self._is_borrowed = False  # 状态:是否已借出(私有属性,封装状态)

    # 检查是否可借
    def is_available(self):
        return not self._is_borrowed

    # 借出图书(内部方法,仅图书馆可调用)
    def _borrow(self):
        if self._is_borrowed:
            raise BookAlreadyBorrowedError(self.book_id)  # 状态非法时抛异常
        self._is_borrowed = True

    # 归还图书(内部方法,仅图书馆可调用)
    def _return(self):
        if not self._is_borrowed:
            raise BookNotBorrowedError(self.book_id)  # 状态非法时抛异常
        self._is_borrowed = False

    # 重写 __str__,友好展示图书信息
    def __str__(self):
        status = "已借出" if self._is_borrowed else "可借阅"
        return f"编号:{self.book_id},名称:《{self.name}》,状态:{status}"
  • 封装:用私有属性 _is_borrowed 隐藏状态,通过 _borrow()_return() 控制状态变更(带校验),外部无法直接修改状态,确保数据安全。
3. 设计 Library 类(管理图书,实现核心功能)

图书馆类负责图书的添加、借阅、归还等操作,调用 Book 类的方法时捕获并处理异常:

class Library:
    def __init__(self):
        self._books = {}  # 用字典存储图书:{book_id: Book对象},键为编号(快速查找)

    # 添加图书
    def add_book(self, book):
        if book.book_id in self._books:
            print(f"警告:图书编号 {book.book_id} 已存在,未重复添加")
            return
        self._books[book.book_id] = book
        print(f"已添加图书:{book}")

    # 借阅图书
    def borrow_book(self, book_id):
        # 1. 检查图书是否存在
        if book_id not in self._books:
            raise BookNotFoundError(book_id)  # 抛出自定义异常
        
        # 2. 尝试借出
        book = self._books[book_id]
        try:
            book._borrow()  # 调用Book的借出方法(可能抛异常)
            print(f"借阅成功:{book}")
        except BookAlreadyBorrowedError as e:
            print(f"借阅失败:{e}")  # 捕获并处理异常

    # 归还图书
    def return_book(self, book_id):
        if book_id not in self._books:
            raise BookNotFoundError(book_id)
        
        book = self._books[book_id]
        try:
            book._return()  # 调用Book的归还方法(可能抛异常)
            print(f"归还成功:{book}")
        except BookNotBorrowedError as e:
            print(f"归还失败:{e}")

    # 显示所有图书
    def show_all_books(self):
        print("\n图书馆藏书:")
        for book in self._books.values():
            print(book)
  • 逻辑分层Book 类负责维护自身状态(单一职责),Library 类负责管理图书集合和业务流程,符合面向对象的 “高内聚低耦合” 原则。
  • 异常处理:在 borrow_bookreturn_book 中,通过 try...except 捕获 Book 类抛出的异常,并给用户友好提示,避免程序崩溃。
4. 系统使用示例(多态与异常的实际交互)
if __name__ == "__main__":
    # 创建图书馆
    lib = Library()

    # 添加图书
    lib.add_book(Book(1, "Python编程"))
    lib.add_book(Book(2, "数据结构与算法"))
    lib.add_book(Book(1, "重复的图书"))  # 测试重复添加

    # 显示所有图书
    lib.show_all_books()

    # 测试借阅
    lib.borrow_book(1)    # 借阅成功
    lib.borrow_book(1)    # 测试重复借阅(已借出)
    lib.borrow_book(3)    # 测试借阅不存在的图书

    # 测试归还
    lib.return_book(1)    # 归还成功
    lib.return_book(1)    # 测试归还未借出的图书

输出结果

已添加图书:编号:1,名称:《Python编程》,状态:可借阅
已添加图书:编号:2,名称:《数据结构与算法》,状态:可借阅
警告:图书编号 1 已存在,未重复添加

图书馆藏书:
编号:1,名称:《Python编程》,状态:可借阅
编号:2,名称:《数据结构与算法》,状态:可借阅

借阅成功:编号:1,名称:《Python编程》,状态:已借出
借阅失败:图书编号 1 已借出
BookNotFoundError: 图书编号 3 不存在

归还成功:编号:1,名称:《Python编程》,状态:可借阅
归还失败:图书编号 1 未借出,无法归还

三、面向对象与异常处理结合的核心优势

  1. 代码模块化: 用类封装实体(Book)和管理逻辑(Library),每个类职责明确,便于维护和扩展(如后续添加 “电子书” 类,可通过继承 Book 实现,符合继承特性)。
  2. 错误边界清晰: 自定义异常(如 BookNotFoundError)使错误类型可区分,开发者能针对性处理;异常在方法中抛出、在调用处捕获,符合 “谁调用谁处理” 的原则。
  3. 程序健壮性: 即使出现错误(如借阅不存在的图书),程序也能捕获异常并输出友好提示,而非直接崩溃,提升用户体验。
  4. 可扩展性: 若需新增功能(如 “预约图书”),只需在 Library 中添加新方法,并定义对应的异常(如 BookAlreadyReservedError),原有逻辑无需修改(符合开闭原则)。

四、总结

面向对象编程通过封装、继承、多态构建清晰的代码结构,实现 “实体抽象” 和 “代码复用”;异常处理则通过捕获和抛出异常,定义 “错误规则” 并保证程序稳定性。二者结合是开发复杂系统的基础 —— 例如:

  • 电商系统中,用类封装 “订单”“商品”,用异常处理 “库存不足”“支付失败”;
  • 游戏开发中,用类定义 “角色”“道具”,用异常处理 “技能冷却中”“背包已满”。

掌握这种协同设计思路,能显著提升代码的可维护性和健壮性。

为何使用if name == “main”

在 Python 中,if __name__ == "__main__": 是一个非常重要的代码结构,它的核心作用是区分模块的两种使用场景:当模块被直接运行时执行特定代码,当模块被导入到其他模块时不执行这些代码。

一、先理解 __name__ 变量

__name__ 是 Python 的一个内置变量,它的值取决于模块的使用方式

  • 当一个模块(.py 文件)被直接运行时(比如在命令行执行 python script.py),Python 会将该模块的 __name__ 变量赋值为 "__main__"
  • 当一个模块被导入到其他模块时(比如在 a.py 中执行 import b),被导入模块(b.py)的 __name__ 变量会被赋值为模块名(即 "b")。
二、if __name__ == "__main__": 的作用

这个条件判断的含义是:当模块被直接运行时,执行缩进块内的代码;当模块被导入时,不执行这些代码

它的核心价值是让一个模块同时具备 “可直接运行” 和 “可被导入复用” 的双重角色

三、举个例子:直观理解

假设我们有一个 calculator.py 文件,内容如下:

# calculator.py

def add(a, b):
    return a + b

# 直接运行时会执行的代码
print("模块的 __name__ 值:", __name__)

if __name__ == "__main__":
    # 这个块内的代码,只有直接运行时才会执行
    print("calculator 被直接运行了!")
    print("1 + 2 =", add(1, 2))

场景 1:直接运行 calculator.py

在命令行执行 python calculator.py,输出:

模块的 __name__ 值:__main__
calculator 被直接运行了!
1 + 2 = 3

此时 __name__"__main__",所以 if 条件成立,执行了块内的代码(打印提示、调用 add 函数)。

场景 2:将 calculator.py 导入到其他模块

创建 test.py,导入 calculator

# test.py
import calculator  # 导入 calculator 模块

print("在 test.py 中,calculator 的 __name__ 是:", calculator.__name__)
print("调用 calculator.add(3, 4) =", calculator.add(3, 4))

执行 python test.py,输出:

模块的 __name__ 值:calculator  # 因为 calculator 被导入,__name__ 为模块名
在 test.py 中,calculator 的 __name__ 是:calculator
调用 calculator.add(3, 4) = 7

此时 calculator.py 中的 if __name__ == "__main__": 条件不成立(因为 __name__"calculator"),所以块内的代码(print("calculator 被直接运行了!") 等)没有执行。

四、为什么需要这个结构?

  1. 区分 “运行” 和 “导入” 场景 一个模块可能既需要作为独立脚本运行(比如执行测试、展示功能),又需要被其他模块导入复用(比如提供工具函数)。if __name__ == "__main__": 可以让 “运行时的逻辑”(如测试代码、主程序)只在直接运行时执行,避免被导入时干扰其他模块。
  2. 存放测试代码 开发者常将模块的测试代码放在这个块内。例如 calculator.py 中的 add 函数,直接运行时可以通过块内代码测试功能,而被导入时测试代码不会执行,不影响其他模块。
  3. 作为程序入口 对于复杂程序,通常会将主逻辑放在这个块内,使模块成为程序的 “入口文件”(类似其他语言的 main 函数)。

五、总结

if __name__ == "__main__": 是 Python 中实现 “模块复用性” 和 “运行独立性” 的关键机制:

  • 当模块被直接运行时,__name__"__main__",执行块内代码(如主逻辑、测试)。
  • 当模块被导入时,__name__ 为模块名,块内代码不执行,只提供函数 / 类供其他模块使用。

这使得 Python 模块既能作为独立程序运行,又能作为库被灵活复用,是编写高质量 Python 代码的基础技巧。

Python面向对象异常详解(类的缺陷,自定义缺陷)

在 Python 面向对象编程中,“类的缺陷” 通常指类在设计或运行时出现的不符合预期的状态或操作(如无效属性值、不支持的方法调用、状态冲突等)。这些 “缺陷” 需要通过异常机制来捕获和处理,而自定义异常是面向对象中处理类特定缺陷的核心手段 —— 它能让错误信息更精准、业务逻辑更清晰。

一、类的常见 “缺陷” 场景

类在使用过程中可能出现的 “缺陷” 本质上是违背类设计逻辑的操作或状态,常见场景包括:

  1. 属性值非法:类的属性被设置为不符合业务规则的值(如年龄为负数、价格为 0 以下)。
  2. 方法调用时机错误:在不恰当的状态下调用方法(如对已关闭的文件调用read(),对已提交的订单调用cancel())。
  3. 资源冲突:多个操作竞争同一资源导致的冲突(如两个线程同时修改同一条数据)。
  4. 不支持的操作:对类实例调用其不支持的方法(如对 “只读文件” 调用write())。

这些 “缺陷” 若不处理,可能导致程序逻辑错误或崩溃。通过异常(尤其是自定义异常)可以明确标记这些缺陷,并提供针对性的处理逻辑。

二、自定义异常:为类的 “缺陷” 定制错误类型

Python 允许通过继承Exception类定义自定义异常,用于描述类特有的缺陷。相比内置异常(如ValueError),自定义异常的优势在于:

  • 语义明确:直接关联类的业务逻辑(如InsufficientFundsErrorValueError更能说明 “余额不足”)。
  • 便于区分处理:调用者可通过捕获特定异常,对不同缺陷执行不同逻辑(如 “余额不足” 提示充值,“账户冻结” 提示联系客服)。
1. 自定义异常的基本定义

自定义异常通常是继承Exception的空类(核心是通过类名表达错误含义),也可添加属性存储错误详情:

# 自定义异常:属性值非法(如年龄为负)
class InvalidAttributeError(Exception):
    """当类的属性被设置为非法值时抛出"""
    def __init__(self, attr_name, invalid_value, reason):
        self.attr_name = attr_name  # 非法属性名
        self.invalid_value = invalid_value  # 非法值
        self.reason = reason  # 错误原因
        super().__init__(f"属性 {attr_name} 赋值为 {invalid_value} 非法:{reason}")

# 自定义异常:方法调用时机错误(如对已关闭的对象调用方法)
class InvalidOperationError(Exception):
    """当在不恰当的状态下调用方法时抛出"""
    def __init__(self, method_name, current_state):
        super().__init__(f"方法 {method_name} 不能在状态 {current_state} 下调用")
2. 在类中使用自定义异常处理 “缺陷”

在类的方法中,通过条件判断检测 “缺陷”,并抛出自定义异常。以下以 “银行账户类” 为例,展示如何处理常见缺陷:

class BankAccount:
    def __init__(self, account_id, balance=0):
        self.account_id = account_id  # 账号(只读)
        self._balance = balance  # 余额(私有属性,通过方法访问)
        self._is_frozen = False  # 状态:是否冻结

    @property
    def balance(self):
        return self._balance

    # 存款(无缺陷风险,直接操作)
    def deposit(self, amount):
        if amount <= 0:
            # 抛出自定义异常:存款金额非法
            raise InvalidAttributeError("amount", amount, "存款金额必须大于0")
        self._balance += amount
        print(f"存款 {amount} 元成功,当前余额:{self._balance}")

    # 取款(可能存在“余额不足”“账户冻结”等缺陷)
    def withdraw(self, amount):
        # 缺陷1:账户已冻结
        if self._is_frozen:
            raise InvalidOperationError("withdraw", "账户已冻结")
        
        # 缺陷2:取款金额非法
        if amount <= 0:
            raise InvalidAttributeError("amount", amount, "取款金额必须大于0")
        
        # 缺陷3:余额不足
        if amount > self._balance:
            # 可再定义更具体的异常:InsufficientFundsError
            raise InvalidAttributeError("amount", amount, f"余额不足(当前余额:{self._balance})")
        
        self._balance -= amount
        print(f"取款 {amount} 元成功,当前余额:{self._balance}")

    # 冻结账户(状态变更)
    def freeze(self):
        self._is_frozen = True
        print(f"账户 {self.account_id} 已冻结")
3. 调用类时捕获并处理自定义异常

外部调用类的方法时,通过try...except捕获自定义异常,根据不同缺陷执行针对性处理:

if __name__ == "__main__":
    acc = BankAccount("622202XXXXXXXX1234", 1000)

    try:
        acc.withdraw(1500)  # 缺陷:余额不足
    except InvalidAttributeError as e:
        print(f"取款失败:{e}")  # 输出具体错误原因
        print("建议:减少取款金额或先存款")

    try:
        acc.freeze()
        acc.withdraw(500)  # 缺陷:账户已冻结时调用取款
    except InvalidOperationError as e:
        print(f"取款失败:{e}")
        print("建议:联系客服解冻账户")

    try:
        acc.deposit(-200)  # 缺陷:存款金额为负
    except InvalidAttributeError as e:
        print(f"存款失败:{e}")

输出结果

取款失败:属性 amount 赋值为 1500 非法:余额不足(当前余额:1000)
建议:减少取款金额或先存款
账户 622202XXXXXXXX1234 已冻结
取款失败:方法 withdraw 不能在状态 账户已冻结 下调用
建议:联系客服解冻账户
存款失败:属性 amount 赋值为 -200 非法:存款金额必须大于0

三、面向对象中异常处理的设计原则

  1. 异常与类的职责绑定 自定义异常应与类的业务逻辑强相关(如BankAccount的异常应围绕 “账户操作”),避免使用过于通用的异常(如直接raise Exception)。

  2. 在 “缺陷发生点” 及时抛出异常 类的内部(如BankAccount.withdraw)应尽早检测到缺陷并抛出异常,而非将错误状态传递到外部,导致调试困难。

  3. 通过继承构建异常体系 对于复杂类或模块,可通过异常继承构建层级,便于批量处理。例如:

    class AccountError(Exception):
        """账户相关异常的基类"""
        pass
       
    class InsufficientFundsError(AccountError):
        """余额不足(继承自AccountError)"""
        pass
       
    class AccountFrozenError(AccountError):
        """账户冻结(继承自AccountError)"""
        pass
    

    调用时可捕获基类AccountError处理所有账户相关异常,也可捕获子类处理特定异常:

    try:
        acc.withdraw(2000)
    except InsufficientFundsError:
        # 处理余额不足
    except AccountError:
        # 处理其他账户异常(如冻结)
    
  4. 避免过度使用异常 类的轻微逻辑判断(如 “如果余额为 0 则提示”)应使用普通if语句,异常仅用于 “非预期的缺陷”(如非法操作、资源错误)。

四、总结

在面向对象编程中,类的 “缺陷” 本质是违背设计逻辑的操作或状态,通过自定义异常可以精准描述这些缺陷,使错误处理更贴合业务需求。核心要点:

  • 自定义异常需继承Exception,并通过类名和属性传递错误语义;
  • 在类的方法中主动检测缺陷并抛出自定义异常,确保内部状态合法;
  • 外部调用时通过try...except捕获特定异常,执行针对性处理。

这种模式让类的逻辑更健壮,同时提高代码的可读性和可维护性 —— 使用者无需猜测错误原因,只需根据异常类型即可明确如何处理。

8.4 Python高阶:自动化框架专题

Logging日志处理模块(logging, FileHandler, StreamHandler, 日志等级)

在自动化框架(如测试框架、运维脚本、服务监控系统)中,日志(Logging) 是核心组件之一。它用于记录程序运行状态、错误详情、关键操作步骤等信息,是调试问题、监控系统、追溯流程的重要依据。Python 内置的 logging 模块提供了强大的日志处理能力,支持日志分级、多目标输出(控制台 / 文件)、格式化等功能,远优于简单的 print 语句。

一、logging 模块的核心优势

相比 printlogging 模块的核心优势:

  • 日志分级:按重要程度区分日志(如调试信息、正常运行信息、错误信息),可按需过滤。
  • 多目标输出:同时将日志输出到控制台(StreamHandler)、文件(FileHandler)、甚至网络服务。
  • 格式化输出:自定义日志格式(包含时间、模块名、日志等级等),便于分析。
  • 灵活性:支持通过配置文件或代码动态调整日志行为(如运行时修改输出级别)。

二、日志等级(Logging Levels)

日志等级用于区分日志的重要性,logging 模块定义了 5 个标准等级(从低到高),等级越高,日志越重要:

等级常量 数值 含义 典型场景
DEBUG 10 调试信息,用于开发阶段排查细节(如变量值、函数调用流程) 打印循环变量、数据库查询语句
INFO 20 正常运行信息,记录程序关键节点(如 “服务启动成功”“用户登录”) 系统启动完成、任务开始执行
WARNING 30 警告信息,表示存在潜在问题(如 “磁盘空间不足”“参数即将过期”) 配置文件使用默认值、连接超时重试
ERROR 40 错误信息,表示操作失败(如 “文件打开失败”“API 调用错误”) 数据库连接失败、用户输入验证错误
CRITICAL 50 严重错误,表明程序可能无法继续运行(如 “内存耗尽”“核心模块崩溃”) 系统资源耗尽、关键服务中断

等级规则

  • 当设置日志等级为 LEVEL 时,只有等级 ≥ LEVEL 的日志会被记录(低等级日志被过滤)。
  • 默认等级为 WARNING(即默认只记录 WARNINGERRORCRITICAL)。

三、日志处理器(Handlers)

logging 通过 “处理器” 控制日志的输出目标。常用处理器:

处理器类型 作用 适用场景
StreamHandler 将日志输出到控制台(默认绑定 sys.stderr 开发调试、实时查看运行状态
FileHandler 将日志输出到指定文件 持久化保存日志、事后分析
RotatingFileHandler 日志文件达到指定大小后自动切割(如 100MB 换一个文件) 避免单个日志文件过大
TimedRotatingFileHandler 按时间自动切割日志(如每天一个文件) 按时间维度管理日志(如每日日志)

四、日志格式化(Formatters)

Formatter 用于定义日志的输出格式,可包含时间、日志等级、模块名、日志内容等信息。常用格式化符号:

符号 含义
%(asctime)s 日志记录时间(默认格式:2023-10-01 12:00:00,123
%(levelname)s 日志等级(如 DEBUGERROR
%(module)s 产生日志的模块名
%(funcName)s 产生日志的函数名
%(lineno)d 产生日志的代码行号
%(message)s 日志消息内容

五、实战:自动化框架中的日志配置

以下是一个自动化测试框架的日志配置示例,实现:

  • 同时输出日志到控制台(StreamHandler)和文件(FileHandler)。
  • 控制台输出 INFO 及以上等级日志,文件记录 DEBUG 及以上等级日志(更详细)。
  • 自定义日志格式(包含时间、等级、模块、行号、消息)。
完整配置代码
import logging
from logging.handlers import RotatingFileHandler
import os

def setup_logger():
    # 1. 创建日志器(logger):日志的入口,负责收集和分发日志
    logger = logging.getLogger("auto_test_framework")  # 命名日志器(避免与其他模块冲突)
    logger.setLevel(logging.DEBUG)  # 日志器总等级(低于此的日志会被直接过滤)

    # 2. 定义日志格式(Formatter)
    formatter = logging.Formatter(
        fmt="%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"  # 时间格式
    )

    # 3. 配置控制台处理器(StreamHandler):输出到控制台,等级为INFO
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)  # 控制台只输出INFO及以上
    console_handler.setFormatter(formatter)  # 应用格式

    # 4. 配置文件处理器(RotatingFileHandler):输出到文件,自动切割
    log_dir = "logs"  # 日志文件夹
    os.makedirs(log_dir, exist_ok=True)  # 确保文件夹存在
    file_handler = RotatingFileHandler(
        filename=os.path.join(log_dir, "test.log"),  # 日志文件路径
        maxBytes=1024 * 1024 * 5,  # 单个文件最大5MB
        backupCount=5,  # 最多保留5个备份文件
        encoding="utf-8"  # 支持中文
    )
    file_handler.setLevel(logging.DEBUG)  # 文件记录DEBUG及以上(更详细)
    file_handler.setFormatter(formatter)  # 应用格式

    # 5. 给日志器添加处理器(可添加多个,实现多目标输出)
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)

    return logger

# 初始化日志器(在框架启动时执行一次)
logger = setup_logger()

# 测试日志输出
if __name__ == "__main__":
    logger.debug("这是调试信息:用户输入参数为{'name': 'test'}")  # 仅文件记录
    logger.info("测试用例 [login_case_001] 开始执行")  # 控制台和文件都记录
    logger.warning("测试数据 [user_info.csv] 即将过期,请更新")  # 控制台和文件都记录
    try:
        1 / 0
    except ZeroDivisionError:
        logger.error("测试用例执行失败:除数不能为零", exc_info=True)  # 记录异常栈
    logger.critical("数据库连接失败,所有测试用例中止执行!")  # 严重错误
输出效果
  • 控制台输出INFO 及以上):

    2023-10-01 15:30:00 - INFO - test_log:45 - 测试用例 [login_case_001] 开始执行
    2023-10-01 15:30:00 - WARNING - test_log:46 - 测试数据 [user_info.csv] 即将过期,请更新
    2023-10-01 15:30:00 - ERROR - test_log:49 - 测试用例执行失败:除数不能为零
    Traceback (most recent call last):
      File "test_log.py", line 47, in <module>
        1 / 0
    ZeroDivisionError: division by zero
    2023-10-01 15:30:00 - CRITICAL - test_log:50 - 数据库连接失败,所有测试用例中止执行!
    
  • 文件输出logs/test.log,包含 DEBUG 及以上): 比控制台多一条 DEBUG 日志,其余内容相同。

六、在自动化框架中的最佳实践

  1. 全局单例日志器:整个框架使用一个日志器实例(如上述 logger),避免重复配置。
  2. 分级日志策略:
    • 开发 / 调试阶段:设置等级为 DEBUG,记录详细信息。
    • 生产 / 运行阶段:设置等级为 INFOWARNING,减少冗余日志。
  3. 异常日志必记录:使用 logger.error(..., exc_info=True) 记录异常栈(exc_info=True 会打印完整的错误追踪),便于定位问题。
  4. 日志文件管理:使用 RotatingFileHandlerTimedRotatingFileHandler 自动切割日志,避免文件过大。
  5. 日志格式标准化:包含时间、等级、模块、行号等关键信息,确保日志可追溯。

总结

logging 模块通过日志等级、处理器、格式化三大组件,为自动化框架提供了灵活、可控的日志解决方案。合理配置日志不仅能帮助快速定位问题,还能监控框架运行状态,是构建健壮自动化系统的必备技能。核心要点:

  • 日志等级控制输出粒度,按需过滤;
  • StreamHandler 输出到控制台,FileHandler 持久化到文件;
  • 自定义格式提升日志可读性;
  • 结合框架场景配置全局日志策略。

Python序列化和反序列化详解

在 Python 中,序列化指将内存中的对象(如字典、列表、自定义类实例等)转换为可存储(如写入文件)或可传输(如网络发送)的格式(通常是字节流或字符串);反序列化则是相反过程,将存储或传输的格式恢复为内存中的对象。这一机制是数据持久化、跨进程通信、网络传输的基础。

一、为什么需要序列化和反序列化?

  • 数据持久化:将程序中的对象保存到文件,下次运行时恢复(如游戏存档、配置保存)。
  • 网络传输:网络只能传输字节流,需将对象序列化为字节流后发送,接收方再反序列化为对象(如 API 接口传输复杂数据)。
  • 跨进程通信:不同进程间交换数据时,需通过序列化统一数据格式。

二、Python 常用序列化模块

Python 提供多个序列化模块,适用于不同场景,核心差异在于支持的数据类型跨语言兼容性安全性

1. pickle:Python 专属序列化(推荐内部使用)

pickle 是 Python 内置模块,支持几乎所有 Python 对象(包括自定义类、函数、集合等)的序列化,但仅能在 Python 中使用(不跨语言),且反序列化不可信数据有安全风险。

核心方法

  • 序列化:
    • pickle.dumps(obj):将对象转为字节流(bytes 类型)。
    • pickle.dump(obj, file):将对象序列化后写入文件对象。
  • 反序列化:
    • pickle.loads(bytes_data):将字节流恢复为对象。
    • pickle.load(file):从文件中读取数据并反序列化为对象。

示例:序列化自定义类实例

import pickle

# 定义一个自定义类
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"

# 创建对象
person = Person("Alice", 30)
print("原始对象:", person)  # 输出:Person(name=Alice, age=30)

# 1. 序列化:对象 → 字节流
pickle_bytes = pickle.dumps(person)
print("序列化后(字节流):", pickle_bytes[:50])  # 输出前50字节:b'\x80\x04\x95\x1f\x00\x00\x00\x00\x00\x00\x00\x8c...'

# 2. 反序列化:字节流 → 对象
restored_person = pickle.loads(pickle_bytes)
print("反序列化后:", restored_person)  # 输出:Person(name=Alice, age=30)

# 3. 序列化到文件
with open("person.pkl", "wb") as f:  # 注意用二进制模式(wb)
    pickle.dump(person, f)

# 4. 从文件反序列化
with open("person.pkl", "rb") as f:  # 二进制读取(rb)
    file_person = pickle.load(f)
print("从文件恢复:", file_person)  # 输出:Person(name=Alice, age=30)

特点与注意事项

  • 优势:支持所有 Python 对象(类、函数、lambda、集合等),序列化彻底(对象状态完全保留)。
  • 劣势:
    • 不跨语言(其他语言无法解析 pickle 格式)。
    • 安全风险:反序列化不可信数据可能执行恶意代码(如黑客构造的 pickle 字节流),禁止用于处理外部输入
  • 适用场景:Python 程序内部的数据持久化(如缓存、临时存储)、进程间通信(如多进程共享对象)。
2. json:跨语言通用序列化(推荐跨平台 / 网络使用)

json 是 Python 内置模块,用于处理 JSON(JavaScript Object Notation) 格式 —— 一种轻量、跨语言的文本格式,几乎所有编程语言都支持。但 json 仅支持基础数据类型(字典、列表、字符串、数字、布尔值、None),不直接支持自定义类、集合等。

核心方法

  • 序列化:
    • json.dumps(obj, ensure_ascii=False, indent=2):将对象转为 JSON 字符串(str 类型),ensure_ascii=False 支持中文,indent 格式化输出。
    • json.dump(obj, file, ...):将对象序列化后写入文件。
  • 反序列化:
    • json.loads(json_str):将 JSON 字符串恢复为 Python 对象。
    • json.load(file):从文件读取 JSON 并恢复为对象。

示例:序列化基础数据类型

import json

# 基础数据(json支持的类型)
data = {
    "name": "张三",
    "age": 25,
    "hobbies": ["读书", "跑步"],
    "is_student": False,
    "scores": None
}

# 1. 序列化:Python对象 → JSON字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2)  # 格式化输出,支持中文
print("JSON字符串:\n", json_str)
# 输出:
# {
#   "name": "张三",
#   "age": 25,
#   "hobbies": [
#     "读书",
#     "跑步"
#   ],
#   "is_student": false,
#   "scores": null
# }

# 2. 反序列化:JSON字符串 → Python对象
restored_data = json.loads(json_str)
print("反序列化后:", restored_data["name"])  # 输出:张三

# 3. 序列化到文件
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# 4. 从文件反序列化
with open("data.json", "r", encoding="utf-8") as f:
    file_data = json.load(f)
print("从文件恢复:", file_data["hobbies"])  # 输出:['读书', '跑步']

处理自定义类(需手动转换)

json 不直接支持自定义类,需通过 default(序列化)和 object_hook(反序列化)参数手动转换为基础类型(如字典):

import json

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    # 定义序列化方法:转为字典
    def to_dict(self):
        return {"name": self.name, "age": self.age}
    
    # 定义反序列化方法:从字典创建对象
    @classmethod
    def from_dict(cls, data):
        return cls(data["name"], data["age"])

# 创建对象
person = Person("Bob", 28)

# 序列化:通过default参数指定转换函数
person_json = json.dumps(
    person,
    ensure_ascii=False,
    default=lambda obj: obj.to_dict()  # 对Person对象调用to_dict()
)
print("自定义类JSON:", person_json)  # 输出:{"name": "Bob", "age": 28}

# 反序列化:通过object_hook参数指定恢复函数
restored_person = json.loads(
    person_json,
    object_hook=Person.from_dict  # 用from_dict创建Person对象
)
print("恢复的对象:", restored_person.name)  # 输出:Bob

特点与注意事项

  • 优势:跨语言兼容(所有主流语言支持 JSON)、文本格式(易读、易调试)、无安全风险。
  • 劣势:仅支持基础数据类型,自定义对象需手动转换,序列化效率低于 pickle
  • 适用场景:网络 API 数据传输(如前后端交互、跨语言服务通信)、配置文件存储(JSON 格式比 pickle 更易人工编辑)。
3. 其他序列化模块
  • marshal:Python 内置,用于序列化简单对象(如函数、类),但主要供 Python 解释器内部使用(如 .pyc 文件),不推荐用户直接使用(兼容性差,可能随 Python 版本变化)。
  • msgpack:第三方模块(需 pip install msgpack),二进制格式,比 JSON 更紧凑、效率更高,跨语言支持(类似 JSON 但体积更小),适合高性能场景。
  • yaml:第三方模块(需 pip install pyyaml),支持复杂结构和注释,适合配置文件,但序列化效率较低。

三、核心模块对比

模块 支持类型 跨语言 格式 安全性 适用场景
pickle 所有 Python 对象 二进制 低(风险) Python 内部持久化、进程通信
json 基础类型(需手动扩展) 文本 跨语言通信、API 数据、配置文件
msgpack 基础类型(类似 JSON) 二进制 高性能跨语言数据传输

四、最佳实践

  1. 优先考虑场景:
    • 跨语言 / 网络传输:用 jsonmsgpack
    • Python 内部使用:用 pickle(便捷性优先)。
  2. 自定义对象处理:
    • pickle 无需额外代码(自动处理)。
    • json 需实现 to_dict()from_dict() 手动转换。
  3. 安全性:
    • 绝对禁止用 pickle 反序列化外部输入(如网络接收的字节流、未知文件)。
    • json 可安全处理外部数据(仅解析基础类型,无执行风险)。
  4. 性能:大量数据或高频传输时,优先选 msgpack(二进制、高效),其次 pickle,最后 json

总结

序列化和反序列化是 Python 中数据持久化和跨场景传输的核心技术。pickle 适合 Python 内部的全类型序列化,json 适合跨语言的通用场景,选择时需权衡类型支持跨语言性安全性。掌握这些工具能有效解决数据存储、网络通信等实际问题。

外部数据库MySQL高级应用(MySqldb、Python操作实现CURD、事务机制)

在 Python 操作 MySQL 数据库的场景中,MySqldb 因对 Python 3 兼容性较差,目前主流替代方案是 PyMySQL(纯 Python 实现,功能一致且支持 Python 3+)。本文以 PyMySQL 为核心,讲解 Python 操作 MySQL 的高级应用,包括数据库连接、CURD 实现、事务机制及性能优化,覆盖企业级开发中的核心需求。

一、环境准备

1. 依赖安装

首先安装 PyMySQL(替代 MySqldb):

pip install pymysql
2. MySQL 服务准备
  • 确保 MySQL 服务已启动(本地或远程)。

  • 创建测试数据库和表(示例用数据库和表):

    test_db
    
    user
    
    -- 创建数据库
    CREATE DATABASE IF NOT EXISTS test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
      
    -- 使用数据库
    USE test_db;
      
    -- 创建用户表
    CREATE TABLE IF NOT EXISTS user (
        id INT PRIMARY KEY AUTO_INCREMENT,  -- 主键(自增)
        name VARCHAR(50) NOT NULL,          -- 姓名
        age INT NOT NULL,                   -- 年龄
        email VARCHAR(100) UNIQUE NOT NULL, -- 邮箱(唯一)
        create_time DATETIME DEFAULT CURRENT_TIMESTAMP  -- 创建时间(默认当前时间)
    );
    

二、Python 操作 MySQL 基础:连接与资源管理

1. 核心连接参数

PyMySQL 通过 pymysql.connect() 建立连接,关键参数:

  • host:MySQL 服务地址(本地为 localhost127.0.0.1)。
  • user:MySQL 用户名(如 root)。
  • password:MySQL 密码。
  • db:默认操作的数据库(如 test_db)。
  • port:MySQL 端口(默认 3306)。
  • charset:字符集(必须为 utf8mb4,支持中文和表情)。
  • cursorclass:游标类型(默认 Cursor 返回元组,DictCursor 返回字典,推荐后者更易读)。
2. 安全的连接管理(推荐 with 语句)

使用 with 语句可自动关闭连接和游标,避免资源泄漏(无需手动 close()):

import pymysql
from pymysql.cursors import DictCursor  # 返回字典类型的游标

# 数据库配置(实际开发中建议放在配置文件,而非硬编码)
DB_CONFIG = {
    "host": "localhost",
    "user": "root",
    "password": "your_mysql_password",  # 替换为你的MySQL密码
    "db": "test_db",
    "port": 3306,
    "charset": "utf8mb4",
    "cursorclass": DictCursor  # 游标返回字典(key为字段名,value为值)
}

# 用with语句管理连接和游标
with pymysql.connect(**DB_CONFIG) as conn:  # 自动关闭连接
    with conn.cursor() as cursor:           # 自动关闭游标
        # 执行SQL(后续CURD操作在此处扩展)
        cursor.execute("SELECT VERSION()")  # 查询MySQL版本
        result = cursor.fetchone()          # 获取单条结果
        print("MySQL版本:", result["VERSION()"])

三、核心应用:CURD 实现(参数化查询防注入)

CURD 是数据库操作的基础(Create 插入、Read 查询、Update 更新、Delete 删除)。必须使用参数化查询(而非字符串拼接),避免 SQL 注入攻击(黑客通过构造特殊输入篡改 SQL 逻辑)。

PyMySQL 中参数化查询的占位符为 %s(无论字段类型,均用 %s,无需区分 %d/%s)。

1. Create(插入数据)

支持单条插入和批量插入,批量插入效率远高于循环单条插入。

(1)单条插入
with pymysql.connect(**DB_CONFIG) as conn:
    with conn.cursor() as cursor:
        # 1. 定义SQL(用%s占位符)
        sql = """
        INSERT INTO user (name, age, email) 
        VALUES (%s, %s, %s)
        """
        # 2. 定义参数(元组或列表,顺序与%s对应)
        data = ("Alice", 25, "[email protected]")
        # 3. 执行SQL
        cursor.execute(sql, data)
        # 4. 提交事务(MySQL默认自动提交关闭,需手动commit,否则数据不生效)
        conn.commit()
        # 获取插入的自增主键(如id)
        print("插入的用户ID:", cursor.lastrowid)  # 输出:1(首次插入)
(2)批量插入

executemany() 批量执行,参数为列表(每个元素是一条数据的元组):

with pymysql.connect(**DB_CONFIG) as conn:
    with conn.cursor() as cursor:
        sql = "INSERT INTO user (name, age, email) VALUES (%s, %s, %s)"
        # 批量数据(列表,含3条记录)
        batch_data = [
            ("Bob", 28, "[email protected]"),
            ("Charlie", 30, "[email protected]"),
            ("David", 22, "[email protected]")
        ]
        # 批量执行(仅需1次SQL调用,效率高)
        cursor.executemany(sql, batch_data)
        conn.commit()
        print("批量插入的记录数:", cursor.rowcount)  # 输出:3
2. Read(查询数据)

常用查询方法:

  • fetchone():获取单条结果(字典或元组)。
  • fetchmany(size):获取指定条数结果(列表)。
  • fetchall():获取所有结果(列表)。
(1)查询单条数据(按条件)
with pymysql.connect(**DB_CONFIG) as conn:
    with conn.cursor() as cursor:
        sql = "SELECT id, name, age, email FROM user WHERE id = %s"
        cursor.execute(sql, (1,))  # 参数是元组(注意:单元素元组需加逗号,避免被解析为普通值)
        user = cursor.fetchone()   # 获取单条结果
        print("查询到的用户:", user)
        # 输出:{'id': 1, 'name': 'Alice', 'age': 25, 'email': '[email protected]'}
(2)查询多条数据(带分页)
with pymysql.connect(**DB_CONFIG) as conn:
    with conn.cursor() as cursor:
        # 分页查询:第2页,每页2条(LIMIT 偏移量, 条数;偏移量 = (页码-1)*条数)
        page = 2
        page_size = 2
        offset = (page - 1) * page_size
        
        sql = """
        SELECT id, name, age, email 
        FROM user 
        WHERE age > %s 
        ORDER BY create_time DESC 
        LIMIT %s, %s
        """
        cursor.execute(sql, (23, offset, page_size))  # 参数:age>23,偏移量2,条数2
        users = cursor.fetchall()  # 获取所有匹配结果
        
        print(f"第{page}页用户(共{len(users)}条):")
        for u in users:
            print(u)
        # 输出:第2页用户(共2条):
        # {'id': 2, 'name': 'Bob', 'age': 28, 'email': '[email protected]'}
        # {'id': 3, 'name': 'Charlie', 'age': 30, 'email': '[email protected]'}
3. Update(更新数据)
with pymysql.connect(**DB_CONFIG) as conn:
    with conn.cursor() as cursor:
        # 更新Alice的年龄为26
        sql = "UPDATE user SET age = %s WHERE name = %s"
        cursor.execute(sql, (26, "Alice"))
        conn.commit()
        # 查看影响行数(更新成功的记录数)
        print("更新的记录数:", cursor.rowcount)  # 输出:1(若Alice存在)
4. Delete(删除数据)
with pymysql.connect(**DB_CONFIG) as conn:
    with conn.cursor() as cursor:
        # 删除邮箱为[email protected]的用户
        sql = "DELETE FROM user WHERE email = %s"
        cursor.execute(sql, ("[email protected]",))
        conn.commit()
        print("删除的记录数:", cursor.rowcount)  # 输出:1(若David存在)

四、高级应用:事务机制(保证数据一致性)

事务是数据库操作的原子单元,确保一组 SQL 操作 “要么全部成功,要么全部失败”(避免部分执行导致数据不一致,如转账时 “扣钱成功但加钱失败”)。

1. 事务的 ACID 特性
  • 原子性(Atomicity):事务中的操作不可分割,要么全执行,要么全回滚。
  • 一致性(Consistency):事务执行前后,数据库状态符合业务规则(如转账后总金额不变)。
  • 隔离性(Isolation):多个事务并发执行时,互不干扰(避免脏读、不可重复读、幻读)。
  • 持久性(Durability):事务提交后,数据永久保存到数据库(即使系统崩溃也不丢失)。
2. Python 中事务的控制

PyMySQL 中,事务默认自动提交关闭autocommit=False),需手动调用 conn.commit() 提交事务;若发生异常,调用 conn.rollback() 回滚事务(恢复到事务开始前的状态)。

示例:转账场景(事务核心应用)

假设存在 account 表(账户表),实现 “从 A 账户转账 100 元到 B 账户”,需保证两个更新操作要么都成功,要么都失败:

-- 创建账户表(若不存在)
CREATE TABLE IF NOT EXISTS account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    balance DECIMAL(10,2) NOT NULL DEFAULT 0.00,
    FOREIGN KEY (user_id) REFERENCES user(id)
);

-- 初始化数据(A用户ID=1,余额500;B用户ID=2,余额300)
INSERT INTO account (user_id, balance) VALUES (1, 500.00), (2, 300.00);

Python 事务实现:

def transfer(from_user_id, to_user_id, amount):
    """
    转账函数:从from_user_id账户转账amount元到to_user_id账户
    :param from_user_id: 转出用户ID
    :param to_user_id: 转入用户ID
    :param amount: 转账金额(正数)
    :return: 成功返回True,失败返回False
    """
    if amount <= 0:
        print("转账金额必须为正数")
        return False
    
    conn = None
    try:
        # 1. 建立连接
        conn = pymysql.connect(**DB_CONFIG)
        # 2. 获取游标
        with conn.cursor() as cursor:
            # 3. 步骤1:查询转出账户余额(判断是否足够)
            cursor.execute("SELECT balance FROM account WHERE user_id = %s", (from_user_id,))
            from_account = cursor.fetchone()
            if not from_account or from_account["balance"] < amount:
                print("转出账户余额不足")
                return False
            
            # 4. 步骤2:转出账户扣钱(balance = balance - amount)
            cursor.execute(
                "UPDATE account SET balance = balance - %s WHERE user_id = %s",
                (amount, from_user_id)
            )
            
            # 模拟异常(测试回滚:取消注释后,转账会失败,数据回滚)
            # raise Exception("模拟转账异常")
            
            # 5. 步骤3:转入账户加钱(balance = balance + amount)
            cursor.execute(
                "UPDATE account SET balance = balance + %s WHERE user_id = %s",
                (amount, to_user_id)
            )
        
        # 6. 所有步骤成功,提交事务
        conn.commit()
        print(f"转账成功:从用户{from_user_id}到用户{to_user_id},金额{amount}元")
        
        # 验证结果
        with conn.cursor() as cursor:
            cursor.execute("SELECT user_id, balance FROM account WHERE user_id IN (%s, %s)", (from_user_id, to_user_id))
            result = cursor.fetchall()
            print("转账后余额:", result)
        
        return True
    
    except Exception as e:
        # 7. 发生异常,回滚事务(恢复到转账前状态)
        if conn:
            conn.rollback()
        print(f"转账失败:{str(e)}")
        return False
    
    finally:
        # 8. 关闭连接(无论成功或失败)
        if conn:
            conn.close()

# 测试转账:从用户1(Alice)转账100元到用户2(Bob)
transfer(1, 2, 100)

事务执行结果:

  • 无异常时:提交事务,Alice 余额 400,Bob 余额 400(数据一致)。
  • 取消 “模拟异常” 注释后:触发 rollback(),Alice 和 Bob 余额恢复为 500 和 300(无数据不一致)。
3. 事务隔离级别(高级配置)

MySQL 支持 4 种事务隔离级别(默认 REPEATABLE READ),可通过 conn.set_session(transaction_isolation="隔离级别") 配置:

  • READ UNCOMMITTED:最低级别,允许读未提交数据(脏读)。
  • READ COMMITTED:避免脏读,允许不可重复读(同一事务内多次读同一数据,结果可能不同)。
  • REPEATABLE READ(默认):避免脏读和不可重复读,允许幻读(同一事务内多次查询,结果行数可能不同)。
  • SERIALIZABLE:最高级别,避免所有问题,但并发效率低(事务串行执行)。

示例配置隔离级别:

with pymysql.connect(**DB_CONFIG) as conn:
    # 设置隔离级别为READ COMMITTED
    conn.set_session(transaction_isolation="READ COMMITTED")
    # 后续事务操作...

五、性能优化:连接池(DBUtils)

在高并发场景(如 Web 服务)中,频繁创建和关闭数据库连接会严重影响性能。连接池通过预先创建一定数量的连接,复用连接避免重复开销,是企业级开发的必备优化。

1. 依赖安装(DBUtils)

pip install dbutils

2. 连接池实现(PersistentDB)

PersistentDB 为每个线程创建一个持久连接,适合多线程场景(如 Flask/Django 服务):

from dbutils.persistent_db import PersistentDB
import pymysql

# 1. 初始化连接池(全局只初始化一次)
POOL = PersistentDB(
    creator=pymysql,        # 数据库驱动
    maxusage=None,          # 单个连接最大使用次数(None表示无限)
    setsession=[],          # 会话参数(如设置隔离级别)
    ping=1,                 # 每次获取连接时ping数据库(确保连接有效)
    closeable=False,        # 连接是否可关闭(False表示池管理连接)
    **DB_CONFIG             # 数据库配置(复用之前的DB_CONFIG)
)

# 2. 从连接池获取连接(多线程安全)
def get_conn():
    return POOL.connection()

# 3. 使用连接池执行SQL
with get_conn() as conn:
    with conn.cursor() as cursor:
        cursor.execute("SELECT name, age FROM user LIMIT 2")
        print("连接池查询结果:", cursor.fetchall())

六、最佳实践与注意事项

  1. 防 SQL 注入:必须使用参数化查询(%s 占位符),禁止字符串拼接 SQL(如 f"SELECT * FROM user WHERE name = '{name}'",黑客可输入 ' OR 1=1 -- 篡改逻辑)。
  2. 配置管理:数据库密码、地址等敏感信息,避免硬编码,应放在配置文件(如 config.ini)或环境变量中,并加密存储(生产环境)。
  3. 异常处理:所有数据库操作必须加 try-except,捕获 pymysql.MySQLError 等异常,避免程序崩溃。
  4. 连接管理:优先用 with 语句或连接池,避免手动管理连接导致泄漏。
  5. 事务场景:涉及多步写操作(如转账、订单创建)时,必须用事务保证数据一致性。

总结

Python 操作 MySQL 的核心流程是 “连接 → 执行 SQL → 处理结果 → 提交 / 回滚 → 释放资源”。通过 PyMySQL 实现 CURD 时,参数化查询是安全基础;通过事务机制保证数据一致性;通过连接池优化高并发性能。这些技术是企业级 MySQL 应用的核心,需结合实际场景灵活运用(如 Web 服务用连接池,脚本用 with 语句)。

外部数据库Mysql常用管理(yum安装、yum卸载、pyyaml模块操作)

在 MySQL 管理中,YUM 安装 / 卸载是 Linux(如 CentOS、RHEL)系统下标准化的环境部署方式,而PyYAML 模块则常用于管理 MySQL 的连接配置(如将数据库地址、账号密码等写入 YAML 配置文件,避免硬编码)。以下是三者的详细操作指南,包含实操步骤、注意事项和示例代码。

一、YUM 安装 MySQL(CentOS/RHEL 系统)

YUM(Yellowdog Updater Modified)是 Linux 系统的包管理器,通过官方 YUM 仓库安装 MySQL 可确保版本兼容性和安全性,推荐用于生产环境。以下以MySQL 8.0(主流稳定版)为例,适用于 CentOS 7/8(CentOS 8 可使用dnf命令,兼容yum)。

1. 前置准备:清理旧版本(可选)

若系统中已安装旧版 MySQL(如 MySQL 5.7)或 MariaDB(CentOS 默认数据库),需先卸载,避免冲突:

# 停止旧服务(若存在)
systemctl stop mysqld mariadb

# 卸载旧包
yum remove -y mysql mysql-server mariadb mariadb-server

# 删除残留文件(数据、配置)
rm -rf /var/lib/mysql /etc/my.cnf /var/log/mysqld.log

2. 安装 MySQL 官方 YUM 仓库

默认系统 YUM 源中的 MySQL 版本较旧,需先添加 MySQL 官方仓库:

# 下载MySQL 8.0官方YUM仓库(CentOS 7为例,CentOS 8需替换为对应repo文件)
wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm

# 安装仓库(将仓库配置写入/etc/yum.repos.d/)
rpm -Uvh mysql80-community-release-el7-3.noarch.rpm

# 验证仓库是否生效
yum repolist enabled | grep mysql
# 输出应包含 "mysql80-community" 相关条目

注意:CentOS 8 用户需下载对应仓库文件,地址可从MySQL 官方仓库页获取。

3. 安装 MySQL 服务器

通过 YUM 安装 MySQL 核心服务(mysql-community-server):

# 安装MySQL服务器(自动依赖其他组件,如客户端、库文件)
yum install -y mysql-community-server

4. 启动并配置 MySQL

(1)启动服务并设置开机自启

# 启动MySQL服务
systemctl start mysqld

# 查看服务状态(确保active (running))
systemctl status mysqld

# 设置开机自启(重启后自动运行)
systemctl enable mysqld

(2)初始化密码(MySQL 8.0 特性)

MySQL 8.0 安装后会自动生成临时密码,存储在日志文件中,需先获取并修改:

# 1. 查看临时密码(关键字:temporary password)
grep 'temporary password' /var/log/mysqld.log
# 示例输出:2024-05-01T12:34:56.789Z 1 [Note] A temporary password is generated for root@localhost: Abc123!@#

# 2. 登录MySQL(输入上述临时密码)
mysql -u root -p

# 3. 修改密码(MySQL 8.0要求密码复杂度:大小写+数字+特殊符号,长度≥8)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass123!';

(3)配置远程访问(可选,开发 / 测试环境)

默认情况下,MySQL 仅允许本地(localhost)访问,若需远程连接(如 Python 脚本在另一台机器),需授权远程账号:

# 1. 授权root用户允许远程访问(%表示所有IP,生产环境建议指定具体IP,如192.168.1.100
CREATE USER 'root'@'%' IDENTIFIED BY 'MyNewPass123!';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;

# 2. 刷新权限(使配置生效)
FLUSH PRIVILEGES;

# 3. 退出MySQL
exit;

# 4. 开放Linux防火墙3306端口(MySQL默认端口)
firewall-cmd --add-port=3306/tcp --permanent  # 永久开放
firewall-cmd --reload  # 刷新防火墙

二、YUM 卸载 MySQL

若需彻底删除 MySQL(如更换版本、清理环境),需按以下步骤操作,注意:卸载会删除数据,务必先备份!

1. 停止 MySQL 服务

systemctl stop mysqld
systemctl disable mysqld  # 取消开机自启

2. 卸载已安装的 MySQL 包

先查看已安装的 MySQL 相关包,再针对性卸载:

# 1. 列出所有MySQL相关包
yum list installed | grep mysql
# 示例输出:mysql-community-client.x86_64、mysql-community-server.x86_64 等

# 2. 卸载所有相关包(替换为实际列出的包名,或用通配符简化)
yum remove -y mysql-community-server mysql-community-client mysql-community-common mysql-community-libs

3. 删除残留文件(关键步骤)

YUM 卸载仅删除安装包,需手动删除数据、配置和日志文件,避免残留影响后续安装:

# 删除数据目录(所有数据库数据!务必先备份)
rm -rf /var/lib/mysql

# 删除配置文件
rm -rf /etc/my.cnf /etc/my.cnf.d/

# 删除日志文件
rm -rf /var/log/mysqld.log

# 删除MySQL用户和组(可选,若后续不再安装)
userdel mysql
groupdel mysql

4. 清理 YUM 仓库缓存

# 清理仓库缓存
yum clean all

# 删除MySQL官方仓库配置(可选)
rm -rf /etc/yum.repos.d/mysql80-community.repo

三、PyYAML 模块在 MySQL 管理中的应用

PyYAML 是 Python 处理YAML 格式文件的库,核心作用是:将 MySQL 的连接配置(如hostuserpassword)写入 YAML 文件,避免硬编码到 Python 脚本中,实现 “配置与代码分离”(便于环境切换,如开发 / 测试 / 生产)。

1. 核心逻辑

  1. 编写 YAML 配置文件:存储多环境的 MySQL 连接信息;
  2. 用 PyYAML 读取配置:Python 脚本加载 YAML 中的配置;
  3. 连接 MySQL 执行操作:结合pymysql(Python 操作 MySQL 的库)执行 CURD。

2. 步骤 1:安装依赖库

需安装pyyaml(处理 YAML)和pymysql(连接 MySQL):

pip install pyyaml pymysql

3. 步骤 2:编写 MySQL YAML 配置文件

创建mysql_config.yaml,示例如下(支持多环境配置):

# mysql_config.yaml
# 多环境配置:开发(dev)、测试(test)、生产(prod)
dev:
  host: "192.168.1.100"    # 数据库IP
  port: 3306               # 端口(注意:YAML中数字无需引号)
  user: "root"             # 用户名
  password: "DevPass123!"  # 密码
  db: "dev_db"             # 数据库名
  charset: "utf8mb4"       # 字符集(支持emoji)

test:
  host: "192.168.1.101"
  port: 3306
  user: "test_user"
  password: "TestPass123!"
  db: "test_db"
  charset: "utf8mb4"

prod:
  host: "10.0.0.10"
  port: 3306
  user: "prod_user"
  password: "ProdPass123!"
  db: "prod_db"
  charset: "utf8mb4"

注意:YAML 语法严格,需满足:

  • 缩进用空格(不能用 Tab),通常 2 个或 4 个空格;
  • 键值对用键: 值(冒号后必须加空格);
  • 字符串可加引号(避免特殊字符歧义,如password: "123!@#")。

4. 步骤 3:Python 脚本读取配置并操作 MySQL

编写mysql_with_yaml.py,实现 “读取 YAML 配置 → 连接 MySQL → 执行查询”:

import yaml
import pymysql
from pymysql import OperationalError

def load_mysql_config(env="dev"):
    """
    加载MySQL YAML配置
    :param env: 环境(dev/test/prod),默认dev
    :return: 配置字典
    """
    try:
        # 打开YAML文件(使用with确保文件自动关闭)
        with open("mysql_config.yaml", "r", encoding="utf-8") as f:
            # 安全加载YAML(yaml.safe_load()避免加载恶意代码,比yaml.load()安全)
            config = yaml.safe_load(f)
            # 返回指定环境的配置
            return config[env]
    except FileNotFoundError:
        raise Exception(f"配置文件 mysql_config.yaml 未找到!")
    except KeyError:
        raise Exception(f"YAML中未配置 {env} 环境!")

def connect_mysql(config):
    """
    连接MySQL并返回连接对象
    :param config: 配置字典(含host、port、user等)
    :return: pymysql连接对象
    """
    try:
        conn = pymysql.connect(
            host=config["host"],
            port=config["port"],  # port是整数,无需转换
            user=config["user"],
            password=config["password"],
            db=config["db"],
            charset=config["charset"],
            cursorclass=pymysql.cursors.DictCursor  # 游标返回字典(便于按字段名取值)
        )
        print(f"成功连接到 {config['env']} 环境的 {config['db']} 数据库!")
        return conn
    except OperationalError as e:
        raise Exception(f"MySQL连接失败:{e}")

if __name__ == "__main__":
    # 1. 加载测试环境配置(可切换为test/prod)
    mysql_config = load_mysql_config(env="test")
    # 2. 连接MySQL
    conn = connect_mysql(mysql_config)
    
    # 3. 执行查询示例(查询user表)
    try:
        with conn.cursor() as cursor:
            # 编写SQL
            sql = "SELECT id, username, create_time FROM user LIMIT 5;"
            # 执行SQL
            cursor.execute(sql)
            # 获取结果(因cursorclass是DictCursor,结果为列表套字典)
            results = cursor.fetchall()
            
            # 打印结果
            print("\n查询结果:")
            for idx, res in enumerate(results, 1):
                print(f"{idx}. ID: {res['id']}, 用户名: {res['username']}, 创建时间: {res['create_time']}")
    except Exception as e:
        print(f"执行SQL失败:{e}")
    finally:
        # 关闭连接
        if conn:
            conn.close()
            print("\n数据库连接已关闭")

5. 关键注意事项

  1. 配置文件权限:YAML 中存储了数据库密码,需限制文件权限(如 Linux 下chmod 600 mysql_config.yaml,仅当前用户可读写),避免泄露;
  2. 安全加载 YAML:必须用yaml.safe_load()而非yaml.load(),后者可能加载 YAML 中的恶意代码(如!!python/object/apply:os.system ["rm -rf /"]);
  3. 环境切换:仅需修改load_mysql_config(env="xxx")中的env参数,即可切换开发 / 测试 / 生产环境,无需修改脚本逻辑;
  4. 异常处理:脚本中需捕获 “配置文件不存在”“连接失败”“SQL 错误” 等异常,避免程序崩溃。

总结

  • YUM 安装 / 卸载:是 Linux 下标准化的 MySQL 环境管理方式,需注意清理旧版本、初始化密码和防火墙配置;
  • PyYAML:核心价值是 “配置与代码分离”,通过 YAML 文件统一管理 MySQL 连接信息,提升脚本的可维护性和安全性;
  • 搭配使用:YUM 负责 MySQL 环境部署,PyYAML 负责配置管理,再结合pymysql即可实现 Python 对 MySQL 的灵活操作。

外部配置文件Excel/Csv的读取(xlrd、xlwt、pandas、openpyxl、二次封装)

在数据处理和自动化场景中,Excel(.xls/.xlsx)和 CSV 是最常用的外部配置文件格式。Python 提供了多个库处理这些文件,各有侧重(如读取、写入、复杂格式处理)。本文详细讲解主流库的使用方法,并通过 “二次封装” 实现更简洁、通用的文件读取工具。

一、核心库对比与适用场景

库名 支持格式 核心功能 适用场景 局限性
xlrd .xls(优先)、.xlsx(旧版支持) 读取 Excel 数据 读取旧版.xls 文件 2.0 + 版本不再支持.xlsx;不支持写入
openpyxl .xlsx 读取 / 写入.xlsx,支持复杂格式 处理新版.xlsx(读写)、带公式 / 样式的文件 不支持.xls
xlwt .xls 写入.xls 文件 生成旧版.xls 文件 不支持.xlsx;单 sheet 最大 65536 行
pandas .xls/.xlsx/.csv 高效读写 + 数据处理(依赖 xlrd/openpyxl) 批量数据处理、格式转换、统计分析 复杂格式(如合并单元格)处理较弱
csv(内置) .csv 读写 CSV 文件 轻量读取 CSV,自定义分隔符 / 编码 需手动处理数据类型转换(如字符串转数字)

二、Excel 文件读取(.xls/.xlsx)

1. xlrd:读取 .xls 文件(推荐)

xlrd 是读取 .xls 文件的经典库,对旧版格式支持稳定。

安装

pip install xlrd==1.2.0  # 1.2.0版本仍支持.xlsx,2.0+仅支持.xls

示例:读取 .xls 文件

import xlrd

def read_xls_with_xlrd(file_path, sheet_name=None):
    """
    用xlrd读取.xls文件
    :param file_path: 文件路径
    :param sheet_name: 工作表名(None则取第一个sheet)
    :return: 数据列表(每行一个字典,key为表头)
    """
    # 打开文件(formatting_info=True保留格式,可选)
    workbook = xlrd.open_workbook(file_path)
    
    # 获取sheet(按名称或索引)
    if sheet_name:
        sheet = workbook.sheet_by_name(sheet_name)
    else:
        sheet = workbook.sheets()[0]  # 第一个sheet
    
    # 获取表头(第一行)
    headers = [sheet.cell_value(0, col) for col in range(sheet.ncols)]
    
    # 读取数据(从第二行开始)
    data = []
    for row in range(1, sheet.nrows):
        row_data = {headers[col]: sheet.cell_value(row, col) for col in range(sheet.ncols)}
        data.append(row_data)
    
    return data

# 测试
xls_data = read_xls_with_xlrd("data.xls", sheet_name="用户表")
print("xlrd读取.xls结果:", xls_data[:2])  # 打印前2行

2. openpyxl:读取 .xlsx 文件(推荐)

openpyxl 是处理 .xlsx 的主流库,支持读写和复杂格式(如合并单元格、公式)。

安装

pip install openpyxl

示例:读取 .xlsx 文件

from openpyxl import load_workbook

def read_xlsx_with_openpyxl(file_path, sheet_name=None):
    """
    用openpyxl读取.xlsx文件
    :param file_path: 文件路径
    :param sheet_name: 工作表名(None则取第一个sheet)
    :return: 数据列表(每行一个字典,key为表头)
    """
    # 加载工作簿(data_only=True:读取公式计算后的值,而非公式本身)
    workbook = load_workbook(file_path, data_only=True)
    
    # 获取sheet
    if sheet_name:
        sheet = workbook[sheet_name]
    else:
        sheet = workbook.active  # 激活的sheet(默认第一个)
    
    # 获取表头(第一行)
    headers = [cell.value for cell in sheet[1]]  # sheet[1] 表示第一行
    
    # 读取数据(从第二行开始)
    data = []
    for row in sheet.iter_rows(min_row=2, values_only=True):  # values_only=True直接取单元格值
        row_data = {headers[col]: row[col] for col in range(len(headers))}
        data.append(row_data)
    
    workbook.close()  # 关闭工作簿(释放资源)
    return data

# 测试
xlsx_data = read_xlsx_with_openpyxl("data.xlsx", sheet_name="订单表")
print("openpyxl读取.xlsx结果:", xlsx_data[:2])

3. pandas:高效读取 Excel(兼容 .xls/.xlsx)

pandas 封装了 xlrd(.xls)和 openpyxl(.xlsx),一行代码即可读取,返回 DataFrame 方便后续数据处理(筛选、统计等)。

安装

pip install pandas openpyxl  # openpyxl用于支持.xlsx

示例:pandas 读取 Excel

import pandas as pd

def read_excel_with_pandas(file_path, sheet_name=0):
    """
    用pandas读取Excel(.xls/.xlsx)
    :param file_path: 文件路径
    :param sheet_name: 工作表名或索引(0表示第一个)
    :return: DataFrame(可转为列表/字典)
    """
    # 读取Excel(自动识别格式,.xls需xlrd支持)
    df = pd.read_excel(
        io=file_path,
        sheet_name=sheet_name,
        header=0  # 第0行作为表头
    )
    
    # 处理空值(将NaN转为None)
    df = df.where(pd.notnull(df), None)
    
    # 转为列表套字典(方便通用处理)
    return df.to_dict("records")

# 测试
excel_data = read_excel_with_pandas("data.xlsx", sheet_name="用户表")
print("pandas读取Excel结果:", excel_data[:2])

三、CSV 文件读取

CSV(逗号分隔值)是轻量文本格式,Python 内置 csv 模块和 pandas 均可处理,后者更简洁。

1. 内置 csv 模块:灵活控制格式

适合需要自定义分隔符、编码或处理复杂 CSV 格式的场景。

示例:读取 CSV

import csv

def read_csv_with_csv(file_path, encoding="utf-8", delimiter=","):
    """
    用内置csv模块读取CSV
    :param file_path: 文件路径
    :param encoding: 编码(如gbk、utf-8)
    :param delimiter: 分隔符(默认逗号)
    :return: 数据列表(每行一个字典)
    """
    data = []
    with open(file_path, "r", encoding=encoding, newline="") as f:
        # 读取为字典(表头为key)
        reader = csv.DictReader(f, delimiter=delimiter)
        for row in reader:
            # 转换空字符串为None
            row_data = {k: v if v != "" else None for k, v in row.items()}
            data.append(row_data)
    return data

# 测试(读取UTF-8编码的CSV)
csv_data = read_csv_with_csv("data.csv", encoding="utf-8")
print("csv模块读取结果:", csv_data[:2])

2. pandas 读取 CSV:高效简洁

一行代码完成读取,自动处理数据类型(如数字、日期)。

示例:pandas 读取 CSV

import pandas as pd

def read_csv_with_pandas(file_path, encoding="utf-8", delimiter=","):
    """
    用pandas读取CSV
    :return: 列表套字典
    """
    df = pd.read_csv(
        file_path,
        encoding=encoding,
        delimiter=delimiter,
        parse_dates=False  # 如需自动解析日期,设为True或指定列名
    )
    df = df.where(pd.notnull(df), None)  # 空值处理
    return df.to_dict("records")

# 测试(读取GBK编码的CSV)
csv_data_pd = read_csv_with_pandas("data_gbk.csv", encoding="gbk")
print("pandas读取CSV结果:", csv_data_pd[:2])

四、二次封装:通用文件读取工具类

为简化调用,封装一个 FileReader 类,自动识别文件格式(.xls/.xlsx/.csv),统一接口返回 “列表套字典” 格式,支持异常处理。

import os
import pandas as pd
from pandas.errors import ParserError

class FileReader:
    """通用文件读取工具类(支持Excel和CSV)"""
    
    @staticmethod
    def read(file_path, sheet_name=None, encoding="utf-8"):
        """
        自动识别格式并读取文件
        :param file_path: 文件路径
        :param sheet_name: Excel工作表名(CSV无需指定)
        :param encoding: 编码(主要用于CSV)
        :return: 数据列表 [{"字段1": 值1, ...}, ...]
        """
        # 检查文件是否存在
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在:{file_path}")
        
        # 获取文件后缀(小写)
        ext = os.path.splitext(file_path)[1].lower()
        
        try:
            if ext in [".xls", ".xlsx"]:
                # 读取Excel
                return FileReader._read_excel(file_path, sheet_name)
            elif ext == ".csv":
                # 读取CSV
                return FileReader._read_csv(file_path, encoding)
            else:
                raise ValueError(f"不支持的文件格式:{ext},仅支持.xls/.xlsx/.csv")
        except Exception as e:
            raise Exception(f"读取文件失败:{str(e)}")
    
    @staticmethod
    def _read_excel(file_path, sheet_name):
        """内部方法:读取Excel"""
        sheet = sheet_name if sheet_name is not None else 0
        df = pd.read_excel(file_path, sheet_name=sheet, header=0)
        df = df.where(pd.notnull(df), None)
        return df.to_dict("records")
    
    @staticmethod
    def _read_csv(file_path, encoding):
        """内部方法:读取CSV"""
        df = pd.read_csv(file_path, encoding=encoding, header=0)
        df = df.where(pd.notnull(df), None)
        return df.to_dict("records")

# 测试二次封装工具
if __name__ == "__main__":
    try:
        # 读取Excel
        excel_data = FileReader.read("data.xlsx", sheet_name="用户表")
        print("工具类读取Excel:", len(excel_data), "行")
        
        # 读取CSV
        csv_data = FileReader.read("data.csv", encoding="utf-8")
        print("工具类读取CSV:", len(csv_data), "行")
    except Exception as e:
        print("错误:", e)

五、关键注意事项

  1. 格式兼容性
    • .xls 用 xlrdpandas(依赖 xlrd);
    • .xlsx 用 openpyxlpandas(依赖 openpyxl);
    • 避免混用库(如用 openpyxl 读 .xls 会报错)。
  2. 编码问题
    • CSV 常见编码:utf-8(推荐)、gbk(Windows 生成的文件可能用此编码);
    • 读取乱码时尝试更换编码(如 encoding="gbk")。
  3. 空值处理
    • Excel/CSV 中的空单元格会被读为 NaN(pandas)或 ''(csv 模块),需统一转为 None 便于后续处理。
  4. 性能优化
    • 大文件(10 万行 +)优先用 pandas(基于 C 扩展,速度快);
    • 读取部分列:pandas 可通过 usecols=["列1", "列2"] 指定,减少内存占用。
  5. 复杂格式
    • 合并单元格、公式、图片等复杂内容:用 openpyxl(.xlsx)或 xlrd(.xls)手动解析;
    • pandas 对复杂格式支持较弱,适合纯数据读取。

总结

  • 简单读取:优先用 pandas,一行代码搞定,支持多格式,返回 DataFrame 便于后续处理;
  • 精细控制:.xls 用 xlrd,.xlsx 用 openpyxl,适合处理合并单元格、公式等复杂场景;
  • CSV 处理:轻量用内置 csv 模块,高效用 pandas
  • 项目复用:通过二次封装(如 FileReader 类)统一接口,减少重复代码,提高可维护性。

根据文件格式、大小和复杂度选择合适的工具,可大幅提升数据读取效率。

Python正则表达式(json、Xpath、email、ip地址等)

正则表达式是处理字符串模式匹配的强大工具,在 Python 中通过 re 模块实现。它能高效解决格式验证(如邮箱、IP)、信息提取(如从文本中抓取值)、格式转换等问题。以下针对常见场景(邮箱、IP、JSON 格式检查、XPath 模式匹配)详解正则表达式的设计与应用。

一、正则表达式核心语法(快速回顾)

元字符 / 语法 含义 示例
. 匹配任意字符(除换行) a.b 匹配 aaba1b
* 前一个字符匹配 0 次或多次 ab* 匹配 aababb
+ 前一个字符匹配 1 次或多次 ab+ 匹配 ababb
? 前一个字符匹配 0 次或 1 次 ab? 匹配 aab
{n,m} 前一个字符匹配 n 到 m 次 a{2,3} 匹配 aaaaa
^ 匹配字符串开头 ^abc 匹配 abc123
$ 匹配字符串结尾 abc$ 匹配 123abc
[] 字符集,匹配其中任一字符 [0-9a-zA-Z] 匹配数字或字母
() 分组,提取匹配的子串 (ab)+ 匹配 ababab
\d 匹配数字(等价于 [0-9] \d{3} 匹配 123
\w 匹配字母、数字、下划线([a-zA-Z0-9_] \w+ 匹配 user123
\s 匹配空白字符(空格、制表符等) a\sb 匹配 a b
| 逻辑或 a|b 匹配 ab
\ 转义字符(取消元字符特殊含义) a\.b 匹配 a.b(不匹配 a1b

二、常见场景实战

1. 邮箱地址(Email)验证

邮箱格式规则(简化版):

  • 用户名:可包含字母、数字、下划线、点、加号(+
  • 必须包含 @
  • 域名:至少包含一个 .,如 comco.uk

正则表达式

import re

email_pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'

def is_valid_email(email):
    """验证邮箱格式是否合法"""
    return re.match(email_pattern, email) is not None

# 测试
test_emails = [
    "[email protected]",    # 合法
    "[email protected]",  # 合法(含点、加号、多级域名)
    "invalid-email",       # 非法(无@)
    "[email protected]",           # 非法(域名开头为.)
    "user@com"             # 非法(域名无.)
]

for email in test_emails:
    print(f"{email}: {'合法' if is_valid_email(email) else '非法'}")

解析

  • ^[a-zA-Z0-9_.+-]+:匹配用户名(字母、数字、_.+-,至少 1 个字符)
  • @:必须包含 @
  • [a-zA-Z0-9-]+:匹配域名前缀(如 examplegoogle
  • \.[a-zA-Z0-9-.]+$:匹配域名后缀(如 .com.co.uk,至少 1 个.
2. IP 地址(IPv4)验证

IPv4 格式规则:

  • 由 4 段数字组成,用 . 分隔
  • 每段数字范围:0-255

正则表达式

ip_pattern = r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$'

def is_valid_ipv4(ip):
    """验证IPv4地址格式是否合法"""
    return re.match(ip_pattern, ip) is not None

# 测试
test_ips = [
    "192.168.1.1",    # 合法
    "255.255.255.255",# 合法(最大值)
    "0.0.0.0",        # 合法(最小值)
    "192.168.0.256",  # 非法(256>255)
    "192.168.1",      # 非法(仅3段)
    "192.168.1.01"    # 合法但不推荐(前导0,正则允许但实际应避免)
]

for ip in test_ips:
    print(f"{ip}: {'合法' if is_valid_ipv4(ip) else '非法'}")

解析

  • (25[0-5]|2[0-4]\d|[01]?\d\d?)
    

    :匹配 0-255 的数字

    • 25[0-5]:250-255
    • 2[0-4]\d:200-249
    • [01]?\d\d?:0-199(含 0-9、10-99、100-199)
  • ((...)\.){3}:匹配前 3 段数字 + .(如 192.168.1.

  • 最后一段同上,确保整体为 4 段
3. JSON 格式简易检查(非完整解析)

JSON 格式特点(简化):

  • 键值对用 {} 包裹
  • 键和字符串值用双引号 " 包裹
  • 分隔符为 ,

注意:正则无法完全解析 JSON(因嵌套结构复杂),但可用于初步格式校验(如检查是否有未闭合的括号、错误引号等)。

正则表达式

python

运行

json_pattern = r'^\s*\{(?:\s*"[^"]+"\s*:\s*(?:"[^"]*"|\d+|true|false|null)\s*,?\s*)*\s*\}$'

def is_simple_json(s):
    """简易检查JSON格式(支持基本键值对,不支持嵌套)"""
    return re.match(json_pattern, s) is not None

# 测试
test_jsons = [
    '{"name": "Alice", "age": 30}',  # 合法
    ' { "id": 1, "active": true } ',  # 合法(允许空格)
    '{"name": Alice}',                # 非法(值未用双引号)
    '{"name": "Bob",}',               # 非法(末尾多余逗号)
    '{"name": "Charlie"',             # 非法(缺少闭合})
    '{"user": {"id": 1}}'             # 非法(嵌套结构,简易正则不支持)
]

for js in test_jsons:
    print(f"{js}: {'合法' if is_simple_json(js) else '非法'}")

解析

  • ^\s*\{:开头允许空格,后跟 {

  • (?:...)
    

    :非捕获分组,匹配键值对

    • "[^"]+":键(双引号包裹,内容非双引号)
    • \s*:\s*:冒号(允许前后空格)
    • (?:"[^"]*"|\d+|true|false|null):值(字符串、数字、布尔、null)
    • ,?\s*:允许逗号(最后一个键值对可省略)
  • \s*\}$:结尾允许空格,后跟 }
4. XPath 表达式模式匹配

XPath 用于定位 XML/HTML 元素,常见格式如:

  • 绝对路径:/html/body/div
  • 相对路径://div[@class="container"]
  • 属性匹配://a[@href="/login"]

正则表达式(匹配基础 XPath 格式):

xpath_pattern = r'^(/?[\w-]+(?:\[@[\w-]+"[^"]*"\])?(/|//)?)*$'

def is_basic_xpath(xpath):
    """检查是否为基础XPath格式(支持元素、属性筛选)"""
    return re.match(xpath_pattern, xpath) is not None

# 测试
test_xpaths = [
    "/html/body/div",                # 合法(绝对路径)
    "//div[@class='container']",     # 合法(相对路径+属性)
    "//a[@href='/login']/span",      # 合法(嵌套)
    "//div[@id=123]",                # 非法(属性值未用引号)
    "//div.class",                   # 非法(错误的属性语法)
    "/html//body"                    # 合法(双斜杠)
]

for xp in test_xpaths:
    print(f"{xp}: {'合法' if is_basic_xpath(xp) else '非法'}")

解析

  • /?:可选的开头 /(绝对 / 相对路径)
  • [\w-]+:元素名(字母、数字、下划线、连字符)
  • (?:\[@[\w-]+"[^"]*"\])?:可选的属性筛选(如 [@class="box"]
  • (/|//)?:路径分隔符(///
  • 整体重复匹配多级路径
5. 从文本中提取目标信息(综合示例)

需求:从一段文本中提取所有邮箱、IP 地址和手机号(国内手机号规则:11 位数字,以 1 开头)。

import re

text = """
联系我们:[email protected][email protected]
服务器IP:192.168.1.100、255.255.255.0(网关)
客服电话:13800138000、13912345678(工作时间)
无效信息:invalid-email、256.0.0.1、1234567890(短号)
"""

# 定义各模式
email_pattern = r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+'
ip_pattern = r'\b(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'
phone_pattern = r'\b1[3-9]\d{9}\b'  # 国内手机号:1开头,第2位3-9,共11位

# 提取信息
emails = re.findall(email_pattern, text)
ips = re.findall(ip_pattern, text)
phones = re.findall(phone_pattern, text)

print("提取的邮箱:", emails)
print("提取的IP:", ips)
print("提取的手机号:", phones)

输出

提取的邮箱: ['[email protected]', '[email protected]']
提取的IP: ['192.168.1.100', '255.255.255.0']
提取的手机号: ['13800138000', '13912345678']

关键:使用 re.findall() 提取所有匹配项,通过 \b(单词边界)避免部分匹配(如手机号避免匹配 12 位数字中的前 11 位)。

三、Python re 模块核心方法

方法 作用 示例
re.match(pattern, string) 从字符串开头匹配,返回 Match 对象或 None re.match(r'abc', 'abc123') 匹配成功
re.search(pattern, string) 搜索整个字符串,返回第一个匹配的 Match 对象 re.search(r'abc', 'xabcx') 匹配成功
re.findall(pattern, string) 返回所有匹配的子串列表 re.findall(r'\d+', 'a1b2c3')['1','2','3']
re.sub(pattern, repl, string) 替换匹配的子串 re.sub(r'\d', '*', 'a1b2')'a*b*'
re.compile(pattern) 编译正则表达式为 Pattern 对象,提高复用效率 p = re.compile(r'\d+') 后用 p.match(...)

四、最佳实践与注意事项

  1. 原始字符串(r'':正则表达式前加 r 避免转义字符冲突(如 r'\d''\\d' 更清晰)。

  2. 贪婪与非贪婪匹配:默认贪婪(尽可能多匹配),加 ? 变为非贪婪(尽可能少匹配)。例如 r'<.*>' 匹配 <a><b> 整体,r'<.*?>' 仅匹配 <a>

  3. 性能优化:频繁使用的正则用 re.compile() 编译,减少重复解析开销。

  4. 复杂场景优先专用库

    • JSON 解析用 json 模块(而非正则);
    • XPath 解析用 lxml 库(支持完整语法);
    • 正则适合简单格式验证 / 提取,不适合复杂语法解析。

总结

正则表达式在 Python 中是处理字符串的 “瑞士军刀”,针对邮箱、IP 等格式验证,以及信息提取场景非常高效。核心是根据目标格式设计精准的匹配模式,结合 re 模块的 match/search/findall 等方法实现需求。但需注意:复杂结构(如嵌套 JSON、完整 XPath)应优先使用专用库,正则作为辅助工具。


上一篇 如何锻体

下一篇 9月11日

Comments

Content