- Python
- 精通Python编程从零基础到实战
- 8.1 Python初阶:基础语法
- Python简介&环境搭建&以及环境变量原理
- Python常用IDE以及免安装开发环境(如Anaconda、PyCharm等)
- Python基础语法和基本数据类型(Number、String)
- Python高级数据类型(Tuple、List、Set、Dict)
- Python函数和模块基础知识以及高级应用
- 一、函数基础
- 二、模块基础
- 一、定义包级别的变量
- 二、定义包级别的函数
- 三、高级技巧:控制包级成员的可见性
- 四、注意事项
- 三、包的导入方式
- 四、相对导入与绝对导入
- 五、包的高级应用
- 六、最佳实践
- 三、函数高级应用
- 常见用途:
- 一、核心内置函数
- 二、
functools模块工具 - 四、函数式编程风格特点
- 五、适用场景
- 四、模块高级应用
- 五、实用技巧
- Python运算符(算术、关系、赋值、逻辑、位、身份、三目等)
- Python流程控制之if判断
- Python流程控制之循环语句(While循环&For循环)
- Python流程控制之
try-except
- Python:函数和类
- 8.2 Python初阶:函数和模块
- Python函数(def)以及函数的分类
- Python函数参数分类(必选参数&关键字参数&默认参数&不定长参数)
- Python函数的嵌套、函数递归、匿名函数
- Python模块和包的分类
- Python模块的导入(import&from…import…)
- Python标准库(date, datetime, os模块, json模块, jsonpath模块, 文件处理)
- Python输入输出(print&input及基础输出优化)
- Python异常处理(捕获异常try…except…finally&抛出异常raise)
- 8.3 Python高阶:面向对象编程
- 8.4 Python高阶:自动化框架专题
- Logging日志处理模块(logging, FileHandler, StreamHandler, 日志等级)
- Python序列化和反序列化详解
- 外部数据库MySQL高级应用(MySqldb、Python操作实现CURD、事务机制)
- 外部数据库Mysql常用管理(yum安装、yum卸载、pyyaml模块操作)
- 外部配置文件Excel/Csv的读取(xlrd、xlwt、pandas、openpyxl、二次封装)
- Python正则表达式(json、Xpath、email、ip地址等)
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 的函数和模块是构建程序的核心组件,从基础到高级应用涵盖了丰富的编程思想和实践技巧。以下是系统的知识梳理:
一、函数基础
- 函数定义与调用
函数通过def关键字定义,使用缩进区分代码块:
def greet(name):
return f"Hello, {name}!"
print(greet("Python")) # 调用函数
- 参数类型
- 位置参数:按顺序传递的参数
位置参数是指按参数定义顺序传递的参数,必须按照函数定义时的参数顺序依次提供,否则会导致逻辑错误。
特点:
-
必须按顺序传递,数量需与函数定义一致(除非有默认值)
-
调用时直接写参数值,不指定参数名
-
关键字参数:指定参数名传递,可改变顺序
关键字参数是指通过 “参数名 = 值” 形式传递的参数,可以不按定义顺序传递,因为参数名明确指定了对应的参数。
特点:
- 传递时需指定参数名(
参数名=值) - 顺序可以任意调整
- 通常用于可选参数或默认参数
位置参数和关键字参数可以混合使用,但必须遵循 “位置参数在前,关键字参数在后” 的原则。
-
默认参数
:定义时指定默认值,调用时可省略
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:收集多余的关键字参数,打包成字典(用于可变数量的关键字参数)
- 返回值
- 用
return返回结果,可返回多个值(实际是元组) - 无
return时默认返回None
二、模块基础
- 模块定义与导入
-
一个
.py文件就是一个模块 -
导入方式:
import module_name # 导入整个模块 from module_name import func # 导入特定成员 from module_name import * # 导入所有成员(不推荐) import module_name as mn # 别名导入
- 包(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 文件是包的标识,主要功能包括:
- 初始化包:当包被导入时,会自动执行该文件中的代码
- 控制导出内容:通过
__all__变量指定from package import *时导入的模块列表
# mypackage/__init__.py
__all__ = ['module1', 'module2'] # 定义可导出的模块
3.简化导入路径:在 __init__.py 中导入子模块,简化外部导入
# mypackage/__init__.py
from . import module1 # 这样外部可以直接 import mypackage.module1
- 包级别的变量和函数:定义包级别的常量、函数等
在 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
使用方式:
-
外部导入使用:
import mypackage print(mypackage.__version__) # 输出:1.0.2 print(mypackage.MAX_CONNECTIONS) # 输出:10 -
包内部模块使用(如
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}"
使用方式:
-
外部调用:
import mypackage mypackage.init_config(60) # 调用包级函数修改配置 print(mypackage.format_message("操作完成")) # 输出:[1.0.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__ 中列出的成员,保护内部实现。
四、注意事项
- 全局状态管理:包级变量是全局的,修改后会影响整个程序中使用该包的部分,需谨慎操作(尤其在多线程环境)。
- 初始化逻辑:复杂的初始化代码(如读取配置文件、连接数据库)可以放在包级函数中,而非直接写在
__init__.py顶层,避免导入包时就执行耗时操作。 - 相对导入:包内部模块引用包级成员时,需使用相对导入(
from . import xxx),避免绝对路径导致的可移植性问题。 - 命名规范:
- 公开的包级变量 / 函数使用常规命名(如
MAX_SIZE、setup())。 - 内部私有成员以单下划线开头(如
_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__ 中指定的模块
四、相对导入与绝对导入
在包内部模块之间导入时,可以使用:
-
绝对导入:从顶层包开始的完整路径
# 在 subpackage/module3.py 中 from mypackage.module1 import function1 -
相对导入:使用
.表示当前目录,..表示父目录(仅在包内部使用)# 在 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 包含 part1 和 part2,可以将两个 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.py 或 pyproject.toml,例如:
# setup.py
from setuptools import setup, find_packages
setup(
name="mypackage",
version="1.0.0",
packages=find_packages(),
)
六、最佳实践
- 始终在包目录中添加
__init__.py,明确标识包并控制导出内容 - 避免使用
from package import *,除非是有意设计的公共 API - 包内部优先使用相对导入,外部使用绝对导入
- 大型项目采用分层结构,合理划分子包
- 通过
__all__明确指定公共接口,隐藏内部实现细节
掌握包的使用可以帮助你构建结构清晰、易于维护的大型 Python 项目,是从脚本开发转向工程化开发的重要一步。
- 模块搜索路径
Python 按以下顺序查找模块:
- 当前目录
- 环境变量
PYTHONPATH指定的路径 - Python 安装目录的标准库路径
可通过sys.path查看和修改搜索路径。
三、函数高级应用
-
匿名函数(lambda)
简洁的单行函数,适用于简单逻辑:
add = lambda x, y: x + y
print(add(3, 5)) # 输出:8
在 Python 中,lambda 是用于创建匿名函数的关键字,它允许你快速定义简单的、单行的函数,而无需使用 def 关键字进行完整的函数定义。
lambda 的特点:
- 匿名性:lambda 函数没有正式名称,通常通过赋值给变量来使用
- 简洁性:只能包含一个表达式,适合简单逻辑
- 单行性:通常写在一行内
- 表达式限制:只能是单个表达式,不能包含复杂结构(如循环、条件语句只能用三元表达式)
与普通函数的对比:
上面的 lambda 表达式等价于:
def add(x, y):
return x + y
常见用途:
-
作为简单函数的快捷方式,尤其是在需要临时函数的场景
-
与高阶函数(如
map()、filter()、sorted())配合使用:numbers = [(1, 3), (4, 1), (2, 5)] # 按元组第二个元素排序 sorted_numbers = sorted(numbers, key=lambda x: x[1]) -
作为回调函数或简单的处理逻辑
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)。 - 下次调用生成器时,从上次暂停的位置继续执行(而不是从头开始)。
生成器的核心优势是惰性计算:不会一次性生成所有结果,而是按需产生,节省内存(尤其适合处理大量数据或无限序列)。
四、函数式编程风格特点
-
纯函数优先:函数输出仅由输入决定,无副作用(不修改外部状态)。
-
不可变数据:避免修改原有数据,而是返回新数据(如使用元组、冻结集合)。
-
函数组合
:将复杂逻辑拆分为多个简单函数,通过组合实现功能。
# 函数组合示例 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
五、适用场景
- 数据处理与转换(如列表推导的函数式替代)
- 并行计算(纯函数无副作用,适合并发)
- 配置化逻辑(通过函数组合动态构建功能)
- 简化代码(用内置函数替代循环)
这些工具的核心价值在于减少副作用、提高代码复用性,并使逻辑更简洁清晰。对于复杂场景,也可结合第三方库(如 toolz、fn)增强函数式编程能力。
四、模块高级应用
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]
二、关系运算符(比较运算符)
用于比较两个值,返回布尔值 True 或 False。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
== |
等于 | 5 == 5 |
True |
!= |
不等于 | 5 != 3 |
True |
> |
大于 | 5 > 3 |
True |
< |
小于 | 5 < 3 |
False |
>= |
大于等于 | 5 >= 5 |
True |
<= |
小于等于 | 3 <= 1 |
False |
注意:
==比较值是否相等,is比较身份(见下文 “身份运算符”)- 支持链式比较:
1 < 3 < 5→True
三、赋值运算符
用于给变量赋值,可结合算术运算符简化写法。
| 运算符 | 描述 | 示例(等价于) |
|---|---|---|
= |
基础赋值 | x = 5 |
+= |
加法赋值 | x += 3 → x = x + 3 |
-= |
减法赋值 | x -= 2 → x = x - 2 |
*= |
乘法赋值 | x *= 4 → x = x * 4 |
/= |
除法赋值 | x /= 2 → x = x / 2 |
//= |
整除赋值 | x //= 3 → x = x // 3 |
%= |
取模赋值 | x %= 5 → x = x % 5 |
**= |
幂运算赋值 | x **= 3 → x = x** 3 |
多变量赋值:
a, b = 1, 2 # 同时给多个变量赋值
x = y = 10 # 链式赋值
四、逻辑运算符
用于组合布尔表达式,返回 True 或 False。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
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 & 0b1100 → 0b1000 | 8 | | |
| ` | 按位或(有 1 则 1) | 0b10100b1100→0b1110 | 14 | | | | |
| ^ | 按位异或(不同则 1) | 0b1010 ^ 0b1100 → 0b0110 | 6 | | |
| ~ | 按位取反(0 变 1,1 变 0) | ~0b1010 → -0b1011 | -11 | | |
| « | 左移(高位丢弃,低位补 0) | 0b1010 « 1 → 0b10100 | 20 | | |
| » | 右移(低位丢弃,高位补符号位) | 0b1010 » 1 → 0b101 | 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) # "良好"
九、运算符优先级(简化版)
从高到低排序(可通过括号 () 改变优先级):
- 幂运算
** - 正负号
+x、-x - 算术运算符(
*、/、//、%高于+、-) - 位运算符(
~高于<<、>>高于&高于^高于|) - 关系运算符(
==、!=、>、<等) - 身份运算符(
is、is not) - 成员运算符(
in、not in) - 逻辑非
not - 逻辑与
and - 逻辑或
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
关键点:
- 条件表达式的结果必须是布尔值(
True或False) - 代码块必须通过缩进(通常是 4 个空格)区分,缩进是 Python 语法的一部分
elif和else都是可选的,可根据需求组合使用
二、单分支判断(仅 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 后的条件表达式不仅可以是比较运算,还可以是任何返回布尔值的表达式:
-
比较运算(最常用):
x = 10 if x > 5 and x < 15: # 逻辑运算符组合条件 print("x在5到15之间") -
成员判断(
in/not in):fruits = ["apple", "banana"] if "apple" in fruits: print("苹果在列表中") -
身份判断(
is/is not):a = None if a is None: print("变量a未赋值") -
真值判断(利用 Python 中 “假值” 的特性): Python 中以下值被视为
False:None、0、""、[]、{}、()等空对象。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 层)。
七、常见用法与技巧
-
多条件并列: 使用逻辑运算符
and(且)、or(或)组合多个条件:# 判断年份是否为闰年(能被4整除且不能被100整除,或能被400整除) year = 2020 if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0): print(f"{year}是闰年") -
与三目运算符结合: 简单的
if-else可简化为三目表达式:# 等价于 if-else 结构 result = "通过" if score >= 60 else "未通过" -
提前退出(卫语句): 对于复杂条件,可先判断 “不满足条件” 的情况并提前退出,减少嵌套:
def calculate(a, b): if b == 0: # 提前判断异常情况 print("除数不能为0") return # 正常逻辑(无需嵌套) print(a / b)
八、常见错误
- 缩进错误:忘记缩进或缩进不一致会导致
IndentationError - 条件后缺少冒号:
if、elif、else后必须加冒号: - 误用
=代替==:if x = 5是赋值而非判断,会导致语法错误 - 条件顺序错误:如先判断
score >= 60再判断score >= 90,会导致高分条件永远不触发
if 语句是 Python 中实现分支逻辑的基础,掌握其用法能让程序根据不同情况做出灵活响应。在实际开发中,合理组织条件判断的结构,能显著提高代码的可读性和可维护性。
Python流程控制之循环语句(While循环&For循环)
在 Python 中,循环语句用于重复执行一段代码,是实现 “批量处理”“迭代遍历” 等逻辑的核心工具。主要分为 while 循环(基于条件重复)和 for 循环(基于可迭代对象遍历),两者适用场景不同,但都支持灵活的流程控制(如 break、continue)。
一、while 循环:基于条件的重复
while 循环的核心逻辑是:只要条件表达式为 True,就持续执行循环体,直到条件变为 False 时退出。
1. 基本语法
while 条件表达式:
# 循环体(缩进代码块)
语句1
语句2
# (可选)更新条件的语句(避免死循环)
执行流程:
- 判断
条件表达式的值(True/False)。 - 若为
True,执行循环体;若为False,退出循环,执行后续代码。 - 循环体执行完毕后,回到步骤 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
三、循环控制:break 与 continue
break 和 continue 是用于 “精细控制循环流程” 的关键字,可在循环体内根据条件调整执行逻辑。
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未被遍历)
continue:跳过当前迭代,进入下一次
continue 会跳过当前循环体的剩余代码,直接回到循环开头,判断下一次迭代是否执行(不终止整个循环)。
示例:打印 1~10 中的奇数
for num in range(1, 11):
if num % 2 == 0: # 若为偶数,跳过后续打印
continue
print(f"奇数:{num}") # 仅奇数会执行此句
# 输出:
# 奇数:1
# 奇数:3
# 奇数:5
# 奇数:7
# 奇数:9
3. break 与 continue 的区别
| 关键字 | 作用 | 对循环的影响 |
|---|---|---|
break |
强制退出 | 终止整个循环,不再执行后续迭代 |
continue |
跳过当前迭代 | 仅跳过当前次,继续下一次迭代 |
四、嵌套循环
循环内部可以嵌套另一个循环(while 或 for 均可),用于处理 “多层结构” 的数据(如二维列表、矩阵等)。
示例:打印九九乘法表(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. 批量处理元素(如字典键值对) |
语法简洁,无需手动管理循环变量,不易死循环 |
六、常见错误与注意事项
while循环的死循环:忘记更新循环条件(如count未递增),导致条件永远为True。for循环修改可迭代对象:遍历列表时直接修改列表(如del或append),可能导致遍历异常(建议遍历副本,如for x in list.copy())。- 缩进错误:循环体必须缩进(4 个空格),否则会报
IndentationError,或导致代码逻辑错误(如循环体外的代码被误判为循环内)。 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:
# 无论是否发生异常,都会执行的代码(可选)
清理操作
执行流程:
- 执行
try块中的代码。 - 如果发生异常,立即跳转到对应的
except块处理。 - 如果无异常,执行
else块(若有)。 - 最后一定会执行
finally块(若有)。
二、核心组件解析
try块
- 包含可能引发异常的代码(如文件操作、网络请求、数据转换等)。
- 一旦某行代码引发异常,后续代码不再执行,直接进入异常处理流程。
try:
print("尝试转换数字...")
num = int("abc") # 这里会引发 ValueError
print("转换成功") # 此行不会执行(因为上一行已出错)
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)。
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:对象属性不存在
可以通过官方文档查看完整的异常层级结构。
六、最佳实践
- 捕获具体异常:避免用
except Exception捕获所有异常,应针对性处理已知错误。 - 提供有用信息:在异常处理中说明错误原因,便于调试(如
print(f"错误:{e}"))。 - 清理资源优先:用
finally或with语句确保资源(文件、网络连接等)被正确释放。 - 不滥用异常:简单的条件判断(如
if x < 0)比try-except更高效,不应替代if使用。 - 避免空
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)内置函数(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)第三方库函数
来自第三方库(如 numpy、pandas)提供的函数,需先安装并导入库才能使用。
示例:
import numpy as np
print(np.mean([1, 2, 3, 4])) # 第三方库 numpy 的均值函数,输出:2.5
- 按参数形式分类
(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)匿名函数(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 中一种强大的语法,它允许你在不修改原函数代码的前提下,为函数添加额外功能(如日志记录、性能测试、权限验证等)。装饰器本质上是一个高阶函数(接收函数作为参数,并返回新函数)。
一、装饰器的基本原理
装饰器的核心逻辑是:
- 定义一个 “装饰器函数”,接收被装饰的函数作为参数。
- 在装饰器内部定义一个 “包装函数”(
wrapper),用于添加额外功能。 - 包装函数中调用原函数,并返回其结果。
- 装饰器返回包装函数,替代原函数。
通过 @装饰器名 语法,可以简化装饰器的使用(语法糖)。
二、装饰器用例详解
用例 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.wrapper → logger.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)更方便地保存状态(相比函数装饰器的闭包更直观)。
三、装饰器的常见应用场景
- 日志记录:自动记录函数调用、参数、返回值。
- 性能测试:统计函数执行时间。
- 权限验证:调用函数前检查用户权限(如 Web 框架中的登录验证)。
- 缓存结果:缓存函数调用结果,避免重复计算(如
functools.lru_cache)。 - 输入验证:检查函数参数是否符合要求。
总结
装饰器是 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 requestsimport 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(路径处理)。 - 数据处理模块:处理数据编解码、转换等,如
json、csv(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),用于提升性能或调用底层系统接口(如_socket是socket模块的 C 实现)。
二、包(Package)的分类
包是包含多个模块的目录,必须包含 __init__.py 文件(Python 3.3+ 允许省略,但仍推荐保留以明确标识为包)。其分类主要基于来源、结构和功能:
1. 按来源分类(与模块对应)
(1)内置包(Built-in Packages)
-
定义:Python 标准库中的包,由多个相关模块组成,无需安装。
-
示例:
os.path:os包下的path子包,处理路径相关功能。xml:包含xml.etree、xml.dom等模块,处理 XML 数据。http:包含http.client、http.server等模块,处理 HTTP 相关功能。
from os.path import join # 导入内置包的子模块 print(join("a", "b")) # 输出:a/b(路径拼接)
(2)第三方包(Third-party Packages)
-
定义:由社区开发的包含多个模块的功能集合,需通过
pip安装,通常比单个模块更复杂(可能包含多级子包)。 -
示例:
pandas:数据分析包,包含pandas.core、pandas.io等子包。flask:Web 框架包,包含flask.app、flask.routing等子包。scikit-learn:机器学习包,包含sklearn.model_selection、sklearn.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 中模块的导入是代码复用的核心机制,通过 import 和 from...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 查看):
- 当前执行脚本所在目录
- 系统环境变量
PYTHONPATH中的路径 - Python 标准库目录
- 第三方库安装目录(如
site-packages)
若模块不在这些路径中,导入会报错(ModuleNotFoundError),需手动将模块所在目录添加到 sys.path:
import sys
sys.path.append("/path/to/module/directory") # 添加路径
import my_module # 现在可导入
五、常见问题与最佳实践
- 避免循环导入:模块 A 导入模块 B,同时模块 B 导入模块 A,会导致报错。解决方法:重构代码,将共享逻辑抽离到新模块。
- 慎用
from...import \*:易引发命名冲突,推荐显式导入所需成员。 - 优先使用相对导入(包内部):在包内部模块间导入时,可用相对路径(如
from . import module,.表示当前目录)。 - 遵循命名规范:模块名用小写字母,多个单词用下划线连接(如
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 操作及文件处理相关的核心模块,包括 datetime、os、json 等,并补充说明非标准库但常用的 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. 格式化与解析(strftime 与 strptime)
-
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会转为 JSONnull。 - 自定义对象序列化需通过
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 函数结合 os、shutil 等模块实现文件读写和管理。
1. 文件读写基础(open 函数)
open(file, mode='r', encoding=None) 用于打开文件,返回文件对象,常用模式:
r:只读(默认);w:写入(覆盖原有内容);a:追加(在末尾添加)。b:二进制模式(如rb、wb,用于非文本文件如图片)。
示例:文本文件读写
# 写入文件
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 |
日期时间处理 | date、datetime、timedelta、strftime |
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 语句的核心价值:自动管理资源生命周期,无论代码块正常执行还是发生异常,都能保证资源被正确释放。
二、基本语法与执行流程
- R
with 上下文管理器表达式 as 变量:
# 资源使用代码块(缩进部分)
对资源的操作(如读写文件)
- 上下文管理器:一个实现了
__enter__()和__exit__()方法的对象(如文件对象、open()函数返回的对象),负责资源的获取和释放。 as 变量:可选,将__enter__()方法的返回值赋值给变量(方便在代码块中使用资源)。
2. 执行流程(以文件操作为例)
with open("test.txt", "r") as f: # 打开文件(获取资源)
content = f.read() # 使用资源
# 代码块结束后,自动关闭文件(释放资源),无需手动调用 f.close()
具体步骤:
- 执行
open("test.txt", "r"),返回一个文件对象(上下文管理器)。 - 调用该对象的
__enter__()方法:打开文件,返回文件对象本身(赋值给f)。 - 执行缩进的代码块(
f.read()):使用资源。 - 无论代码块是否正常执行(或发生异常),自动调用对象的
__exit__()方法:关闭文件(释放资源)。
三、为什么要用 with 语句?(对比手动管理)
以文件操作为例,对比 “手动管理” 和 “with 语句管理” 的差异:
- 手动管理资源(繁琐且危险)
# 手动打开文件
f = open("test.txt", "r")
try:
content = f.read() # 使用资源
finally:
f.close() # 确保关闭(即使发生异常)
- 缺点:必须用
try...finally保证close()执行,代码冗长;若忘记写finally或close(),会导致文件句柄泄漏。
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语句!
- 数据库连接
操作数据库时,连接需要及时关闭以释放资源,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())
- 线程锁(避免资源竞争)
多线程中,锁的获取和释放需成对出现,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_type、exc_val、exc_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 装饰器
对于简单场景,可使用标准库 contextlib 的 contextmanager 装饰器,无需手动实现 __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
-
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+,推荐)
在字符串前加 f 或 F,用 {变量/表达式} 直接嵌入值,简洁高效,是目前最推荐的方式。
# 直接嵌入变量
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. 无论是否有异常,都会执行此块(可选,用于资源清理)
清理操作(如关闭文件、释放连接)
执行顺序总结:
- 无异常:
try→else→finally - 有异常:
try(出错行后停止)→ 匹配的except→finally
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}")❌ 禁止使用空
except(except:):会捕获包括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}") # 输出:访问失败:只有管理员可访问此资源
四、异常处理的最佳实践
-
捕获具体异常,避免过宽:优先捕获
ValueError、FileNotFoundError等具体异常,而非直接用Exception,防止掩盖未知错误(如KeyboardInterrupt)。 -
提供明确的错误信息:在
raise或except中附带具体原因(如 “取款金额不能为负数” 而非 “参数错误”),便于调试和用户理解。 -
清理资源优先:用
finally或with确保资源(文件、连接)被释放,避免泄漏。 -
不滥用异常:简单的条件判断(如
if x < 0)比try...except更高效,异常仅用于 “不可预见的错误”(如文件突然被删除、网络中断)。 -
记录异常日志:在
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}")
- 类名规范:通常用首字母大写的驼峰式命名(如
Person、Car),与函数 / 变量的小写风格区分。 - 类的作用:作为模板,用于创建具体的 “对象”(实例)。
二、对象的创建:实例化类
对象是类的具体 “实例”(如 “张三” 是 “人” 类的一个实例)。通过类名加括号(类似函数调用)创建,称为 “实例化”。
示例:用 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__的name和age参数(self由 Python 自动传入,无需手动指定)。
四、self:引用实例本身
self 是类中实例方法的第一个参数,代表 “当前对象的引用”(类似其他语言的 this)。通过 self 可以:
- 访问当前对象的实例属性(如
self.name、self.age)。 - 调用当前对象的其他实例方法(如
self.speak())。
为什么需要 self?
类是模板,可创建多个对象(如 person1、person2)。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 → object(object 是所有类的根父类),因此 a.move() 优先调用 Runnable 的 move()。
(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:数值计算基础库
用途:处理多维数组(矩阵)、数学运算(线性代数、傅里叶变换等),是 pandas、matplotlib 等库的基础。
核心特点:用 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_book和return_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 未借出,无法归还
三、面向对象与异常处理结合的核心优势
- 代码模块化:
用类封装实体(
Book)和管理逻辑(Library),每个类职责明确,便于维护和扩展(如后续添加 “电子书” 类,可通过继承Book实现,符合继承特性)。 - 错误边界清晰:
自定义异常(如
BookNotFoundError)使错误类型可区分,开发者能针对性处理;异常在方法中抛出、在调用处捕获,符合 “谁调用谁处理” 的原则。 - 程序健壮性: 即使出现错误(如借阅不存在的图书),程序也能捕获异常并输出友好提示,而非直接崩溃,提升用户体验。
- 可扩展性:
若需新增功能(如 “预约图书”),只需在
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 被直接运行了!") 等)没有执行。
四、为什么需要这个结构?
- 区分 “运行” 和 “导入” 场景
一个模块可能既需要作为独立脚本运行(比如执行测试、展示功能),又需要被其他模块导入复用(比如提供工具函数)。
if __name__ == "__main__":可以让 “运行时的逻辑”(如测试代码、主程序)只在直接运行时执行,避免被导入时干扰其他模块。 - 存放测试代码
开发者常将模块的测试代码放在这个块内。例如
calculator.py中的add函数,直接运行时可以通过块内代码测试功能,而被导入时测试代码不会执行,不影响其他模块。 - 作为程序入口
对于复杂程序,通常会将主逻辑放在这个块内,使模块成为程序的 “入口文件”(类似其他语言的
main函数)。
五、总结
if __name__ == "__main__": 是 Python 中实现 “模块复用性” 和 “运行独立性” 的关键机制:
- 当模块被直接运行时,
__name__为"__main__",执行块内代码(如主逻辑、测试)。 - 当模块被导入时,
__name__为模块名,块内代码不执行,只提供函数 / 类供其他模块使用。
这使得 Python 模块既能作为独立程序运行,又能作为库被灵活复用,是编写高质量 Python 代码的基础技巧。
Python面向对象异常详解(类的缺陷,自定义缺陷)
在 Python 面向对象编程中,“类的缺陷” 通常指类在设计或运行时出现的不符合预期的状态或操作(如无效属性值、不支持的方法调用、状态冲突等)。这些 “缺陷” 需要通过异常机制来捕获和处理,而自定义异常是面向对象中处理类特定缺陷的核心手段 —— 它能让错误信息更精准、业务逻辑更清晰。
一、类的常见 “缺陷” 场景
类在使用过程中可能出现的 “缺陷” 本质上是违背类设计逻辑的操作或状态,常见场景包括:
- 属性值非法:类的属性被设置为不符合业务规则的值(如年龄为负数、价格为 0 以下)。
- 方法调用时机错误:在不恰当的状态下调用方法(如对已关闭的文件调用
read(),对已提交的订单调用cancel())。 - 资源冲突:多个操作竞争同一资源导致的冲突(如两个线程同时修改同一条数据)。
- 不支持的操作:对类实例调用其不支持的方法(如对 “只读文件” 调用
write())。
这些 “缺陷” 若不处理,可能导致程序逻辑错误或崩溃。通过异常(尤其是自定义异常)可以明确标记这些缺陷,并提供针对性的处理逻辑。
二、自定义异常:为类的 “缺陷” 定制错误类型
Python 允许通过继承Exception类定义自定义异常,用于描述类特有的缺陷。相比内置异常(如ValueError),自定义异常的优势在于:
- 语义明确:直接关联类的业务逻辑(如
InsufficientFundsError比ValueError更能说明 “余额不足”)。 - 便于区分处理:调用者可通过捕获特定异常,对不同缺陷执行不同逻辑(如 “余额不足” 提示充值,“账户冻结” 提示联系客服)。
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
三、面向对象中异常处理的设计原则
-
异常与类的职责绑定 自定义异常应与类的业务逻辑强相关(如
BankAccount的异常应围绕 “账户操作”),避免使用过于通用的异常(如直接raise Exception)。 -
在 “缺陷发生点” 及时抛出异常 类的内部(如
BankAccount.withdraw)应尽早检测到缺陷并抛出异常,而非将错误状态传递到外部,导致调试困难。 -
通过继承构建异常体系 对于复杂类或模块,可通过异常继承构建层级,便于批量处理。例如:
class AccountError(Exception): """账户相关异常的基类""" pass class InsufficientFundsError(AccountError): """余额不足(继承自AccountError)""" pass class AccountFrozenError(AccountError): """账户冻结(继承自AccountError)""" pass调用时可捕获基类
AccountError处理所有账户相关异常,也可捕获子类处理特定异常:try: acc.withdraw(2000) except InsufficientFundsError: # 处理余额不足 except AccountError: # 处理其他账户异常(如冻结) -
避免过度使用异常 类的轻微逻辑判断(如 “如果余额为 0 则提示”)应使用普通
if语句,异常仅用于 “非预期的缺陷”(如非法操作、资源错误)。
四、总结
在面向对象编程中,类的 “缺陷” 本质是违背设计逻辑的操作或状态,通过自定义异常可以精准描述这些缺陷,使错误处理更贴合业务需求。核心要点:
- 自定义异常需继承
Exception,并通过类名和属性传递错误语义; - 在类的方法中主动检测缺陷并抛出自定义异常,确保内部状态合法;
- 外部调用时通过
try...except捕获特定异常,执行针对性处理。
这种模式让类的逻辑更健壮,同时提高代码的可读性和可维护性 —— 使用者无需猜测错误原因,只需根据异常类型即可明确如何处理。
8.4 Python高阶:自动化框架专题
Logging日志处理模块(logging, FileHandler, StreamHandler, 日志等级)
在自动化框架(如测试框架、运维脚本、服务监控系统)中,日志(Logging) 是核心组件之一。它用于记录程序运行状态、错误详情、关键操作步骤等信息,是调试问题、监控系统、追溯流程的重要依据。Python 内置的 logging 模块提供了强大的日志处理能力,支持日志分级、多目标输出(控制台 / 文件)、格式化等功能,远优于简单的 print 语句。
一、logging 模块的核心优势
相比 print,logging 模块的核心优势:
- 日志分级:按重要程度区分日志(如调试信息、正常运行信息、错误信息),可按需过滤。
- 多目标输出:同时将日志输出到控制台(
StreamHandler)、文件(FileHandler)、甚至网络服务。 - 格式化输出:自定义日志格式(包含时间、模块名、日志等级等),便于分析。
- 灵活性:支持通过配置文件或代码动态调整日志行为(如运行时修改输出级别)。
二、日志等级(Logging Levels)
日志等级用于区分日志的重要性,logging 模块定义了 5 个标准等级(从低到高),等级越高,日志越重要:
| 等级常量 | 数值 | 含义 | 典型场景 |
|---|---|---|---|
DEBUG |
10 | 调试信息,用于开发阶段排查细节(如变量值、函数调用流程) | 打印循环变量、数据库查询语句 |
INFO |
20 | 正常运行信息,记录程序关键节点(如 “服务启动成功”“用户登录”) | 系统启动完成、任务开始执行 |
WARNING |
30 | 警告信息,表示存在潜在问题(如 “磁盘空间不足”“参数即将过期”) | 配置文件使用默认值、连接超时重试 |
ERROR |
40 | 错误信息,表示操作失败(如 “文件打开失败”“API 调用错误”) | 数据库连接失败、用户输入验证错误 |
CRITICAL |
50 | 严重错误,表明程序可能无法继续运行(如 “内存耗尽”“核心模块崩溃”) | 系统资源耗尽、关键服务中断 |
等级规则:
- 当设置日志等级为
LEVEL时,只有等级 ≥ LEVEL 的日志会被记录(低等级日志被过滤)。 - 默认等级为
WARNING(即默认只记录WARNING、ERROR、CRITICAL)。
三、日志处理器(Handlers)
logging 通过 “处理器” 控制日志的输出目标。常用处理器:
| 处理器类型 | 作用 | 适用场景 |
|---|---|---|
StreamHandler |
将日志输出到控制台(默认绑定 sys.stderr) |
开发调试、实时查看运行状态 |
FileHandler |
将日志输出到指定文件 | 持久化保存日志、事后分析 |
RotatingFileHandler |
日志文件达到指定大小后自动切割(如 100MB 换一个文件) | 避免单个日志文件过大 |
TimedRotatingFileHandler |
按时间自动切割日志(如每天一个文件) | 按时间维度管理日志(如每日日志) |
四、日志格式化(Formatters)
Formatter 用于定义日志的输出格式,可包含时间、日志等级、模块名、日志内容等信息。常用格式化符号:
| 符号 | 含义 |
|---|---|
%(asctime)s |
日志记录时间(默认格式:2023-10-01 12:00:00,123) |
%(levelname)s |
日志等级(如 DEBUG、ERROR) |
%(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日志,其余内容相同。
六、在自动化框架中的最佳实践
- 全局单例日志器:整个框架使用一个日志器实例(如上述
logger),避免重复配置。 - 分级日志策略:
- 开发 / 调试阶段:设置等级为
DEBUG,记录详细信息。 - 生产 / 运行阶段:设置等级为
INFO或WARNING,减少冗余日志。
- 开发 / 调试阶段:设置等级为
- 异常日志必记录:使用
logger.error(..., exc_info=True)记录异常栈(exc_info=True会打印完整的错误追踪),便于定位问题。 - 日志文件管理:使用
RotatingFileHandler或TimedRotatingFileHandler自动切割日志,避免文件过大。 - 日志格式标准化:包含时间、等级、模块、行号等关键信息,确保日志可追溯。
总结
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) | 是 | 二进制 | 高 | 高性能跨语言数据传输 |
四、最佳实践
- 优先考虑场景:
- 跨语言 / 网络传输:用
json或msgpack。 - Python 内部使用:用
pickle(便捷性优先)。
- 跨语言 / 网络传输:用
- 自定义对象处理:
pickle无需额外代码(自动处理)。json需实现to_dict()和from_dict()手动转换。
- 安全性:
- 绝对禁止用
pickle反序列化外部输入(如网络接收的字节流、未知文件)。 json可安全处理外部数据(仅解析基础类型,无执行风险)。
- 绝对禁止用
- 性能:大量数据或高频传输时,优先选
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_dbuser-- 创建数据库 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 服务地址(本地为localhost或127.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())
六、最佳实践与注意事项
- 防 SQL 注入:必须使用参数化查询(
%s占位符),禁止字符串拼接 SQL(如f"SELECT * FROM user WHERE name = '{name}'",黑客可输入' OR 1=1 --篡改逻辑)。 - 配置管理:数据库密码、地址等敏感信息,避免硬编码,应放在配置文件(如
config.ini)或环境变量中,并加密存储(生产环境)。 - 异常处理:所有数据库操作必须加
try-except,捕获pymysql.MySQLError等异常,避免程序崩溃。 - 连接管理:优先用
with语句或连接池,避免手动管理连接导致泄漏。 - 事务场景:涉及多步写操作(如转账、订单创建)时,必须用事务保证数据一致性。
总结
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 的连接配置(如host、user、password)写入 YAML 文件,避免硬编码到 Python 脚本中,实现 “配置与代码分离”(便于环境切换,如开发 / 测试 / 生产)。
1. 核心逻辑
- 编写 YAML 配置文件:存储多环境的 MySQL 连接信息;
- 用 PyYAML 读取配置:Python 脚本加载 YAML 中的配置;
- 连接 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. 关键注意事项
- 配置文件权限:YAML 中存储了数据库密码,需限制文件权限(如 Linux 下
chmod 600 mysql_config.yaml,仅当前用户可读写),避免泄露; - 安全加载 YAML:必须用
yaml.safe_load()而非yaml.load(),后者可能加载 YAML 中的恶意代码(如!!python/object/apply:os.system ["rm -rf /"]); - 环境切换:仅需修改
load_mysql_config(env="xxx")中的env参数,即可切换开发 / 测试 / 生产环境,无需修改脚本逻辑; - 异常处理:脚本中需捕获 “配置文件不存在”“连接失败”“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)
五、关键注意事项
- 格式兼容性:
- .xls 用
xlrd或pandas(依赖 xlrd); - .xlsx 用
openpyxl或pandas(依赖 openpyxl); - 避免混用库(如用
openpyxl读 .xls 会报错)。
- .xls 用
- 编码问题:
- CSV 常见编码:
utf-8(推荐)、gbk(Windows 生成的文件可能用此编码); - 读取乱码时尝试更换编码(如
encoding="gbk")。
- CSV 常见编码:
- 空值处理:
- Excel/CSV 中的空单元格会被读为
NaN(pandas)或''(csv 模块),需统一转为None便于后续处理。
- Excel/CSV 中的空单元格会被读为
- 性能优化:
- 大文件(10 万行 +)优先用
pandas(基于 C 扩展,速度快); - 读取部分列:
pandas可通过usecols=["列1", "列2"]指定,减少内存占用。
- 大文件(10 万行 +)优先用
- 复杂格式:
- 合并单元格、公式、图片等复杂内容:用
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 匹配 aab、a1b |
* |
前一个字符匹配 0 次或多次 | ab* 匹配 a、ab、abb |
+ |
前一个字符匹配 1 次或多次 | ab+ 匹配 ab、abb |
? |
前一个字符匹配 0 次或 1 次 | ab? 匹配 a、ab |
{n,m} |
前一个字符匹配 n 到 m 次 | a{2,3} 匹配 aa、aaa |
^ |
匹配字符串开头 | ^abc 匹配 abc123 |
$ |
匹配字符串结尾 | abc$ 匹配 123abc |
[] |
字符集,匹配其中任一字符 | [0-9a-zA-Z] 匹配数字或字母 |
() |
分组,提取匹配的子串 | (ab)+ 匹配 ab、abab |
\d |
匹配数字(等价于 [0-9]) |
\d{3} 匹配 123 |
\w |
匹配字母、数字、下划线([a-zA-Z0-9_]) |
\w+ 匹配 user123 |
\s |
匹配空白字符(空格、制表符等) | a\sb 匹配 a b |
| |
逻辑或 | a|b 匹配 a 或 b |
\ |
转义字符(取消元字符特殊含义) | a\.b 匹配 a.b(不匹配 a1b) |
二、常见场景实战
1. 邮箱地址(Email)验证
邮箱格式规则(简化版):
- 用户名:可包含字母、数字、下划线、点、加号(
+) - 必须包含
@ - 域名:至少包含一个
.,如com、co.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-]+:匹配域名前缀(如example、google)\.[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-2552[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(...) |
四、最佳实践与注意事项
-
原始字符串(
r''):正则表达式前加r避免转义字符冲突(如r'\d'比'\\d'更清晰)。 -
贪婪与非贪婪匹配:默认贪婪(尽可能多匹配),加
?变为非贪婪(尽可能少匹配)。例如r'<.*>'匹配<a><b>整体,r'<.*?>'仅匹配<a>。 -
性能优化:频繁使用的正则用
re.compile()编译,减少重复解析开销。 -
复杂场景优先专用库
:
- JSON 解析用
json模块(而非正则); - XPath 解析用
lxml库(支持完整语法); - 正则适合简单格式验证 / 提取,不适合复杂语法解析。
- JSON 解析用
总结
正则表达式在 Python 中是处理字符串的 “瑞士军刀”,针对邮箱、IP 等格式验证,以及信息提取场景非常高效。核心是根据目标格式设计精准的匹配模式,结合 re 模块的 match/search/findall 等方法实现需求。但需注意:复杂结构(如嵌套 JSON、完整 XPath)应优先使用专用库,正则作为辅助工具。