【云运维】Python基础(三)
Python基础(三)
第7章:文件与数据格式化
程序运行时的临时数据会随程序结束消失,而游戏角色属性、用户配置等数据需长期保存。Python 中通过文件操作实现数据持久化,
7.1:文件概述:计算机中的数据存储单元
文件是计算机中以外部介质(如硬盘)为载体的数据集合,文本文档、图片、程序等均属于文件,操作系统以文件为单位管理数据。
7.1.1 文件标识:找到唯一文件的 “地址”
文件标识用于定位计算机中唯一文件,由三部分组成:
- 文件路径:文件在存储介质中的位置(如
f:\a.txt) - 文件名主干:文件的核心名称(如
a) - 文件扩展名:标识文件类型(如
.txt、.png)

7.1.2 文件类型:按逻辑结构分类
文件按数据逻辑存储结构分为两类,物理层面均以二进制形式存储:
- 文本文件:存储文本字符数据,可通过记事本直接读写
- 二进制文件:需遵循特定序列化 / 反序列化规则,无法用普通文字处理程序直接打开(如图片、可执行程序)
7.1.3 Python 标准文件
sys 模块内置 3 个标准文件,对应输入输出设备:
- stdin:标准输入文件,关联键盘等输入设备
- stdout:标准输出文件,关联显示器等输出设备
- stderr:标准错误文件,关联显示器等输出设备
import sys
sys.stdout.write('lucky cloud') # 直接输出到显示器
7.2:文件基础操作:打开、关闭与读写
文件的核心操作包括打开、关闭、读写,所有复杂操作均基于这些基础动作,关键在于控制打开模式和正确处理资源。
7.2.1 文件打开模式:控制操作权限
open () 函数用于打开文件,返回文件对象,核心参数 mode 决定操作权限,常见模式如下:
| 打开模式 | 名称 | 描述 |
|---|---|---|
| r/rb | 只读模式 | 只读打开文本 / 二进制文件,文件不存在则报错 |
| w/wb | 只写模式 | 只写打开文本 / 二进制文件,文件存在则覆盖,不存在则创建 |
| a/ab | 追加模式 | 只写打开文本 / 二进制文件,仅在文件末尾追加数据,不存在则创建 |
| r+/rb+ | 读取(更新)模式 | 可读可写打开文本 / 二进制文件,文件不存在则报错 |
| w+/wb+ | 写入(更新)模式 | 可读可写打开文本 / 二进制文件,文件存在则覆盖,不存在则创建 |
| a+/ab+ | 追加(更新)模式 | 可读可写打开文本 / 二进制文件,仅在末尾追加数据,不存在则创建 |
7.2.2 打开与关闭文件:资源管理关键
- 打开文件:使用
open(file, mode='r', buffering=None)函数,file为文件路径 - 关闭文件:必须及时关闭以释放资源,避免文件句柄耗尽或数据丢失
两种关闭方式
- close () 方法:文件对象的内置方法,需手动调用
file = open('f:\\a.txt', 'r')
file.close() # 手动关闭文件
- with 语句:自动关闭文件,推荐使用(无需手动调用 close ())
with open('f:\\a.txt', 'r') as f:# 文件操作代码pass # 代码块结束后自动关闭文件
==思考:==为什么要及时关闭文件?
- 计算机中可打开的文件数量是有限。
- 打开的文件占用系统资源。
- 若程序因异常关闭,可能产生数据丢失。
7.2.3 文件读取:三种核心方法
Python 提供 read ()、readline ()、readlines () 三种读取方法,适用于不同场景:
1. read (size):读取指定字节数
- size 为读取字节数,默认读取全部数据
- 大文件不建议省略 size,避免耗尽内存
示例:读取f盘中luckycloud.txt的数据,代码示例如下:
方法一:
with open('f:\\luckycloud.txt', 'r', encoding='utf-8') as f:print(f.read(2)) # 读取前2个字节print(f.read()) # 读取剩余全部数据
方法二:
file = open('f:\\luckycloud.txt','r')
connect = file.read(2)
print(connect)
print('-'*30)
connect = file.read()
print(connect)
file.close()
2. readline ():逐行读取
- 每次读取一行数据,换行符
\n会被保留 - 适合大文件逐行处理,避免内存占用过高
with open('f:\\luckycloud.txt', 'r') as f:print(f.readline())print(f.readline())
运行结果:
actions speak louder than wordsluckycloud is here
3. readlines (hint):读取所有行并返回列表
readlines()方法可以一次读取文件中的所有数据,若读取成功,该方法会返回一个列表,文件中的每一行对应列表中的一个元素。语法格式如下:
readlines(hint=-1)
-
hint 控制读取行数(按字节总数限制),默认读取全部行
-
hint:单位为字节,用于控制要读取的行数如果行中数据的总大小超出了hint字节,readlines()不会再读取更多的行。
-
返回列表,每行数据为列表元素,大文件慎用
-
读取文件时若出现编码错误,可使用 chardet 库自动识别编码:
import chardet
# 先以二进制模式读取文件,检测编码
with open('f:\\luckycloud.txt', 'rb') as f:raw_data = f.read()encoding = chardet.detect(raw_data)['encoding'] # 获取编码格式
# 按检测到的编码读取文件
with open('f:\\luckycloud.txt', 'r', encoding=encoding, errors='ignore') as f:print(f.readlines())
运行结果:
['actions speak louder than words\n', 'luckycloud is here\n']
总结:
-
read()(参数缺省时)和readlines()方法都可一次读取文件中的全部数据,但因为计算机的内存是有限的,若文件较大,read()和readlines()的一次读取便会耗尽系统内存,所以这两种操作都不够安全。
-
为了保证读取安全,通常多次调用read()方法,每次读取size字节的数据。
7.2.4 文件写入:两种核心方法
写入文件需注意打开模式(w 覆盖、a 追加),核心方法为 write () 和 writelines ():
1. write (data):写入字符串
- 接收字符串参数,返回写入的字节数
- 适用于单行数据写入
with open('f:\\luckycloud.txt', 'w', encoding='utf-8') as f:size = f.write('I am planning to write Python') # 写入字符串print(size) # 输出写入的字节数:29
2. writelines (lines):写入字符串序列
- 接收字符串或字符串列表,无返回值
- 需显式添加换行符
\n,适用于多行数据写入
with open('f:\\luckycloud.txt', 'w', encoding='utf-8') as f:lines = ['Life is short\n', 'I use python'] # 字符串列表f.writelines(lines) # 按行写入,需手动加换行符
写入方法对比
- write ():仅接收字符串,单行写入更简洁
- writelines ():接收字符串列表,多行写入更高效,需手动处理换行
7.2.5 字符编码:文本文件的底层规则
不同编码方式下,字符与字节的对应关系不同,常见编码及特点:
| 编码方式 | 支持语言 | 中文字节数 | 英文字节数 |
|---|---|---|---|
| ASCII | 英文 | 不支持 | 1 |
| UTF-8 | 多语言 | 3 | 1 |
| Unicode | 多语言 | 2 | 2 |
| GBK | 中 / 英文 | 2 | 1 |
7.2.6:文件定位读写:控制读写位置
read()方法读取了文件luckycloud.txt,结合代码与程序运行结果进行分析,可以发现read()方法第1次读取了2个字符,第2次从第3个字符开始读取了剩余字符。
-
在文件的一次打开与关闭之间进行的读写操作是连续的,程序总是从上次读写的位置继续向下进行读写操作。
-
每个文件对象都有一个称为“文件读写位置”的属性,该属性会记录当前读写的位置。
-
文件读写位置默认为0,即在文件首部。
Python提供了一些获取与修改文件读写位置的方法,以实现文件的定位读写:
-
tell():获取文件当前的读写位置。
-
seek():控制文件的读写位置。
1) tell ():获取当前读写位置
tell()方法用于获取文件当前的读写位置,以操作文件lucky.txt为例,tell()的用法如下:
with open('f:\\lucky.txt') as f:print(f.tell()) #获取文件读写位置print(f.read(5)) #利用位置read()方法移动文件读写位置print(f.tell()) #再次获取文件读写位置
运行结果:
0
actio
5
2 ) seek (offset, from):移动读写位置
Python提供了seek()方法,使用该方法可控制文件的读写位置,实现文件的随机读写。
- offset:表示偏移量,即读写位置需要移动的字节数。
- from:用于指定文件的读写位置,该参数的取值为0、1、2。
- 0:表示文件开头
- 1:表示使用当前读写位置
- 2:表示文件末尾
- seek()方法调用成功后会返回当前读写位置。
- 注意:文本文件仅支持 from=0,二进制文件支持所有参考位置
# 二进制文件示例
with open('f:\\lucky.txt') as f:f.tell() # 获取文件读写位置sep = f.seek(5,0) # 相对文件首部移动5字节print(sep) # 输出当前文件读写位置
运行结果:
5
7.3:文件与目录管理:os 模块的核心用法
对于用户而言,文件和目录以不同的形式展现,但对计算机而言,目录是文件属性信息集合,它本质上也是一种文件。
os 模块提供文件和目录的管理功能,包括删除文件、创建目录、获取文件列表等。
7.3.1 文件管理
- **删除文件:**remove (文件路径),文件不存在则报错
import osos.remove('f:\\lucky.txt') # 删除指定文件
- **重命名文件:**rename (原路径,新路径)
import osos.rename('f:\\luckycloud.txt', 'f:\\cloud.txt') # 文件名修改
7.3.2 目录管理
- **获取当前目录:**getcwd (),返回绝对路径
import osprint(os.getcwd()) # 示例输出:F:\PycharmProjects\pythonProject1\pycode\文件操作
- **创建目录:**mkdir (目录路径),目录已存在则报错
import osos.mkdir('f:\\abc') # 在F盘创建abc目录
- **删除目录:**rmdir (目录路径),目录非空则报错
import osos.rmdir('f:\\abc') # 删除abc目录
- **更改默认目录:**chdir (目录路径),修改 Python 工作目录
import osprint('更改前默认路径:',os.getcwd())
os.chdir('f:\\') # 将默认目录改为F盘
print('更改后默认路径:',os.getcwd())# 运行结果
更改前默认路径: F:\PycharmProjects\pythonProject1\pycode\文件操作
更改后默认路径: f:\
- **获取目录文件列表:**listdir (目录路径),返回文件名列表
import osfiles = os.listdir('f:\\') # 获取F盘所有文件/目录名称
print(files) # 输出格式:['文件1.txt', '目录1', ...]
7.4:数据维度与数据格式化
数据维度和格式化是 Python 数据处理的核心基础,清晰的维度划分能让数据组织更有序,规范的格式化则是数据存储、交换的关键。
7.4.1 基于维度的数据分类
维度本质是与数据相关联的参数数量,不同维度对应不同的数据组织形式,Python 中也有专属的数据结构支持。
1 )一维数据:线性对等的基础数据
- 定义:具有对等关系的线性数据集合,仅需一个参数即可描述。
- 对应 Python 数据结构:列表(list)、元组(tuple)、集合(set)。
- 示例:
["成都", "杭州", "重庆"]、(10, 20, 30)、{"a", "b", "c"}。
2)二维数据:表格化的结构化数据
- 定义:关联两个参数的数据集合,可理解为 “一维数据的集合”,呈现表格形态(行 × 列)。
- 对应 Python 数据结构:矩阵、二维数组、二维列表、二维元组。
- 示例:二维数据如下
| 姓名 | 语文 | 数学 | 英语 | 理综 |
|---|---|---|---|---|
| 刘婧 | 124 | 137 | 145 | 260 |
| 张昭华 | 116 | 143 | 139 | 263 |
| 邢昭林 | 120 | 130 | 148 | 255 |
| 鞠依依 | 115 | 145 | 131 | 240 |
| 黄丽萍 | 123 | 108 | 121 | 235 |
| 赵越 | 132 | 100 | 112 | 210 |
3) 多维数据:复杂关联的嵌套数据
- 定义:关联三个及以上参数,需通过层级关系展示复杂结构。
- 核心特征:用**键值对(key-value)**表达层级关联。
- 对应 Python 数据结构:字典(dict),网络传输中常用 JSON 格式。
- 示例:
"高三一班考试成绩": [{"姓名": "刘婧","语文": "124","数学": "137","英语": "145","理综": "260"},{"姓名": "张华","语文": "116","数学": "143","英语": "139","理综": "263"}..........]
7.4.2 一二维数据的存储与读写实战
数据通常存储在文件中,规范的存储格式是高效读写的前提,其中 CSV 是一二维数据的通用标准。
7.4.2.1 存储格式:约定分隔规则
(1)一维数据存储
- 核心原则:用统一的特殊字符分隔数据,避免歧义。
- 常用分隔符:空格、逗号(,)、&(需满足 “分隔符不出现在数据中”)。
- 格式要求:使用英文半角符号,同一文件 / 文件组统一分隔符。
- 示例:
- 逗号分隔:
成都,杭州,重庆,武汉 - 空格分隔:
10 20 30 40
- 逗号分隔:
(2)二维数据存储:CSV 格式详解
- 二维数据可视为多条一维数据的集合,当二维数据只有一个元素时,这个二维数据就是一维数据。
- CSV(Comma-Separated Values,逗号分隔值):国际通用的表格数据存储格式,纯文本形式。
- 格式规范:
- 每一行对应一条数据记录;
- 每条记录的字段用英文半角逗号分隔;
- 每条记录由一个或多个字段组成;
- Windows平台中CSV文件的后缀名为.csv,可通过Office Excel或记事本打开。
- 示例(学生成绩 CSV):
姓名,语文,数学,英语,理综
刘备,124,137,145,260
张飞,116,143,139,263
关羽,120,130,148,255
周瑜,115,145,131,240
诸葛亮,123,108,121,235
黄月英,132,100,112,210
注意:
CSV广泛应用于不同体系结构下网络应用程序之间表格信息的交换中,它本身并无明确格式标准,具体标准一般由传输双方协议决定。
7.4.2.2 数据读取:Python 解析 CSV 文件
Python 读取 CSV 文件后,默认以二维列表形式存储,需处理编码问题避免乱码。
实战代码:读取 CSV 并转换为二维列表
import chardet# 自动检测文件编码(解决中文乱码问题)
with open('f:\\score.csv', 'rb') as f:raw_data = f.read()encoding = chardet.detect(raw_data)['encoding']# 读取CSV文件
with open('f:\\score.csv', encoding=encoding, errors='ignore') as f:lines = []for line in f:line = line.replace('\n', '') # 去除换行符lines.append(line.split(',')) # 按逗号分隔字段print("读取结果(二维列表):")
print(lines)
运行结果:
[['姓名', '语文', '数学', '英语', '理综'],['刘备', '124', '137', '145', '260'],['张飞', '116', '143', '139', '263'],['关羽', '120', '130', '148', '255']
]
7.4.2.3 数据写入:向 CSV 添加新字段
将一、二维数据写入文件中,即按照数据的组织形式,在文件中添加新的数据。
在已有 CSV 文件中新增数据(如学生总分),需按 CSV 格式规则拼接字段。
实战代码:计算学生总分并写入新文件
import chardet# 自动检测编码
def get_file_encoding(file_path):with open(file_path, 'rb') as f:raw_data = f.read()return chardet.detect(raw_data)['encoding']# 读取原始成绩文件
input_path = 'f:\\score.csv'
output_path = 'f:\\count.csv'
encoding = get_file_encoding(input_path)with open(input_path, encoding=encoding, errors='ignore') as f:# 打开新文件用于写入(w+模式:读写+创建)with open(output_path, 'w+', encoding='utf-8') as new_file:lines = []# 读取并处理每一行for line in f:line = line.replace('\n', '')lines.append(line.split(','))# 1. 添加表头字段“total”lines[0].append('total')# 2. 计算每个学生的总分(跳过表头)for i in range(1, len(lines)):total = 0# 遍历当前学生的所有科目成绩for item in lines[i]:if item.isnumeric(): # 判断是否为数字(排除姓名字段)total += int(item)lines[i].append(str(total)) # 总分转为字符串添加到列表# 3. 写入新文件(按CSV格式拼接)for line in lines:new_file.write(','.join(line) + '\n')print(line) # 打印验证结果
运行结果(使用Excel打开文件):
| 姓名 | 语文 | 数学 | 英语 | 理综 | 总计 |
|---|---|---|---|---|---|
| 刘备 | 124 | 137 | 145 | 260 | 666 |
| 张飞 | 116 | 143 | 139 | 263 | 661 |
| 关羽 | 120 | 130 | 148 | 255 | 653 |
| 周瑜 | 115 | 145 | 131 | 240 | 631 |
| 诸葛亮 | 123 | 108 | 121 | 235 | 587 |
| 黄月英 | 132 | 100 | 112 | 210 | 554 |
关键说明:
isnumeric():判断字符串是否仅包含数字,用于区分 “姓名” 和 “成绩” 字段。','.join(line):将列表元素用逗号拼接为 CSV 格式的字符串。- 输出文件编码设为
utf-8,避免中文乱码。
7.4.3 多维数据格式化:JSON 核心应用
三维及以上的多维数据,需用键值对形式格式化,JSON 是网络传输中最常用的格式(比 XML、HTML 更简洁、省流量)。
7.4.3.1 JSON 格式语法规则
- 数据存储在键值对中:
"key": "value"(key 必须用双引号)。 - 字段用逗号分隔:
"姓名": "张飞", "语文": 116。 - 花括号
{}表示一个 JSON 对象:{"姓名": "张飞", "成绩": 561}。 - 方括号
[]表示一个数组(多个对象集合):[{"姓名": "张飞"}, {"姓名": "关羽"}]。
7.4.3.2 JSON 与 Python 对象的转换
Python 的json模块提供dumps()和loads()两个核心函数,实现 Python 对象与 JSON 字符串的双向转换。
| 函数 | 功能描述 |
|---|---|
| dumps() | 对 Python 对象进行转码,将其转化为 JSON 字符串 |
| loads() | 将 JSON 字符串解析为 Python 对象 |
(1)类型对应关系
| Python 对象 | JSON 数据类型 |
|---|---|
| dict | object |
| list/tuple | array |
| str | string |
| int/float | number |
| True | true |
| False | false |
| None | null |
(2)实战:Python 对象转 JSON(dumps ())
import json# 定义Python复杂对象(包含列表、字典、布尔值等)
py_obj = [[1, 2, 3],10, 3.14,'tom',{'java': 98, 'python': 100},True, False, None
]# 转换为JSON字符串
json_str = json.dumps(py_obj)
print(json_str)
运行结果:
[[1, 2, 3], 10, 3.14, "tom", {"java": 98, "python": 100}, true, false, null]
(3)实战:JSON 转 Python 对象(loads ())
import json# 承接上面的JSON字符串
json_str = '[[1, 2, 3], 10, 3.14, "tom", {"java": 98, "python": 100}, true, false, null]'
print(json_str)# 解析JSON字符串为Python对象
py_new = json.loads(json_str)
print(py_new)
# print("类型验证:", type(py_new)) # 输出:<class 'list'>
# print("字典元素访问:", py_new[5]['python']) # 输出:100
运行结果:
[[1, 2, 3], 10, 3, 14, "tom", {"java": 98, "python": 100}, true, false, null]
[[1, 2, 3], 10, 3, 14, 'tom', {'java': 98, 'python': 100}, True, False, None]
7.4.3.3 多维数据格式化示例
(1)JSON 格式(学生成绩多维数据)
{"班级考试成绩": [{"姓名": "王小天","语文": 124,"数学": 127,"英语": 145,"理综": 259},{"姓名": "张大同","语文": 116,"数学": 143,"英语": 119,"理综": 273}]
}
(2)对比 XML 格式(同一数据)
<班级考试成绩><学生><姓名>王小天</姓名><语文>124</语文><数学>127</数学><英语>145</英语><理综>259</理综></学生><学生><姓名>张大同</姓名><语文>116</语文><数学>143</数学><英语>119</英语><理综>273</理综></学生>
</班级考试成绩>
优势总结:
- JSON 更简洁,无需冗余标签;
- 键(如 “姓名”“语文”)仅定义一次,网络传输时流量消耗更少;
- 与 Python 字典天然兼容,解析效率更高。
第8章:面向对象
面向对象(OOP)是程序开发的核心思想之一,它模拟人类认识世界的思维方式,将复杂问题拆解为多个 “对象”,通过对象的交互解决问题。相比传统的面向过程编程,OOP 更适合大型项目开发,具备封装、继承、多态三大特性。
8.1:面向对象 vs 面向过程:核心区别
8.1.1 两种编程范式的本质
| 编程方式 | 核心思想 | 关注点 | 优点 | 缺点 |
|---|---|---|---|---|
| 面向过程 | 按步骤解决问题 | 步骤、函数 | 简单直接、执行效率高 | 代码复用性差、维护困难 |
| 面向对象 | 抽象为对象交互 | 对象、属性、方法 | 易扩展、易维护、复用性强 | 入门门槛高、执行效率略低 |
8.1.2 实例对比:制作一杯咖啡
🌰 面向过程方式(按步骤执行)
-
准备材料:水、咖啡粉
-
水壶加热水至沸腾→冷却至 90°C
-
滤杯放滤纸,预热杯子
-
咖啡粉放入滤纸
-
缓慢倒水冲泡
-
过滤完成,得到咖啡
🌰 面向对象方式(对象交互)
-
核心对象:咖啡机、水、咖啡粉、杯子
-
对象的属性与方法:
-
咖啡机:添加原料、煮制咖啡
-
水:设置加热温度
-
咖啡粉:设置研磨程度
-
杯子:接收咖啡液
-
灵活扩展:想喝茶时,只需将 “咖啡粉” 对象替换为 “茶叶”,无需修改其他流程
8.2:类与对象:OOP 的核心概念
8.2.1 类与对象的关系
-
类:具有相同属性和行为的事物的抽象集合(如 “汽车类”)
-
对象:类的具体实例(如 “我的特斯拉 Model 3”)
-
通俗理解:类是 “模板”,对象是 “根据模板创建的产品”

8.2.2 类的定义(语法 + 规范)
类的三要素
-
类名:采用大驼峰命名法(首字母大写,如Person、CoffeeMachine)
-
属性:描述事物的静态特征(如姓名、颜色、车轮数)
-
方法:描述事物的动态行为(如行驶、冲泡、加热)
基础语法
class 类名:# 1. 类属性(声明在方法外部)类属性名 = 属性值# 2. 方法(声明在类内部,类似函数)def 方法名(self, 参数...):方法体# 实例属性(声明在方法内部,用self修饰)self.实例属性名 = 属性值
示例:定义 “汽车类”
class Car:# 类属性:所有汽车共享的特征wheels = 4 # 车轮数(默认4个)# 实例方法:汽车的行为def drive(self):# 实例属性:每个对象独有的特征(可动态添加)self.speed = 0print("汽车开始行驶")def accelerate(self, increment):self.speed += incrementprint(f"加速至 {self.speed} km/h")
8.2.3 对象的创建与使用
1) 创建对象(实例化)
# 语法:对象名 = 类名()
my_car = Car() # 创建Car类的实例my_car
2.)访问对象成员(属性 + 方法)
# 访问类属性(类和对象均可访问)
print(Car.wheels) # 输出:4(通过类访问)
print(my_car.wheels) # 输出:4(通过对象访问)# 调用实例方法(只能通过对象调用)
my_car.drive() # 输出:汽车开始行驶
my_car.accelerate(60)# 输出:加速至 60 km/h# 访问实例属性(只能通过对象访问)
print(my_car.speed) # 输出:60
8.3:类的成员:属性与方法详解
8.3.1 属性:类属性 vs 实例属性
| 类型 | 声明位置 | 访问方式 | 修改方式 | 作用域 |
|---|---|---|---|---|
| 类属性 | 类内部、方法外部 | 类。属性 / 对象。属性 | 只能通过类修改 | 所有对象共享 |
| 实例属性 | 方法内部(self 修饰) | 只能通过对象访问 | 对象。属性 = 新值 | 仅当前对象 |
代码示例:属性的使用与修改
class Car:wheels = 4 # 类属性(所有汽车共享)def __init__(self):self.color = "红色" # 实例属性(每个对象独有)# 1. 访问属性
my_car1 = Car()
print(Car.wheels) # 类访问类属性 → 4
print(my_car1.wheels) # 对象访问类属性 → 4
print(my_car1.color) # 对象访问实例属性 → 红色# 2. 修改属性
Car.wheels = 3 # 类修改类属性(影响所有对象)
my_car1.wheels = 5 # 对象"影子修改"类属性(仅影响当前对象)
my_car1.color = "蓝色" # 对象修改实例属性# 验证结果
my_car2 = Car()
print(Car.wheels) # → 3(类属性已被修改)
print(my_car1.wheels) # → 5(当前对象的修改)
print(my_car2.wheels) # → 3(新对象继承修改后的类属性)
print(my_car1.color) # → 蓝色(实例属性修改)
print(my_car2.color) # → 红色(新对象的默认实例属性)
8.3.2 方法:实例方法 vs 类方法 vs 静态方法
| 类型 | 装饰器 | 第一个参数 | 调用方式 | 作用 |
|---|---|---|---|---|
| 实例方法 | 无 | self(代表对象) | 只能对象调用 | 操作实例属性、实现对象行为 |
| 类方法 | @classmethod | cls(代表类) | 类 / 对象均可调用 | 操作类属性、创建实例 |
| 静态方法 | @staticmethod | 无默认参数 | 类 / 对象均可调用 | 独立功能(不依赖类 / 对象属性) |
代码示例:三种方法的使用
class Car:wheels = 4 # 类属性# 1. 实例方法def drive(self, speed):print(f"汽车以{speed}km/h行驶")# 2. 类方法(用cls访问类属性)@classmethoddef change_wheels(cls, num):cls.wheels = numprint(f"车轮数修改为:{cls.wheels}")# 3. 静态方法(独立功能)@staticmethoddef check_safety():print("正在进行安全检查...")# 调用方法
my_car = Car()
my_car.drive(80) # 实例方法 → 汽车以80km/h行驶
Car.change_wheels(6) # 类方法 → 车轮数修改为:6
my_car.check_safety() # 静态方法 → 正在进行安全检查...
8.3.3 私有成员:数据安全的保障
1) 为什么需要私有成员?
类的公有成员可以被外部随意访问和修改,可能导致数据混乱。私有成员限制外部访问,仅允许类内部使用,提升代码安全性。
定义方式:属性 / 方法名前加双下划线(__)
class Car:__engine = "V8" # 私有类属性wheels = 4 # 公有类属性def __start(self): # 私有方法print(f"启动{self.__engine}发动机")# 公有方法(内部访问私有成员)def run(self):self.__start()print("汽车正常行驶")# 测试
my_car = Car()
my_car.run() # 输出:启动V8发动机 → 汽车正常行驶# 外部访问私有成员(报错)
print(my_car.__engine) # AttributeError
my_car.__start() # AttributeError
注意:
核心要点:类是抽象模板,对象是具体实例;属性描述特征,方法描述行为。
易错点:
-
实例属性必须通过self声明,且只能通过对象访问。
-
类方法用cls访问类属性,静态方法不能直接访问类 / 实例属性。
-
私有成员不能外部直接访问,需通过公有方法间接操作。
8.4:特殊方法:构造与析构
8.4.1 构造方法 init()
-
作用:创建对象时自动调用,用于初始化对象属性
-
分类:无参构造、有参构造
示例 1:无参构造(固定初始值)
class Car:def __init__(self):self.color = "红色" # 所有对象默认红色self.speed = 0my_car = Car()
print(my_car.color) # → 红色
示例 2:有参构造(动态初始化)
class Car:def __init__(self, color, speed=0):self.color = color # 传入颜色self.speed = speed # 可选参数(默认0)# 创建不同属性的对象
car1 = Car("蓝色")
car2 = Car("黑色", 50)
print(car1.color, car1.speed) # → 蓝色 0
print(car2.color, car2.speed) # → 黑色 50
8.4.2 析构方法 del()
-
作用:对象被销毁时自动调用,用于释放资源(如关闭文件、断开数据库连接)
-
对象销毁时机:Python 通过 “引用计数器” 管理内存,当对象引用数为 0 时,系统自动销毁对象
示例:析构方法的使用
class Car:def __init__(self, name):self.name = nameprint(f"{self.name}已创建")def __del__(self):print(f"{self.name}已销毁(释放内存)")# 测试
car = Car("特斯拉") # → 特斯拉已创建
del car # 手动销毁对象 → 特斯拉已销毁(释放内存)
**扩展:**与文件类似,每个对象都会占用系统的一块内存,使用之后若不及时销毁,会浪费系统资源。那
么对象什么时候销毁呢?
**答:**Python通过引用计数器记录所有对象的引用(可以理解为对象所占内存的别名)数量,一旦某个对象的
引用计数器的值为0,系统就会销毁这个对象,收回对象所占用的内存空间。
8.5:封装:隐藏细节,安全访问
封装是面向对象的基础特性,核心思想是隐藏类的内部实现细节,仅提供公开接口供外部访问。这样既保护了类内数据的安全性,也降低了外部使用类的复杂度(无需关注内部逻辑)。
实现要求
- 类的属性声明为私有属性(Python 中用双下划线
__开头标识)。 - 提供两类公有方法(
get_xxx()和set_xxx()),分别用于获取和修改私有属性的值。
代码示例
class Person:def __init__(self, name):self.name = name # 公有属性(姓名)self.__age = 1 # 私有属性(年龄,默认1岁)# 公有方法:设置年龄(含合法性校验)def set_age(self, new_age):if 0 < new_age <= 120: # 限制年龄范围,保证数据安全self.__age = new_age# 公有方法:获取年龄def get_age(self):return self.__age# 外部使用:仅通过公开接口操作,无需关注内部实现
person = Person("杰瑞")
person.set_age(22)
print(f"姓名是{person.name},年龄为{person.get_age()}岁") # 输出:姓名是杰瑞,年龄为22岁
核心优势:
- 数据安全:避免外部直接修改属性导致的非法值(如年龄设为 200)。
- 低耦合:外部与类的内部实现解耦,后续修改内部逻辑不影响外部使用。
8.6:继承:复用代码,扩展功能
继承用于描述类与类的 “从属关系”,核心是在不修改原有类的基础上,复用其代码并扩展新功能。被继承的类称为 “父类(基类)”,继承的类称为 “子类(派生类)”,子类会自动拥有父类的公有成员(属性和方法)。
8.6.1. 单继承:子类仅继承一个父类
单继承是最常见的继承形式,子类只关联一个父类。。现实生活中,波斯猫、折耳猫、短毛猫都属于猫类,它们之间存在的继承关系即为单继承,如图所示。

语法格式
class 子类名(父类名):# 子类自身的属性和方法(可选)pass
代码示例
# 父类:猫类
class Cat(object):def __init__(self, color):self.color = color # 公有属性:颜色self.__age = 1 # 私有属性:年龄(子类无法直接访问)# 公有方法:猫的行为def walk(self):print("走猫步")# 私有方法:子类无法直接调用def __test(self):print("父类私有方法")# 子类:折耳猫(继承自 Cat)
class ScottishFold(Cat):pass# 子类使用:自动继承父类的公有成员
fold = ScottishFold("灰色")
print(f"{fold.color}的折耳猫") # 输出:灰色的折耳猫(访问父类公有属性)
fold.walk() # 输出:走猫步(调用父类公有方法)# 注意:子类不会拥有父类的私有成员,也不能访问父类的私有成员。
# 错误示范:子类无法访问父类私有成员
# print(fold.__age) # 报错:AttributeError(无__age属性)
# fold.__test() # 报错:AttributeError(无__test方法)
8.6.2. 多继承:子类继承多个父类
子类可以同时继承多个父类,自动拥有所有父类的公有成员。
语法格式
class 子类名(父类名1, 父类名2, ...):pass
代码示例(房车:继承房屋和汽车的功能)
# 父类1:房屋类
class House(object):def live(self):print("供人居住")# 父类2:汽车类
class Car(object):def drive(self):print("行驶")# 子类:房车(同时继承 House 和 Car)
class TouringCar(House, Car):pass# 子类使用:调用多个父类的方法
tour_car = TouringCar()
tour_car.live() # 输出:供人居住(调用 House 类方法)
tour_car.drive() # 输出:行驶(调用 Car 类方法)
关键注意点(同名方法优先级)
如果多个父类有同名方法,子类会按照继承顺序优先调用先声明的父类方法(即如果子类继承的多个父类是平行关系的类,那么子类先继承哪个类,便会先调用哪个类的方法。)。
8.6.3. 重写:子类自定义父类方法
子类会原封不动继承父类方法,但如果需要适配自身需求,可以在子类中定义与父类同名的方法,覆盖父类方法(即 “重写”)。
代码示例(重写 + 调用父类方法)
# 父类:人类
class Person(object):def say_hello(self):print("打招呼!")# 子类:中国人(重写父类方法)
class Chinese(Person):def say_hello(self):# 子类重写了父类的方法之后,无法直接访问父类的同名方法,但可以使用super()函数间接调用父类中 被重写的方法。super().say_hello() print("阿吃过啦") # 子类自定义逻辑# 调用结果:优先执行子类重写后的方法
chinese = Chinese()
chinese.say_hello()
# 输出:
# 打招呼!
# 阿吃过啦
8.7:多态:同一接口,不同行为
多态是面向对象的灵活特性,核心是让不同类的同一功能,通过同一个接口调用时表现出不同行为。其实现依赖 “继承 + 方法重写”。
核心逻辑
- 定义统一接口(函数或方法),接收 “父类类型” 的参数。
- 不同子类重写该接口对应的方法,实现自定义逻辑。
- 调用接口时,传入不同子类的实例,自动执行对应子类的方法。
代码示例(动物叫:不同动物有不同叫声)
# 父类:猫类
class Cat:def shout(self):print("喵喵喵~")# 父类:狗类(无显式继承,但遵循同一接口规范)
class Dog:def shout(self):print("汪汪汪!")# 统一接口:接收任意实现了 shout() 方法的对象
def animal_shout(obj):obj.shout()# 多态体现:同一接口调用不同对象,行为不同
cat = Cat()
dog = Dog()
animal_shout(cat) # 输出:喵喵喵~
animal_shout(dog) # 输出:汪汪汪!
核心优势
- 灵活性高:新增子类(如 Bird 类)时,无需修改接口代码,直接复用。
- 代码简洁:统一接口减少冗余,降低调用者的记忆成本。
8.8:运算符重载:赋予运算符新功能
运算符重载是 Python 的进阶特性,核心是重写基类 object 的特殊方法,让 +、-、* 等内置运算符能适配自定义类的实例。
常用运算符对应的特殊方法
| 运算符 | 特殊方法 | 功能描述 |
|---|---|---|
+ | __add__(self, other) | 加法运算 |
- | __sub__(self, other) | 减法运算 |
* | __mul__(self, other) | 乘法运算 |
/ | __truediv__(self, other) | 除法运算(真除法) |
代码示例(自定义计算器类)
class Calculator(object):def __init__(self, number):self.number = number # 初始数值# 重载 + 运算符def __add__(self, other):self.number += otherreturn self.number# 重载 - 运算符def __sub__(self, other):self.number -= otherreturn self.number# 重载 * 运算符def __mul__(self, other):self.number *= otherreturn self.number# 重载 / 运算符(注意:除数不能为0)def __truediv__(self, other):if other != 0:self.number /= otherreturn self.numberelse:raise ValueError("除数不能为0")# 使用示例
calc = Calculator(10)
print(calc + 5) # 输出:15(调用 __add__)
print(calc - 3) # 输出:12(调用 __sub__)
print(calc * 2) # 输出:24(调用 __mul__)
print(calc / 4) # 输出:6.0(调用 __truediv__)
小结:
面向对象的三大特性各有侧重:
- 封装:保护数据,隐藏细节(“安全”);
- 继承:复用代码,快速扩展(“高效”);
- 多态:统一接口,灵活适配(“灵活”);
- 运算符重载:扩展运算符功能,让自定义类更易用。
掌握这些特性后,能写出更易维护、可扩展的 Python 代码,尤其适合大型项目开发。
第9章:异常
程序开发和运行过程中,异常是无法避免的。它可能源于代码设计缺陷,也可能由外界环境变化(如文件缺失、输入错误)引发。若不加以处理,程序会直接终止并返回错误信息。
9.1:异常概述
异常是程序运行时的错误事件,会中断正常执行流程。当程序未设置异常处理机制时,Python 解释器会采用默认方式响应:输出异常信息(含行号、类型、描述)并终止程序。
9.1.1 核心异常类型
Python 中所有异常都继承自BaseException类,常用业务异常多继承自其子类Exception。以下是开发中高频遇到的异常类型:
NameError:使用未定义的变量或函数时触发。IndexError:列表、元组等序列类型的索引越界访问时触发。AttributeError:访问对象不存在的属性或方法时触发。FileNotFoundError:尝试打开不存在的文件或目录时触发。ZeroDivisionError:除数为 0 时触发(如5/0)。ValueError:传入的值类型正确但内容无效(如int('abc'))。

9.1.2 异常信息示例
异常触发时,解释器输出的 Traceback 信息格式如下(以除零错误为例):
Traceback (most recent call last):File "E:\pyproject\异常\异常概念.py", line 1, in <module>print(5/0)
ZeroDivisionError: division by zero
信息包含三部分:错误发生的文件路径与行号、触发错误的代码、异常类型及描述。
9.2:异常捕获:try-except 相关语句
Python 提供try-except系列语句捕获并处理异常,避免程序意外终止。可根据需求选择基础捕获、组合子句等不同用法。
9.2.1 基础用法:try-except
核心作用是监控try块中的代码,当异常发生时执行except块的处理逻辑。
语法规则:
try:可能出错的代码
except [异常类型 [as error]]: # 将捕获到的异常对象赋error捕获异常后的处理代码
try-except语句可以捕获与处理程序的单个、多个或全部异常。
(1)捕获单个异常
针对特定异常类型单独处理,精准定位问题。
num1 = int(input('请输入被除数:'))
num2 = int(input('请输入除数:'))
try:print('结果为', num1 / num2)
except ZeroDivisionError: # 单个异常类型print('出错了:除数不能为0')
(2)捕获多个异常
用元组指定多个异常类型,统一处理同类错误。
# 如果输入的是字母进行异常捕捉
try:num1 = int(input('请输入被除数:'))num2 = int(input('请输入除数:'))print('结果为', num1 / num2)
except (ZeroDivisionError, ValueError) as error: # 定义error变量输出异常内容print('出错了:', error) # 输出具体异常描述
(3)捕获所有异常
通过Exception(所有业务异常的父类)捕获未明确指定的异常,适合兜底处理。
try:num1 = int(input('请输入被除数:'))num2 = int(input('请输入除数:'))print('结果为', num1 / num2)
except Exception as error: # 父类异常print('出错了:', error)
9.2.2 组合用法:try-except-else
else子句在try块无异常时执行,用于处理正常流程的后续逻辑。
语法规则:
try:可能出错的代码
except [异常类型 [as error]]: # 将捕获到的异常对象赋值error捕获异常后的处理代码
else:未捕获异常后的处理代码
**示例:**如果除法过程没有异常则输出结果
try:num1 = int(input('请输入被除数:'))num2 = int(input('请输入除数:'))result = num1 / num2
except Exception as error:print('出错了:', error)
else:print('计算成功,结果是:', result) # 无异常时执行
9.2.3 强制执行:try-except-finally
finally子句无论try块是否发生异常,都会执行。常用于资源清理(如关闭文件、网络连接)。
语法规则:
try:可能出错的代码
except [异常类型 [as error]]: # 将捕获到的异常对象赋值error捕获异常后的处理代码
finally:一定执行的代码
示例:
try:file = open('f:\\luckycloud.txt', mode='r', encoding='utf-8')print(file.read())
except FileNotFoundError as error:print('文件操作出错:', error)
finally:file.close() # 无论是否报错,均关闭文件print('文件已关闭')
9.3:主动抛出异常:raise 与 assert
除了程序自动触发的异常,开发人员也可通过语句主动抛出异常,用于校验输入、标记业务错误等场景。
9.3.1 raise 语句
显式抛出指定异常,支持三种使用格式,可自定义异常描述。
raise语句的语法格式如下:
raise 异常类 # 格式1:使用异常类名引发指定的异常
raise 异常类对象 # 格式2:使用异常类的对象引发指定的异常
raise # 格式3: 使用刚出现过的异常重新引发异常
# 格式1:直接抛出异常类
raise IndexError# 格式2:通过异常对象抛出(可加描述)
index_error = IndexError('索引下标超出范围')
raise index_error# 格式3:重新抛出刚捕获的异常
try:5 / 0
except:print('捕获到异常,重新抛出')raise# 格式4:异常链(由一个异常引发另一个异常)
try:5 / 0
except Exception as e:raise IndexError('下标异常') from e # 保留原始异常上下文
9.3.2 assert 语句(断言语句)
用于判断表达式是否为真,若为假则抛出AssertionError异常。适合调试阶段的逻辑校验(如参数合法性)。
语法格式:
assert 表达式[, 异常信息]
示例:
num1 = int(input('请输入被除数:'))
num2 = int(input('请输入除数:'))
assert num2 != 0, '除数不能为0' # 表达式为False时触发异常
result = num1 / num2
print(f'{num1} / {num2} = {result}')
执行结果(若输入除数为 0):
Traceback (most recent call last):File "E:\pyproject\异常\assert.py", line 3, in <module>assert num2 != 0,'除数不能为0' #assert语句判定num2不等于0^^^^^^^^^
AssertionError: 除数不能为0
9.3.3 异常的传递机制
异常会沿函数调用栈自内向外传递,直到被捕获或导致程序终止。
示例:
def func_b():raise ValueError('输入值无效') # 内层函数抛出异常def func_a():func_b() # 调用内层函数,未捕获异常try:func_a() # 外层捕获传递过来的异常
except ValueError as e:print('捕获到异常:', e)
输出:捕获到异常:输入值无效
9.4:自定义异常
当 Python 内置异常无法满足业务需求时,可自定义异常类。需继承Exception类(或其子类),类名建议以Error结尾,增强可读性。
自定义异常示例:
# 定义自定义异常类
class ShortPwd(Exception):'''自定义异常''' def __init__(self, length, atleast):self.length = lengthself.atleast = atleasttry:text = input('请输入密码:')if len(text) < 3:raise ShortPwd(len(text), 3)except ShortPwd as result:print('ShortPwd:输入的长度是%d,长度至少应该是%d' % (result.length, result.atleast))
运行结果:
请输入密码:12
ShortPwd:输入的长度是2,长度至少应该是3
请输入密码:1234
设置密码成功
9.4.1 自定义异常要点
- 必须继承
Exception(而非BaseException,避免捕获系统级异常)。 - 可添加自定义属性和方法,扩展异常信息。
- 配合
raise语句使用,在业务逻辑不满足时主动触发。
