Post

03_python模块文件&面向对象

基于上述提供的代码,将 �大致可以可以看出此部分内容主要包括了模块化编程的定义的关键词与具体案例的下了是: 11# �模块定义了模块和包,级该`定义了`个便于分复序,并) `contact.pyikel文件`条件.py `_和部分调了通用从`0联系人列表.e.e也支持拆配合了.e不同基本接口定义了增.e除查询功能和余/. *定义了基.e `contact.py文件.e.e实现了基于.e关键词对应方法(e.e·例如各个功能.e便于.复.e和。 `主入口.e中定义了一个.e主功能e通过.e子联系信息进行概实现了基本的操作e这里.e.d进.e联系e添加删查响应查询联系资源查更了联系信息e供与.e联系丁.e文件.e进行例如的示具体实现了数据阅读.e文件保存与加载基本逻辑。 ` **验证了模块的的单一职责原则(e各.e实现了.e基本功能e便于.代.e进一步复)e** 通过.简便而了逻辑结构e相互复.e实现.** **演示了交互入口(e..文件.e.e中通过.e全局接口实现了用户基.e功能与具体应用e即实现了具体化实现了某.e具体具体一致性;客户e.。 实现了模块化编程与交互入口解键分别表达了.e完全为独立e便于各复e,遵循单一功能原则(e.< .整个逻辑结构清晰e便于逻辑实现与维护;。** 代码结构结构为结构化包程即简明清楚。提高了易于维护与扩展。。同时充分展示了e模块化编程的优势数组功能实现与界面的交互实现.

Python 阅读 1 点赞 0 评论 0

Python模块文件&面向对象

[TOC]

1. 模块 (Module) 与包 (Package)

1.1 为什么需要模块化

当代码量持续增长,若所有逻辑都堆砌在单个 .py 文件中,会暴露一系列核心问题:

  • 代码维护难:函数、变量、业务逻辑混杂,定位特定功能如同 “大海捞针”

  • 复用性差:其他项目想使用你的工具函数,只能复制粘贴,易出错且无法统一维护

  • 命名冲突:不同功能的同名变量 / 函数极易 “撞车”,引发难以排查的逻辑错误

  • 协作效率低:多人修改同一文件时,代码冲突频繁,合并成本高

模块化的核心逻辑:按 “职责划分” 将代码拆分到不同文件,再通过 import(导入)语句按需组合使用 —— 这和 Java 中按功能拆分不同类文件、通过import导入的设计思路完全一致。

1.2 模块是什么

在 Python 中,一个 .py 文件就是一个独立模块(类比 Java 中一个.java文件对应一个类)。

例如:utils.pymath_tools.py 都是合法模块名(导入时无需写 .py 后缀)。

模块的核心价值:

  • 导入(Import):使用其他模块的变量、函数、类

  • 导出(Export):控制本模块对外暴露的成员(变量、函数、类)

1.3 模块的导入与导出

1.3.1 基础导入:导入整个模块

import math  # 导入Python标准库的数学模块(类比Java import java.lang.Math;)

# 使用模块内的属性/方法,需加“模块名.”前缀(模块导出的所有成员都可通过此方式访问)
print(math.pi)    # 输出圆周率:3.141592653589793(pi是math模块导出的常量)
print(math.sqrt(16))  # 计算平方根:4.0(sqrt是math模块导出的函数)

1.3.2 精准导入:导入模块的指定成员

from math import pi, sqrt  # 只导入需要的成员,减少命名空间占用
# 等价于:从math模块中“导入”pi和sqrt两个导出成员

# 直接使用导入的成员,无需前缀
print(pi)       # 3.141592653589793
print(sqrt(16)) # 4.0

1.3.3 别名导入:为模块 / 成员起别名

import datetime as dt  # 为长模块名起别名(类比Java import java.util.Date as MyDate;)
from math import pi as PI  # 为成员起别名,避免命名冲突

# 通过别名使用模块/成员
now = dt.datetime.now()  # 获取当前时间(dt是datetime模块的别名)
print(now)
print(PI)  # 3.141592653589793(PI是pi的别名)

1.3.4 批量导入:from 模块 import *(导入所有导出成员)

语法说明:

from 模块名 import *  # 导入模块中“所有对外导出”的成员
示例 1:导入标准库所有成员
from math import *  # 导入math模块的所有导出成员(pi、sqrt、sin、cos等)

# 直接使用所有导入的成员,无需前缀
print(pi)    # 3.141592653589793
print(sqrt(25))  # 5.0
print(sin(0))    # 0.0(sin是math模块的导出函数)
示例 2:自定义模块的批量导入与导出控制

第一步:定义模块 calc_utils.py(控制导出内容)

"""计算工具模块:演示导出控制"""

# 1. 公开成员(计划对外导出的)
def add(a, b):
    """加法函数:公开导出"""
    return a + b

def mul(a, b):
    """乘法函数:公开导出"""
    return a * b

PI = 3.1415926  # 常量:公开导出

# 2. 私有成员(仅模块内部使用,不建议对外导出)
# Python中以下划线_开头的成员,约定为“私有成员” [并非真的私有化,面向对象会讲解具体私有化]
def _check_number(num):
    """内部校验函数:私有,不对外导出"""
    return isinstance(num, (int, float))

# 3. 显式指定模块的导出列表(核心:控制*能导入的成员)
# __all__ 是模块的内置变量,定义了使用 from 模块 import * 时能导入的成员列表
__all__ = ["add", "mul", "PI"]

# 模块自测代码:仅直接运行本文件时执行
if __name__ == "__main__":
    print("自测加法:", add(1, 2))  # 输出 3
    print("自测乘法:", mul(3, 4))  # 输出 12
    print("内部校验:", _check_number(5))  # 输出 True(内部可使用私有函数)

Python 中 _单下划线约定俗成的私有成员(仅提醒开发者 “不建议外部使用”,并非真正限制访问),而 __双下划线 才是强制私有成员(通过名称修饰机制阻止外部直接访问)

第二步:在主程序 **main.py **中批量导入

# 方式1:批量导入(仅能导入__all__中定义的成员)
from calc_utils import *

# 可正常使用__all__中的成员
print("10 + 20 =", add(10, 20))  # 输出 30
print("3 × 5 =", mul(3, 5))      # 输出 15
print("圆周率:", PI)             # 输出 3.1415926

# 以下代码会报错:_check_number不在__all__中,无法通过*导入
# print(_check_number(10))  # NameError: name '_check_number' is not defined

# 方式2:强制导入私有成员(不推荐,破坏封装)
# 即使有__all__,仍可通过精准导入方式获取私有成员,但不符合Python规范
from calc_utils import _check_number
print("强制导入私有函数:", _check_number(10))  # 输出 True(不建议这么用)

1.3.5 导入时机与性能说明

  • 规范写法:import 语句统一写在文件顶部(类比 Java 的 import 语句位置),提升代码可读性

  • 性能特点:同一个模块在程序运行期间仅初始化一次,后续import会直接复用缓存,无需担心重复导入的性能损耗

  • 核心原则:少量导入不会成为性能瓶颈,优先保证代码清晰易读

Java 对比

  • Python 的import是 “加载并执行模块初始化代码”,后续复用缓存;

  • Java 的类加载有 “加载 / 链接 / 初始化” 流程,类对象也会被 ClassLoader 缓存;

  • Python 的sys.path 作用类似 Java 的classpath(都是指定模块 / 类的搜索路径)。

1.4 模块搜索路径:sys.path

Python 执行import xxx时,会按顺序在sys.path列表的路径中查找xxx.py文件或包。可通过以下代码查看搜索路径:

import sys

# 打印所有模块搜索路径(优先级从高到低)
for idx, path in enumerate(sys.path):
    print(f"{idx+1}. {path}")

sys.path 通常包含三类核心路径:

  1. 当前运行目录:执行脚本所在的项目目录

  2. 标准库目录:Python 内置模块的存放路径

  3. 第三方库目录:site-packages(pip 安装的库都在这里,类比 Java 的 maven 仓库)

1.5 高频坑:文件名与库名冲突

1.5.1 问题核心

在 Python 项目开发中,切勿在项目根目录(或程序运行目录)创建与 Python 标准库、第三方库同名的 .py 文件(如 json.pyrequests.pyos.py 等)。这是新手极易踩的高频错误,会直接导致模块导入异常、核心功能失效。

1.5.2 错误案例演示

场景复现

假设你在项目根目录创建了一个名为 json.py 的文件,文件内容如下:

# json.py(本地错误文件)
print("这是我自己的 json.py 文件")

接着在同目录下创建 main.py,尝试使用标准库 json 的核心功能:

# main.py
import json

# 尝试使用 json 标准库的 dumps 方法
data = {"name": "Python", "version": 3.11}
print(json.dumps(data))
执行结果与报错

运行 main.py 会触发如下报错(核心报错片段):

这是我自己的 json.py 文件
AttributeError: module 'json' has no attribute 'dumps'

报错原因:Python 执行 import json 时,优先导入了本地的 json.py,而非系统标准库的 json 模块;而你本地的 json.py 中没有 dumps 方法,因此报错。

1.5.3 底层原因解析

Python 的模块导入遵循「就近原则」,其查找模块的顺序为:

  1. 当前执行脚本所在目录(项目根目录);
  2. 环境变量 PYTHONPATH 配置的目录;
  3. Python 安装目录下的标准库路径;
  4. 第三方库安装路径(如 site-packages)。

简单来说:当本地文件名与系统库名一致时,Python 会 “认错” 模块 —— 把你的本地文件当成系统库导入,自然找不到库的核心功能。

1.5.4 解决方案

  1. 重命名本地文件(最直接):将冲突的文件名修改为非库名格式,比如把 json.py 改为 my_json_utils.pyjson_handler.py 等;

  2. 清理缓存文件:若已运行过错误代码,目录下会生成 __pycache__ 文件夹(编译后的缓存),需删除该文件夹后重新运行程序;

  3. 验证导入路径

    (可选):通过以下代码确认导入的模块路径,排查是否导入了本地文件:

    import json
    # 打印导入的 json 模块实际路径
    print(json.__file__)
    
    • 正确结果:指向 Python 安装目录的 json.py(如 C:\Python311\Lib\json\__init__.py);
    • 错误结果:指向你项目目录下的 json.py

1.5.5 避坑技巧

  1. 项目文件命名遵循「自定义前缀 + 功能」规则,比如 proj_json_parse.pytest_requests_api.py,避免直接使用 json.pyrequests.py 等纯库名;
  2. 常用易冲突库名清单(需重点规避):
    • 标准库:json.pyos.pysys.pytime.pyrandom.pycsv.py
    • 第三方库:requests.pypandas.pynumpy.pyflask.pydjango.py
  3. 大型项目建议使用「包结构」管理代码:将功能代码放在子包中(如 src/utils/json_handler.py),而非直接放在项目根目录,从结构上避免冲突。

1.6 name 与程序入口

每个 Python 模块都有内置变量__name__,用于标识模块的运行状态:

  • 直接运行模块__name__ = "__main__"(类比 Java 的public static void main(String[] args)入口方法)
  • 被其他模块导入__name__ = 模块名(如calc_utils

核心用途:将测试 / 演示代码放入入口保护中,避免被导入时自动执行。

demo.py

import keyword
# 1. 打印Python所有关键字列表
print(keyword.kwlist)
# 2. 打印列表对象的所有可用方法/属性(和关键字无关,只是演示dir()用法)
print(dir(keyword.kwlist))

# 3. 演示Python数字的下划线分隔写法(提升可读性,不影响数值)
num1 = 1_000_000_000_000_00_0
print(num1)  # 输出:1000000000000000

# 4. 主程序入口判断
if __name__ == '__main__':
    print(num1 , end=" ")

这段代码主要做了三件事:展示 Python 关键字、演示数字下划线写法、包含了一个 __name__ == '__main__' 语句块!

为什么需要 __name__ == '__main__'

你觉得 “直接输出也能打印,为啥还要加这个判断”,核心原因是:这个判断不是为了 “能输出”,而是为了 “控制代码的执行场景”

第一:先理解 __name__ 是什么?

__name__ 是 Python 的内置变量,它的值完全取决于当前脚本的运行方式,核心规则如下:

  • 当你直接运行这个脚本(比如 python demo.py),__name__ 的值是固定字符串 '__main__'
  • 当你把这个脚本作为模块导入到其他文件时(比如在另一个文件写 import demo),__name__ 的值会变成该脚本的模块名(即文件名,比如脚本叫 calc_utils.py,则 __name__ = 'calc_utils';脚本叫 demo.py,则 __name__ = 'demo')这样就可以控制主动运行执行,别人调用不执行!

简单说:__name__ 就像脚本的 “身份标识”—— 自己直接运行时是 “主程序”,被别人导入时是 “普通模块”。

第二:对比两种场景,一目了然

假设你的代码文件名叫 calc_utils.py(对应你提到的 calc_utils 模块名),我们用具体例子看差异:

场景 1:直接运行 demo.py

python demo.py

此时 __name__ = '__main__',所以:

  • 所有非缩进的代码(打印关键字、打印 num1 等)都会执行;
  • if __name__ == '__main__': 条件成立,里面的打印代码也会执行;
  • 最终会看到多次打印 num1 的结果。

场景 2:把 demo.py 作为模块导入到另一个文件

新建 main.py,内容如下:

# main.py
import demo  # 导入你的demo.py文件(此时demo的__name__ = 'demo')

print("我是main.py的代码")

运行 main.py,你会发现:

  • demo.py所有非缩进的代码(打印关键字、print (num1) 等)都会被执行(这是你不想要的);
  • if __name__ == '__main__': 条件不成立(因为 demo 里的 __name__ = 'demo',不等于 '__main__'),所以这个块里的代码不会执行;
  • 如果把 demo.py 里的打印逻辑都放进 if 块,导入时就只会加载变量 / 函数,不会打印任何内容。
  1. 改造后的示例(更合理的写法)

把 “仅在直接运行时需要执行的代码” 放进 if 块,“供其他模块使用的内容” 放在外面:

# demo.py
import keyword

# 供其他模块使用的变量/函数(对外暴露的内容)
num1 = 1_000_000_000_000_00_0

def print_keywords():
    """打印Python关键字列表(供其他模块调用)"""
    print(keyword.kwlist)

# 仅直接运行本脚本时才执行的代码(测试/演示逻辑)
if __name__ == '__main__':
    # 测试变量
    print(num1)  # 1000000000000000
    # 测试函数
    print_keywords()
    # 多次打印演示
    print(num1 , end=" ")

此时:

  • 直接运行 demo.py__name__ = '__main__'if 块里的所有测试代码都会执行;
  • 导入 demoimport demo):__name__ = 'demo'if 块代码不执行,只会加载 num1 变量和 print_keywords 函数,避免干扰主程序。

1.7 包 (Package) 与目录组织

当项目中模块(.py 文件)数量增多时,零散的文件会导致代码难以维护。Python 提供包(Package) 机制按目录层级组织模块,其设计思路与 Java 按 com.company.utils 这类包结构管理类文件的逻辑完全一致,仅物理实现略有差异。

1.7.1 包的核心定义

Python 中,一个目录要成为合法的包,需满足:

  1. 包含 __init__.py 文件(可空,也可编写包级初始化逻辑,如批量导出模块、定义包版本等);
  2. 现代 Python 支持无 __init__.py 的 “命名空间包”,但入门阶段建议严格遵循 “目录 + __init__.py” 的规范(类比 Java 必须按包名创建对应目录结构)。

1.7.2 典型项目结构

Python 包结构(物理目录)

project/
  ├── main.py          # 主程序(入口文件)
  └── mypkg/           # 自定义包
      ├── __init__.py  # 包初始化文件(必填)
      ├── tools.py     # 工具模块
      └── io_utils.py  # IO操作模块

Java 包结构(物理目录,对比参考)

project/
  ├── Main.java        # 主程序(入口类)
  └── com/
      └── company/
          └── mypkg/
              ├── Tools.java   # 工具类
              └── IOUtils.java # IO操作类

核心对比说明 :

  1. Python 用 “目录 + .py 文件” 组织模块,Java 用 “目录 + .java 文件” 组织类;
  2. 两者均按 “功能职责” 分层,目录结构与包名 / 模块名一一对应;
  3. Python 需通过 __init__.py 标识包,Java 无需额外文件,仅通过 package com.company.mypkg; 声明包名。

1.7.3 包的导入方式

方式 1:导入包中的指定模块

Python 代码

# 导入mypkg包下的tools模块
from mypkg import tools
# 调用模块中的函数
print(tools.some_func())

Java 等效代码

// 导入com.company.mypkg包下的Tools类
import com.company.mypkg.Tools;
// 调用类中的静态方法
System.out.println(Tools.someFunc());

方式 2:导入模块中的指定成员

Python 代码

# 直接导入io_utils模块的load_json函数
from mypkg.io_utils import load_json

data = load_json("data.json")

Java 等效代码

// 静态导入IOUtils类的loadJson方法
import static com.company.mypkg.IOUtils.loadJson;

String data = loadJson("data.json");

方式 3:导入模块并自定义别名

Python 代码

# 简化长模块名(常用技巧)
from mypkg import io_utils as io

data = io.load_json("data.json")

Java 等效代码

// Java无直接别名语法,间接简化
import com.company.mypkg.IOUtils;

IOUtils io = new IOUtils();
String data = io.loadJson("data.json");

1.7.4 Java 对比总结

  • Python 包是 “物理目录 + __init__.py” 的组合,导入时按 “目录。模块” 路径查找;
  • Java 包是 “命名空间 + 物理目录” 的映射,导入时按 “包名。类名” 查找;
  • 两者核心目的一致:按功能拆分代码,降低耦合,提升可读性。

1.8 循环导入:模块化高频报错

循环导入是 Python 模块化开发中最易踩的坑之一,其本质与 Spring 中 “两个组件互相依赖” 导致的编译 / 运行异常完全相同,仅报错形式略有差异。

1.8.1 问题定义

当模块 A 导入模块 B 的成员,同时模块 B 又导入模块 A 的成员时,会触发循环导入(Circular Import)

核心原因:Python/Java 加载模块 / 类时,会按 “自上而下” 的顺序初始化,循环依赖会导致 “被依赖的成员还未定义就被引用”。

运行代码时会触发以下报错(新手易误判为 “代码写错”,实则是依赖关系问题):

Python:ImportError: cannot import name 'XXX' from 'YYY'AttributeError: module 'XXX' has no attribute 'YYY'

1.8.2 示例:循环导入的问题代码

a.py

# A模块导入B模块的函数(触发循环第一步)
from b import b_func

def a_func():
    return "A模块函数"

# 调用B模块函数
print(b_func())

b.py

# B模块导入A模块的函数(形成循环依赖)
from a import a_func

def b_func():
    return "B模块函数"

# 调用A模块函数
print(a_func())

运行 python a.py 报错:ImportError: cannot import name 'b_func' from 'b'

1.8.3 解决思路(优先级从高到低)

1.8.3.1 方案 1:抽离公共代码(推荐)
  • 操作步骤:将 A、B 共用的逻辑抽离到第三个独立模块(如 common.py),A、B 均导入该模块,切断循环依赖。

  • 优化后 Python 结构:

    project/
      ├── a.py
      ├── b.py
      └── common.py  # 抽离公共函数/变量
    
  • Java 类比方案:将两个类的公共逻辑抽离为 CommonUtils.java,A、B 均导入该工具类,不再互相依赖。

1.8.3.2 方案 2:调整依赖方向
  • 操作步骤:梳理模块的 “层级关系”,让底层模块(如工具模块)不依赖上层模块(如业务模块),仅保留 “上层依赖底层” 的单向依赖。
  • Java 类比方案:调整类的依赖关系,确保工具类(底层)不依赖业务类(上层),仅业务类依赖工具类。
1.8.3.3 方案 3:局部导入(临时方案)
  • 操作步骤:将 import 语句移到函数内部(仅在需要时导入),避免模块加载阶段的循环依赖。

  • Python 优化示例(a.py):

    def a_func():
        return "A模块函数"
    
    def call_b_func():
        # 函数内部导入,避开模块初始化阶段的循环
        from b import b_func
        print(b_func())
    
    if __name__ == "__main__":
        call_b_func()
    
  • Java 类比方案:Java 无 “局部导入” 语法,需通过 “延迟初始化”(如在方法内创建对象)临时规避,但不推荐长期使用。

2. 文件操作&JSON格式 (File I/O)

2.1 为什么要学文件操作

程序运行时,变量、列表、字典等数据都存储在内存中,程序结束后会立即丢失;而文件操作可以将数据持久化到磁盘中,下一次运行程序时仍然可以读取、使用这些数据。

日常开发中,常见的文件类型分为两大类:

  • 文本文件:纯字符编码,可直接用文本编辑器打开

    • 普通文本:.txt.log
    • 结构化文本:.csv(表格)、.json(键值对)、.xml
  • 二进制文件:按字节存储,不可直接用文本编辑器解析

    • 多媒体文件:图片(.jpg/.png)、音频(.mp3)、视频(.mp4
    • 压缩包:.zip/.rar、可执行文件(.exe

2.2 openwith:核心写法

Python 中操作文件的基础函数是 open(),它用于打开一个文件并返回文件句柄(类似 Java 的 FileInputStream/FileOutputStream)。

推荐用法:使用 with 语句管理文件资源。它的核心优势是:代码块执行完毕后,会自动调用 close() 方法关闭文件,无需手动编写关闭逻辑,彻底避免因忘记关闭文件导致的资源泄漏问题。

graph TD
    A["with open(...)"] --> B["打开文件引用"]
    B --> C["读/写操作"]
    C --> D["离开 with 代码块"]
    D --> E["自动 close 释放资源"]

Java 对比

Python 的 with open(...) as f: 语法,完全等价于 Java 7 引入的 try-with-resources 语法。两者的设计目标一致:自动管理资源,无需手动关闭。

  • Python 示例:with open("data.txt", "r") as f:
  • Java 等效示例:try (FileReader fr = new FileReader("data.txt")) { ... }

2.2.1 读取文本文件(一次性读取)

适用于小文件(文件大小远小于内存),一次性将文件全部内容读取为一个字符串。

# 定义文件路径(相对路径:相对于当前程序运行的目录;绝对路径:完整路径,如 C:/data.txt)
path = "data.txt"

# with 语句:自动关闭文件
# 参数说明:"r" = 只读模式;encoding="utf-8" = 指定字符编码,读写中文必须显式指定
with open(path, mode="r", encoding="utf-8") as f:
    # read():一次性读取文件的全部内容
    full_text = f.read()

# 打印读取到的内容
print(full_text)

2.2.2 按行读取文本文件(逐行遍历)

适用于大文件(如日志文件、数据量巨大的文本),逐行读取并处理,不会一次性占用大量内存,是处理大文件的标准写法。

path = "data.txt"

with open(path, mode="r", encoding="utf-8") as f:
    # 直接遍历文件句柄 f,会自动按行读取
    for line in f:
        # 注意:每行的末尾会自带一个换行符 \n
        # rstrip("\n"):移除行尾的换行符,避免打印时出现多余的空行
        cleaned_line = line.rstrip("\n")
        print(cleaned_line)

2.2.3 写入文本文件

写入文件有两种核心模式:覆盖写入"w")和追加写入"a")。

模式 1:覆盖写入("w"

如果文件不存在,会自动创建;如果文件已存在,会清空原文件的所有内容,然后写入新内容。

path = "out.txt"

with open(path, mode="w", encoding="utf-8") as f:
    # write() 方法:写入字符串
    # 注意:write() 不会自动添加换行符,需要手动在字符串末尾加 \n
    f.write("Hello, Python!\n")
    f.write("文件操作的核心是 with 语句\n")
模式 2:追加写入("a"

如果文件不存在,会自动创建;如果文件已存在,会在文件的末尾追加新内容,原文件内容不会被修改。

path = "out.txt"

with open(path, mode="a", encoding="utf-8") as f:
    # 追加一行内容到文件末尾
    f.write("这是追加的内容,不会覆盖原文件\n")

2.3 常用模式与核心参数

open() 函数的第二个参数 mode 是必选的,用于指定文件的打开模式。下表是开发中最常用的几种模式:

模式 中文含义 核心说明 适用场景
"r" 只读(文本) 默认模式;文件不存在则报错;只能读,不能写 读取文本文件
"w" 写入(文本) 文件不存在则创建;文件存在则清空;只能写,不能读 覆盖写入文本文件
"a" 追加(文本) 文件不存在则创建;文件存在则追加;只能写,不能读 日志记录、追加文本内容
"rb" 只读(二进制) 以字节流形式读取;文件不存在则报错 读取图片、音频、压缩包
"wb" 写入(二进制) 以字节流形式写入;文件不存在则创建;存在则清空 写入图片、保存二进制数据

核心参数补充:

  • encoding="utf-8"读写中文文本文件时,必须显式指定该参数!如果不指定,Python 会使用系统默认的字符编码(Windows 下为 GBK,Linux/Mac 下为 UTF-8),极易导致中文乱码。
  • 二进制模式("rb"/"wb"):不能指定 encoding 参数,因为二进制文件是按字节存储的,不存在字符编码的概念。

2.4 小练习:统计文本文件的行数与关键字出现次数

结合按行读取和字符串操作,实现一个实用的小功能:统计文本文件的总行数,以及指定关键字在文件中出现的总次数(忽略大小写)。

# 配置参数:文件路径 + 要统计的关键字
file_path = "data.txt"
target_keyword = "python"

# 初始化统计变量
total_lines = 0  # 总行数
total_keyword_hits = 0  # 关键字出现的总次数

# 按行读取文件,逐行处理
with open(file_path, mode="r", encoding="utf-8") as f:
    for line in f:
        total_lines += 1  # 每读取一行,行数加 1
        # 统一转换为小写:实现忽略大小写的统计(如 Python、PYTHON 都算)
        # count() 方法:统计子字符串在当前字符串中出现的次数
        keyword_count_in_line = line.lower().count(target_keyword.lower())
        total_keyword_hits += keyword_count_in_line  # 累加到总次数中

# 打印统计结果
print(f"文件总行数:{total_lines}")
print(f"关键字「{target_keyword}」出现的总次数:{total_keyword_hits}")

2.5 JSON:最常用的数据存储格式之一

JSON(JavaScript Object Notation)是开发中最主流的结构化数据格式,也是 Python 与其他语言(Java/JS/Go)、前后端之间传输数据的首选格式。

JSON 核心特点:

  • 本质是文本文件:可直接用文本编辑器打开,可读性强;
  • 结构清晰:仅包含两种核心结构 ——对象(对应 Python 的 dict)数组(对应 Python 的 list)
  • 跨语言通用:Python、Java、JavaScript、Go 等所有主流语言都内置 / 提供了 JSON 解析库,数据互通无壁垒;
  • 体积小:相比 XML,JSON 语法更简洁,传输 / 存储效率更高。

Java 对比

  • Python:标准库 json 模块原生支持 JSON 序列化 / 反序列化,无需额外依赖;
  • Java:需借助第三方库(如 Jackson、Gson、FastJSON),核心思想完全一致:
    • Python:json.dumps(对象) → JSON 字符串;json.loads(JSON串) → Python 对象;
    • Java(Jackson):new ObjectMapper().writeValueAsString(对象) → JSON 字符串;new ObjectMapper().readValue(JSON串, 类名.class) → Java 对象。

2.5.1 json.dumps/json.loads(字符串级别:内存操作)

dumps(dump string):将 Python 对象序列化为 JSON 格式的字符串(仅在内存中操作,不涉及文件);

loads(load string):将 JSON 格式的字符串反序列化回 Python 对象(dict/list 等)。

import json  # 导入Python内置的json标准库

# 1. 定义一个Python字典(待序列化的对象)
python_data = {
    "name": "Tom",
    "age": 18,
    "is_student": True,
    "tags": ["python", "oop", "json"],
    "score": {"math": 95, "english": 88}
}

# 2. 序列化:Python对象 → JSON字符串
# 关键参数:ensure_ascii=False → 保留中文等非ASCII字符(不转成\uXXXX编码)
json_str = json.dumps(python_data, ensure_ascii=False)
print("JSON字符串:")
print(json_str)
print("数据类型:", type(json_str))  # 输出:<class 'str'>

# 3. 反序列化:JSON字符串 → Python对象
python_obj = json.loads(json_str)
print("\n反序列化后的Python对象:")
print(python_obj)
print("获取tags字段:", python_obj["tags"])  # 输出:['python', 'oop', 'json']
print("数据类型:", type(python_obj))  # 输出:<class 'dict'>

2.5.2 json.dump/json.load(文件级别:持久化操作)

dump:将 Python 对象序列化后直接写入文件(一步完成 “序列化 + 写文件”);

load:从 JSON 文件中读取内容,并反序列化为 Python 对象(一步完成 “读文件 + 反序列化”)。

import json

# 1. 定义要写入文件的Python数据(列表+字典)
user_list = [
    {"name": "Tom", "age": 18, "city": "北京"},
    {"name": "Jerry", "age": 20, "city": "上海"},
    {"name": "Alice", "age": 19, "city": "广州"}
]

# 2. 序列化并写入JSON文件
file_path = "users.json"
with open(file_path, "w", encoding="utf-8") as f:
    # 关键参数:
    # - ensure_ascii=False:保留中文
    # - indent=2:格式化输出(缩进2个空格),便于阅读/调试(生产环境可省略,减少文件体积)
    json.dump(user_list, f, ensure_ascii=False, indent=2)

# 3. 从JSON文件读取并反序列化
with open(file_path, "r", encoding="utf-8") as f:
    loaded_data = json.load(f)

print("从文件读取的Python对象:")
print(loaded_data)
print("第一个用户的姓名:", loaded_data[0]["name"])  # 输出:Tom
print("数据类型:", type(loaded_data))  # 输出:<class 'list'>

2.5.3 常见坑:JSON 仅支持 “可序列化类型”

JSON 是一种轻量级数据格式,仅支持以下基础类型,非内置类型直接序列化会报错

JSON 类型 Python 对应类型 说明
字符串 str 必须用双引号包裹
数字 int/float 无大小限制
布尔 bool JSON 中是 true/false(小写),Python 中是 True/False
空值 None JSON 中是 null,Python 中是 None
数组 list/tuple 序列化后统一为 JSON 数组
对象 dict 键必须是字符串

错误示例(非内置类型序列化)

import json
from datetime import datetime  # 导入Python的日期时间类型

# 定义包含非JSON内置类型的数据(datetime不是JSON支持的类型)
data = {
    "name": "Tom",
    "register_time": datetime.now()  # datetime对象
}

try:
    # 直接序列化会触发TypeError
    json_str = json.dumps(data, ensure_ascii=False)
except TypeError as e:
    print("JSON序列化失败:", e)
    # 输出:JSON序列化失败:Object of type datetime is not JSON serializable

# 解决方案:自定义序列化函数(入门版)
def json_serialize(obj):
    # 如果是datetime对象,转换为字符串
    if isinstance(obj, datetime):
        return obj.strftime("%Y-%m-%d %H:%M:%S")
    # 其他类型抛出原异常
    raise TypeError(f"类型 {type(obj)} 不支持JSON序列化")

# 使用default参数指定自定义序列化函数
json_str = json.dumps(data, ensure_ascii=False, default=json_serialize)
print("\n自定义序列化后的JSON字符串:")
print(json_str)
# 输出示例:{"name":"Tom","register_time":"2026-01-06 15:30:00"}

总结(JSON 核心要点)

  1. JSON 是跨语言的结构化文本格式,Python 用 json 标准库操作,Java 需借助 Jackson/Gson 等库;
  2. 内存操作用 dumps/loads,文件操作用 dump/loadensure_ascii=False 是读写中文的必加参数;
  3. JSON 仅支持基础类型,非内置类型(如 datetime)需自定义序列化逻辑;
  4. indent=2 用于格式化输出(调试用),生产环境建议省略以减小文件体积。

3. 异常处理 (Exception)

3.1 为什么需要异常处理

程序运行时会遇到各种“不可控问题”:文件不存在、网络超时、用户输入不合法等。

异常处理的目标:

  • 防止程序直接崩溃
  • 给用户明确的错误提示
  • 做好兜底处理(默认值、重试、回滚、释放资源等)

3.2 try / except / else / finally

基本语法

try:
    # 可能出错的代码放在 try 里
    x = int("123")
except ValueError:
    # 只处理你“预期会发生”的异常
    print("转换失败:不是合法数字")
else:
    # 没异常才会执行
    print("转换成功:", x)
finally:
    # 无论是否异常都会执行(常用于资源清理)
    print("不管成功失败都会执行到这里")

3.2.1 捕获多个异常

try:
    a = int("1")
    b = 10 / 0
except (ValueError, ZeroDivisionError) as e:
    print("发生错误:", e)

3.2.2 不建议:裸 except

裸except: 会把所有异常都吞掉,容易隐藏真实问题。入门阶段建议尽量写具体异常类型。

假设你写了一段 “读取文件并转换数字” 的代码,预期可能出现「文件不存在」「内容不是数字」两种错误,但代码可能还会触发其他你没预料到的错误(比如变量名写错):

场景 1:用具体异常(推荐)

try:
    # 步骤1:读文件
    with open("data.txt", "r") as f:
        content = f.read()
    # 步骤2:转数字(假设文件内容是字符串)
    num = int(content)
    # 步骤3:这里故意写错变量名(你没预料到的错误)
    print(numm)  # 变量名错了,会触发 NameError
except FileNotFoundError:
    # 只捕获“文件不存在”的异常
    print("错误:文件找不到")
except ValueError:
    # 只捕获“转换数字失败”的异常
    print("错误:文件内容不是合法数字")

运行结果:

NameError: name 'numm' is not defined

好处:你能清晰看到「变量名写错」这个未预期的错误,立刻知道代码哪里写错了,能快速修复。

场景 2:用裸 except(危险)

把上面的代码改成裸 except:

try:
    with open("data.txt", "r") as f:
        content = f.read()
    num = int(content)
    print(numm)  # 变量名错误
except:  # 裸 except:捕捉所有异常
    print("出错了,但不知道为啥")

运行结果:

出错了,但不知道为啥

问题来了:

  1. 你只知道 “出错了”,但完全不知道是「文件不存在」「内容不是数字」还是「变量名写错」;
  2. 这个 “变量名写错” 是语法级的低级错误,本应该立刻发现并修复,但被裸 except 掩盖了;
  3. 更严重的是:哪怕是系统级的致命异常(比如用户按 Ctrl+C 终止程序的 KeyboardInterrupt),也会被裸 except 吞掉,导致程序无法正常退出。

3.3 raise:主动抛出异常

当你检测到“业务规则不满足”时,可以主动抛异常中断流程:

def withdraw(balance, amount):
    if amount <= 0:
        raise ValueError("取款金额必须大于 0")
    if amount > balance:
        raise ValueError("余额不足")
    return balance - amount

print(withdraw(100, 30))

Java 对比

  • 关键字:Python 用 raise 抛出异常,Java 用 throw
  • 异常类型
    • Java 分为 受检异常(Checked Exception)(必须 try-catch 或 throws)和 非受检异常(Unchecked Exception)
    • Python 所有异常都是非受检的,解释器不强制要求捕获,给予开发者最大的灵活性(但也需更自律)。

3.4 自定义异常:表达更清晰的业务错误

Python 中所有异常都是「类」—— 内置异常(如 ValueError)是 Python 提前定义好的类,自定义异常就是继承 Exception 类创建的专属业务异常类

  • 必须继承 Exception(而非 BaseException):BaseException 包含系统级异常(如 KeyboardInterrupt),自定义业务异常继承 Exception 能避免捕获到系统级错误;
  • 自定义异常的核心价值:让异常语义更贴合业务场景(比如 LoginError 一眼就知道是登录相关错误,而 ValueError 是通用错误)。

3.4.1 自定义异常的基础用法

# 1. 定义自定义异常类(继承 Exception)
class LoginError(Exception):
    """
    自定义登录异常类
    用于表达所有登录相关的业务错误(用户名不存在、密码错误、验证码过期等)
    """
    # pass 表示暂时无自定义逻辑,仅继承父类功能
    pass

# 2. 业务函数中抛出自定义异常
def login(username, password):
    """模拟登录校验"""
    # 模拟数据库中的正确账号密码
    valid_username = "admin"
    valid_password = "123456"
    
    # 校验失败时,主动抛出 LoginError
    if username != valid_username or password != valid_password:
        # raise + 异常类实例(括号内是错误提示信息)
        raise LoginError("用户名或密码错误")
    # 校验通过,返回登录成功
    return True

# 3. 捕获并处理自定义异常
try:
    # 调用登录函数(传入错误的账号密码)
    login("tom", "111")
except LoginError as e:
    # e 是异常实例,直接打印会输出错误提示信息
    print("登录失败:", e)  # 输出:登录失败:用户名或密码错误

3.4.2 自定义异常的进阶用法(增强错误信息)

基础版仅能传递字符串提示,进阶版可自定义属性(如错误码、错误详情),让异常信息更完整(便于日志记录、前端展示):

class LoginError(Exception):
    """
    增强版登录异常类
    :param message: 错误提示信息
    :param error_code: 自定义错误码(如1001=用户名错,1002=密码错)
    :param detail: 错误详情(可选,如请求ID、时间戳)
    """
    def __init__(self, message, error_code, detail=None):
        # 调用父类 __init__ 方法,保证基础异常功能
        super().__init__(message)
        # 新增自定义属性
        self.error_code = error_code
        self.detail = detail

# 业务函数中按不同错误类型抛异常
def login(username, password):
    valid_username = "admin"
    valid_password = "123456"
    
    if username != valid_username:
        # 用户名错误:错误码1001,附带详情
        raise LoginError(
            message="用户名不存在",
            error_code=1001,
            detail=f"尝试登录的用户名:{username}"
        )
    if password != valid_password:
        # 密码错误:错误码1002
        raise LoginError(
            message="密码错误",
            error_code=1002,
            detail=f"用户名 {username} 输入了错误密码"
        )
    return True

# 捕获异常时,获取自定义属性
try:
    login("tom", "111")  # 用户名错误
except LoginError as e:
    print(f"登录失败:{e}")          # 输出:登录失败:用户名不存在
    print(f"错误码:{e.error_code}") # 输出:错误码:1001
    print(f"错误详情:{e.detail}")   # 输出:错误详情:尝试登录的用户名:tom

try:
    login("admin", "654321")  # 密码错误
except LoginError as e:
    print(f"登录失败:{e}")          # 输出:登录失败:密码错误
    print(f"错误码:{e.error_code}") # 输出:错误码:1002

3.4.3 自定义异常的适用场景

  • 业务专属错误:登录失败、订单超时、权限不足、库存不足等(用自定义异常比通用异常更清晰);
  • 多错误类型区分:同一业务场景下有多种错误(如登录的 “用户名错”“密码错”“验证码错”);
  • 错误信息标准化:需要统一的错误码、错误格式(如接口返回给前端的异常信息)。

3.4.4 Python 常用内置异常类型

Python 内置了大量异常类,覆盖开发中常见的错误场景,以下是按 “使用频率” 整理的核心清单:

  1. 基础类型错误(最常用)
异常类型 触发场景 示例
ValueError 传入的值类型正确,但内容不合法 int("abc")(字符串无法转数字)
TypeError 传入的参数类型错误 1 + "2"(数字和字符串无法相加)
NameError 使用未定义的变量 / 函数名 print(numm)(变量名写错)
IndexError 列表 / 元组索引超出范围 [1,2,3][5](索引 5 不存在)
KeyError 字典中访问不存在的键 {"name":"Tom"}["age"](键 age 不存在)
ZeroDivisionError 除数为 0 10 / 0
AttributeError 访问对象不存在的属性 / 方法 "abc".append(1)(字符串无 append 方法)
  1. 文件 / IO 相关异常
异常类型 触发场景 示例
FileNotFoundError 打开不存在的文件(只读模式) open("不存在的文件.txt", "r")
PermissionError 没有文件操作权限(如写只读文件) open("/root/test.txt", "w")(无权限)
IOError 通用 IO 错误(如文件读写失败) 网络文件读写中断
  1. 数据结构 / 迭代相关异常
异常类型 触发场景 示例
StopIteration 迭代器遍历完毕后继续取值 it = iter([1,2]); next(it); next(it); next(it)
KeyError 字典 / 集合访问不存在的键 / 元素 set([1,2]).remove(3)(移除不存在元素)
AssertionError assert 断言失败(用于逻辑校验) assert 1>2, "逻辑错误"
  1. 系统 / 运行时异常
异常类型 触发场景 说明
RuntimeError 通用运行时错误(无具体分类的错误) 如递归深度超出限制
MemoryError 内存不足(如创建超大列表) [1]*10000000000(超出内存限制)
KeyboardInterrupt 用户按下 Ctrl+C 终止程序 系统级异常,不建议捕获
ImportError 导入模块失败(如模块不存在) import non_exist_module
ModuleNotFoundError ImportError 的子类,模块不存在(Python3.6+) 同上
  1. 异常层级关系(核心)

所有内置异常都继承自 BaseException,核心层级如下(简化版):

BaseException(顶层)
├── Exception(业务异常的父类)
│   ├── ValueError/TypeError/IndexError(基础错误)
│   ├── FileNotFoundError/IOError(IO错误)
│   ├── 自定义异常(继承Exception)
├── KeyboardInterrupt(用户中断)
├── SystemExit(程序退出)

4. 面向对象编程 (OOP)

4.1 面向过程和面向对象编程

面向过程编程(Procedural Programming)和面向对象编程(OOP)是两种核心编程范式,Python 作为混合型语言,同时支持这两种编程方式。

  • 面向过程:以 “步骤 / 过程” 为核心,将问题拆解为一系列函数 / 步骤,按顺序执行;
  • 面向对象:以 “对象” 为核心,将数据(属性)和操作(方法)封装到对象中,关注对象间的交互。

4.1.1 面向过程示例

面向过程的核心是 “步骤化”,适合简单、线性的任务,示例(做饭):

# 面向过程:拆解为独立步骤函数
def buy():
    """购买食材"""
    print("去超市购买食材。")

def wash():
    """清洗食材"""
    print("清洗蔬菜。")

def cut():
    """切菜"""
    print("切菜。")

def cook():
    """烹饪"""
    print("开始烹饪。")

def serve():
    """上菜"""
    print("上菜啦!")

# 按顺序执行步骤
buy()
wash()
cut()
cook()
serve()

缺点:代码复用性差,新增菜品 / 修改步骤时需大幅改动代码,难以维护。

4.1.2 面向对象示例

面向对象的核心是 “封装、继承、多态”,示例(做饭):

"""
面向对象实现做饭功能:封装+继承+多态
"""
# 父类:菜品模板(抽象共性)
class Dish:
    def __init__(self, name):
        self.name = name  # 实例属性:菜品名称
    
    def prepare(self):
        """准备菜品(子类重写实现具体逻辑)"""
        pass

# 子类1:沙拉(继承Dish,重写prepare)
class Salad(Dish):
    def prepare(self):
        print(f"1. 为【{self.name}】购买蔬菜")
        print(f"2. 清洗【{self.name}】的蔬菜")
        print(f"3. 切【{self.name}】的蔬菜")
        print(f"4. 将【{self.name}】装盘")

# 子类2:炖肉(继承Dish,重写prepare)
class Stew(Dish):
    def prepare(self):
        print(f"1. 为【{self.name}】购买肉类")
        print(f"2. 清洗【{self.name}】的肉")
        print(f"3. 切【{self.name}】的肉")
        print(f"4. 烹饪【{self.name}】")
        print(f"5. 将【{self.name}】装盘")

# 子类3:汤品(继承Dish,重写prepare)
class Soup(Dish):
    def prepare(self):
        print(f"1. 为【{self.name}】购买食材")
        print(f"2. 煮【{self.name}】")

# 实例化并调用(多态:不同子类调用同一方法,表现不同行为)
salad = Salad("蔬菜沙拉")
stew = Stew("土豆炖肉")
soup = Soup("紫菜鸡蛋汤")

salad.prepare()
print()  # 空行分隔
stew.prepare()
print()
soup.prepare()

优势

  • 封装:菜品名称和制作逻辑封装在类中,数据和操作绑定;
  • 继承:子类复用父类的__init__方法,无需重复定义;
  • 多态:不同子类的prepare方法实现不同逻辑,新增菜品只需加子类,不修改原有代码。

Java 对比

  • Python 无 “抽象类 / 接口” 的强制关键字(如 Java 的 abstract class/interface),但可通过空方法模拟抽象逻辑;
  • Java 需显式声明继承(extends),Python 直接在类定义后加父类名即可。

4.2 类和对象

4.2.1 类(Class)和对象(Object)

概念 核心定义
对对象共性的抽象,是创建对象的 “模板”,定义了对象的属性(是什么)和方法(能做什么);
对象 类的具体实例,拥有类定义的属性和方法,是客观存在的具体实体;

4.2.2 定义类

语法格式

class 类名:  # 类名用大驼峰命名法(如 Person、Student)
    """类说明文档(可选)"""
    # 类体:类属性、方法、实例属性等

示例:定义 Person 类

class Person:
    """人的类:演示类属性、实例属性、方法"""
    # 类属性(所有实例共享)
    home = "earth"

    # 初始化方法:创建对象时自动调用,初始化实例属性
    def __init__(self):
        self.age = 0  # 实例属性(每个实例独立)

    # 实例方法:操作实例数据
    def eat(self):
        print("eating...")

    def drink(self):
        print("drinking...")

Java 对比

  • Python 的 __init__ 对应 Java 的构造方法(public Person() {});
  • Java 需显式声明属性类型(如 private String home = "earth";),Python 无需声明类型。

4.2.3 类的操作之一:成员引用

语法类名.成员名(访问类属性、方法、说明文档)

# 访问类属性
print(Person.home)  # 输出:earth
# 访问类方法(返回函数对象)
print(Person.eat)   # 输出:<function Person.eat at 0x...>
# 访问类说明文档
print(Person.__doc__)  # 输出:人的类:演示类属性、实例属性、方法

4.2.4 类的操作之二:实例化

创建对象的过程称为 “实例化”,语法变量名 = 类名(实参列表)

# 实例化 Person 类
p = Person()

# 访问实例属性/类属性
print(p.age)   # 实例属性,输出:0
print(p.home)  # 类属性,输出:earth

# 调用实例方法
p.eat()   # 输出:eating...
p.drink() # 输出:drinking...

4.3 __init__() 方法

__init__() 是 Python 的构造方法,创建对象时自动调用,用于初始化实例属性。

核心特点 :

  1. self:约定俗成的参数名,代表实例对象本身,通过 self 可访问实例属性 / 方法;
  2. 非必需:类中未定义 __init__ 时,Python 会使用默认空构造方法;
  3. 返回值:只能返回 None,返回其他值会触发 TypeError

示例

class Person:
    """人的类:带参数的构造方法"""
    def __init__(self, name, age):
        """
        构造方法:初始化姓名和年龄
        :param name: 姓名(字符串)
        :param age: 年龄(整数)
        """
        self.name = name  # 实例属性:姓名
        self.age = age    # 实例属性:年龄

    def print_info(self):
        """打印实例信息"""
        print(f"姓名:{self.name},年龄:{self.age}")

# 实例化(传入构造方法参数)
person = Person("张三", 18)
person.print_info()  # 输出:姓名:张三,年龄:18

Java 对比

  • Java 构造方法名必须与类名相同(public Person(String name, int age));
  • Python 构造方法固定为 __init__,参数无需声明类型。

4.4 self

self 是实例方法的第一个参数,代表实例对象本身,核心作用:

  1. 在类内部:通过 self 访问 / 修改实例属性、调用实例方法;
  2. 调用方法时:实例对象会自动作为 self 传入,无需手动传参。

示例

class Person:
    home = "earth"

    def __init__(self, name):
        self.name = name  # 实例属性

    def eat(self):
        print(f"{self.name} is eating...")

    def drink(self):
        print(f"{self.name} is drinking...")

    def eat_and_drink(self):
        # 内部调用实例方法(通过self)
        self.eat()
        self.drink()

# 实例化
p = Person("张三")

# 两种调用方式(效果一致)
p.eat()  # 推荐:自动传入self
Person.eat(p)  # 等效:手动传入实例对象

# 调用组合方法
p.eat_and_drink()

Java 对比

  • Java 中无需显式声明 self(对应 this),直接用 this.name 访问实例属性;
  • Python 必须显式写 self 作为第一个参数,否则会报错。

4.5 属性

Python 中属性分为类属性实例属性,核心区别是 “是否共享”。

4.5.1 类属性

  • 定义:类中、方法外的属性(所有实例共享);
  • 访问:推荐 类名.类属性,也可 实例名.类属性
  • 修改:仅推荐 类名.类属性 = 值(实例修改会新增同名实例属性,而非修改类属性)。

示例

class Person:
    home = "earth"  # 类属性

# 访问类属性
print(Person.home)  # 输出:earth
p1 = Person()
p2 = Person()
print(p1.home, p2.home)  # 输出:earth earth

# 修改类属性(所有实例同步变化)
Person.home = "mars"
print(Person.home, p1.home, p2.home)  # 输出:mars mars mars

# 实例“修改”类属性(实际新增实例属性)
p1.home = "venus"
print(Person.home)  # 类属性不变:mars
print(p1.home)      # 实例属性:venus
print(p2.home)      # 其他实例仍用类属性:mars

4.5.2 实例属性

  • 定义:__init__ 中通过 self.属性名 定义(每个实例独立);
  • 访问:仅能 实例名.实例属性,不能 类名.实例属性
  • 修改:实例名.实例属性 = 值(仅影响当前实例)。

示例

class Person:
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age    # 实例属性

# 实例化
p1 = Person("张三", 18)
p2 = Person("李四", 20)

# 访问实例属性
print(p1.name, p1.age)  # 输出:张三 18
print(p2.name, p2.age)  # 输出:李四 20

# 修改实例属性(互不影响)
p1.age = 19
print(p1.age)  # 输出:19
print(p2.age)  # 输出:20

# print(Person.name)  # 报错:类无法访问实例属性

Java 对比

  • Java 类属性需加 static(如 static String home = "earth";),实例属性不加;
  • Python 无需 static,通过定义位置区分类属性 / 实例属性。

4.6 方法

Python 类中有三种方法:实例方法、类方法、静态方法

4.6.1 类方法

  • 定义:@classmethod 装饰器,第一个参数 cls(代表类本身);
  • 访问权限:仅能访问类属性 / 类方法,不能访问实例属性 / 方法;
  • 调用:推荐 类名.类方法(),也可 实例名.类方法()

示例

class Person:
    home = "earth"  # 类属性

    def __init__(self, name):
        self.name = name  # 实例属性

    @classmethod
    def print_home(cls):
        """类方法:访问类属性"""
        print(f"家庭住址:{cls.home}")
        # print(self.name)  # 报错:类方法无法访问实例属性

# 调用类方法
Person.print_home()  # 推荐:输出“家庭住址:earth”
p = Person("张三")
p.print_home()       # 不推荐,但允许

Java 对比

  • Python 类方法对应 Java 的 static 方法(public static void printHome());
  • Java 静态方法中用 类名.属性 访问,Python 用 cls.属性

4.6.2 实例方法

  • 定义:无装饰器,第一个参数 self(代表实例);
  • 访问权限:可访问类属性 / 类方法、实例属性 / 实例方法;
  • 调用:推荐 实例名.方法名(),也可 类名.方法名(实例)

示例

class Person:
    home = "earth"  # 类属性

    def __init__(self, name):
        self.name = name  # 实例属性

    @classmethod
    def print_home(cls):
        print(f"家庭住址:{cls.home}")

    def eat(self):
        """实例方法:访问类属性+实例属性"""
        print(f"{self.name} is eating in {Person.home}")

    def print_info(self):
        """实例方法:调用其他方法"""
        self.eat()          # 调用实例方法
        Person.print_home() # 调用类方法

# 实例化并调用
p = Person("张三")
p.print_info()
# 等效调用:Person.print_info(p)

4.6.3 静态方法

  • 定义:@staticmethod 装饰器,无 self/cls 特殊参数;
  • 作用:工具函数(与类 / 实例无关),仅为代码组织放在类中;
  • 调用:类名.静态方法()实例名.静态方法()

示例

class Person:
    home = "earth"

    def __init__(self, name):
        self.name = name

    @staticmethod
    def say(word):
        """静态方法:无类/实例依赖"""
        print(f"说:{word}")
        # 不推荐:静态方法中访问类属性
        # print(Person.home)

# 调用静态方法
Person.say("hello")  # 推荐:输出“说:hello”
p = Person("张三")
p.say("hi")          # 允许:输出“说:hi”

4.6.4 三种方法对比

特性 实例方法 类方法 静态方法
装饰器 @classmethod @staticmethod
第一个参数 self(实例) cls(类)
访问权限 类 + 实例属性 / 方法 仅类属性 / 方法 无(可手动访问类属性)
推荐调用方式 实例名.方法() 类名.方法() 类名.方法()
使用场景 操作实例数据 操作类数据 / 工厂方法 独立工具函数

4.6.5 特殊方法 / 魔法方法

方法名以 __ 开头和结尾的方法(如 __init__),特定操作时自动调用。

常用特殊方法示例

class DemoClass:
    def __new__(cls, name):
        """1. 创建实例(第一个调用)"""
        print(f"调用 __new__ 创建 {cls.__name__} 实例")
        instance = super().__new__(cls)
        return instance

    def __init__(self, name):
        """2. 初始化实例"""
        print("调用 __init__ 初始化属性")
        self.name = name

    def __str__(self):
        """3. 调用 str(实例) 时执行"""
        return f"DemoClass: name={self.name}"

    def __del__(self):
        """4. 实例销毁时执行"""
        print(f"调用 __del__ 销毁 {self.name} 实例")

# 演示流程
obj = DemoClass("test")  # 触发 __new__ + __init__
print(str(obj))          # 触发 __str__
del obj                  # 触发 __del__(引用计数为0时)

Java 对比

  • Python 的 __str__ 对应 Java 的 toString() 方法;
  • Python 无析构方法的严格触发时机(依赖垃圾回收),Java 有 finalize()(类似 __del__)。

4.6.6 动态增删属性和方法

Python 支持运行时动态修改类 / 实例的属性和方法(Java 需反射,Python 原生支持)。

4.6.6.1 动态添加类属性 / 方法
class Person:
    def __init__(self, name):
        self.name = name

# 动态添加类属性
Person.home = "earth"
# 动态添加类方法
@classmethod
def come_from(cls):
    print(f"I come from {cls.home}")
Person.come_from = come_from

# 验证
print(Person.home)      # 输出:earth
Person.come_from()      # 输出:I come from earth
4.6.6.2 动态添加实例属性 / 方法
import types

# 实例化
p1 = Person("张三")
# 动态添加实例属性
p1.age = 18
# 动态添加实例方法
def eat(self):
    print(f"{self.name} 在吃饭")
p1.eat = types.MethodType(eat, p1)

# 验证
print(p1.age)  # 输出:18
p1.eat()       # 输出:张三 在吃饭
4.6.6.3 __slots__ 限制动态添加

__slots__ 可限制实例能添加的属性 / 方法(仅对当前类生效,子类无效):

class Person:
    __slots__ = ("name", "age")  # 仅允许添加这两个实例属性

p = Person()
p.name = "张三"  # 允许
p.age = 18       # 允许
# p.weight = 60  # 报错:weight 不在 __slots__ 中

5. 面向对象编程三大基本特征

5.1 封装

封装是面向对象的核心特性之一,指将类的属性和方法包裹在类内部,隐藏实现细节,仅对外暴露必要的访问接口,既保证数据安全,又简化外部调用逻辑。

5.1.1 私有化机制

Python 通过命名约定实现 “私有化”,限制属性 / 方法的访问范围,核心分为两种方式:

私有化方式 命名规则 特性
单下划线 _属性名/_方法名 约定俗成的 “非公开 API”,无强制限制,仅提醒开发者 “不要外部访问”;
双下划线 __属性名/__方法名 名称改写(Name Mangling):Python 自动将其转为 _类名__属性名,类外无法直接访问;

5.1.2 私有属性和方法示例

"""
演示私有属性/方法的定义与访问规则
核心:双下划线成员会被名称改写,类外需通过 _类名__成员名 访问(不推荐)
"""
class Person:
    # 私有类属性:自动改写为 _Person__home
    __home = "earth"

    def __init__(self, name):
        self.__name = name  # 私有实例属性:_Person__name
        self.__age = 0      # 私有实例属性:_Person__age
        self._sex = "male"  # 单下划线:约定非公开,无强制限制

    # 私有方法:自动改写为 _Person__check_age
    def __check_age(self, age):
        print(f"检查年龄{age}的合法性...")
        if age < 0 or age > 100:
            raise ValueError("年龄超出0-100范围")

    # 公开接口:获取私有属性
    def get_name(self):
        return self.__name

    # 公开接口:设置私有属性(带校验逻辑)
    def set_age(self, age):
        self.__check_age(age)  # 类内可直接调用私有方法
        self.__age = age

    # 公开接口:获取私有属性
    def get_age(self):
        return self.__age

# 实例化测试
p = Person("张三")

# 1. 单下划线成员:可外部访问(仅为约定)
print(f"单下划线成员访问:p._sex = {p._sex}")  # 输出:male

# 2. 双下划线成员:直接访问报错,需通过改写后的名称(不推荐)
# print(p.__name)  # 报错:AttributeError
print(f"私有属性改写后访问:p._Person__name = {p._Person__name}")  # 输出:张三
print(f"通过公开接口访问:p.get_name() = {p.get_name()}")          # 推荐方式:输出张三

# 3. 私有方法/属性的合法操作
p.set_age(18)  # 通过公开接口设置私有属性(自动触发校验)
print(f"私有属性age:p.get_age() = {p.get_age()}")                # 输出:18
# p.__check_age(20)  # 报错:AttributeError
p._Person__check_age(20)  # 强制访问改写后的私有方法(不推荐)

Java 对比

  • Java 通过 private/public 关键字强制私有化,Python 无关键字,仅靠命名规则;
  • Java 私有成员完全无法外部访问,Python 可通过名称改写 “绕过”(违背封装原则,禁止使用)。

5.1.3 @property 装饰器:属性化方法

@property 可将方法转为 “属性式访问”(调用无需加 ()),常用来封装私有属性的读写逻辑,替代传统的 getter/setter 方法,让代码更简洁。

核心用法

  1. @property:定义 getter 方法(读取私有属性);
  2. @方法名.setter:定义 setter 方法(修改私有属性);
  3. 注意:方法名不能与属性名重名,否则会触发无限递归。

示例代码

"""
@property 核心场景:封装私有属性的读写逻辑
"""
class Person:
    def __init__(self, name=None):
        self.__name = name  # 私有属性

    # 1. 无返回值的属性方法(仅执行逻辑)
    @property
    def eat(self):
        print(f"{self.__name} is eating")

    # 2. getter方法:读取私有属性
    @property
    def name(self):
        return self.__name

    # 3. setter方法:修改私有属性(需与getter方法名一致)
    @name.setter
    def name(self, new_name):
        if not isinstance(new_name, str):
            raise TypeError("姓名必须是字符串")
        self.__name = new_name

# 测试
p = Person("张三")
p.eat  # 属性式调用:无需加(),输出“张三 is eating”
p.name = "李四"  # 调用setter方法,修改私有属性
print(p.name)    # 调用getter方法,输出“李四”

# 错误示例:递归调用(注释掉避免报错)
# @property
# def age(self):
#     return self.age  # 无限递归:self.age 会再次调用 @property 装饰的age方法

5.2 继承

继承允许子类(派生类)复用父类(基类)的属性和方法,同时可扩展子类特有功能或重写父类方法,大幅减少代码冗余,是实现代码复用的核心手段。

5.2.1 单继承

语法格式

class 子类名(父类名):
    子类体  # 可新增属性/方法,或重写父类方法

示例代码

"""
单继承核心:
1. 子类通过 super() 调用父类构造方法;
2. 子类无法直接访问父类私有成员(可改写名称访问,不推荐);
"""
# 父类:通用的Person类
class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age  # 私有属性:_Person__age
        print("父类Person构造方法执行")

    def eat(self):
        print(f"{self.name} 吃东西....")

# 子类1:Student(继承Person,新增score属性)
class Student(Person):
    def __init__(self, name, age, score):
        # 调用父类构造方法(初始化name/__age)
        super().__init__(name, age)
        self.score = score  # 子类特有属性
        print("子类Student构造方法执行")

    def study(self):
        # 不推荐:通过改写名称访问父类私有属性
        print(f"{self.name} 学习,年龄:{self._Person__age},分数:{self.score}")

# 子类2:Teacher(继承Person,新增title属性)
class Teacher(Person):
    def __init__(self, name, age, title):
        super().__init__(name, age)
        self.title = title  # 子类特有属性
        print("子类Teacher构造方法执行")

    def teach(self):
        print(f"{self.name} 老师,教授 {self.title}")

# 测试
s = Student("张三", 18, 90)
s.eat()   # 复用父类方法
s.study() # 子类特有方法

t = Teacher("王五", 28, "Python编程")
t.eat()   # 复用父类方法
t.teach() # 子类特有方法

5.2.2 多继承

多继承允许子类同时继承多个父类的属性和方法,核心规则:

  1. 属性 / 方法重名时,按 “从左到右” 顺序覆盖;
  2. 方法查找时,先查子类,再按继承顺序查父类;
  3. 常用 “混入类(Mixin)” 模式:将单一功能封装为 Mixin 类,通过多继承组合功能。

多继承示例(Mixin 模式)

"""
Mixin模式核心:
1. 每个Mixin类封装单一功能(如飞行、游泳),不单独实例化;
2. 主类通过多继承组合多个Mixin的功能,实现“能力复用”;
"""
# Mixin类1:飞行能力
class Flyable:
    def fly(self):
        print(f"{self.name} 正在飞行")

# Mixin类2:游泳能力
class Swimming:
    def swim(self):
        print(f"{self.name} 正在游泳")

# Mixin类3:行走能力
class Walkable:
    def walk(self):
        print(f"{self.name} 正在行走")

# 基础类:鸟类(提供核心属性)
class Bird:
    def __init__(self, name):
        self.name = name  # 所有Mixin类依赖的核心属性

    def make_sound(self):
        print(f"{self.name} 在鸣叫")

# 子类:鸭子(组合基础类+多个Mixin)
class Duck(Bird, Flyable, Swimming, Walkable):
    def __init__(self, name):
        super().__init__(name)  # 初始化基础类属性

    # 子类特有方法
    def catch_fish(self):
        print(f"{self.name} 正在抓鱼")

# 测试:鸭子拥有所有组合能力
duck = Duck("小黄鸭")
duck.fly()         # 来自Flyable
duck.swim()        # 来自Swimming
duck.walk()        # 来自Walkable
duck.make_sound()  # 来自Bird
duck.catch_fish()  # 子类特有

Java 对比

  • Java 不支持多继承,需通过接口(Interface)实现类似 Mixin 的功能;
  • Python 多继承更灵活,但需注意 “钻石继承” 问题(通过 MRO 解决)。

5.2.3 子类复用 / 重写父类方法

子类可通过两种方式复用父类方法,也可重写父类方法实现自定义逻辑:

  1. 复用:super().方法名()父类名.方法名(self, 参数)
  2. 重写:子类定义与父类同名的方法,覆盖原有逻辑。

示例代码

"""
子类复用/重写父类方法
核心:super() 自动适配继承关系,推荐优先使用
"""
class Person:
    def __init__(self, name):
        self.name = name
        print("父类构造方法执行")

    def __str__(self):
        return f"{self.name} 是一个人"

    def eat(self):
        print(f"{self.name} 吃东西....")

# 子类:Student
class Student(Person):
    # 重写父类构造方法
    def __init__(self, name, score):
        super().__init__(name)  # 复用父类构造方法
        self.score = score
        print("子类构造方法执行")

    # 复用父类方法
    def show(self):
        # 方式1:super()(推荐,适配多继承)
        super().eat()
        # 方式2:父类名.方法名(需手动传self)
        # Person.eat(self)

    # 重写父类__str__方法
    def __str__(self):
        # 复用父类__str__结果 + 子类扩展
        return super().__str__() + f",也是学生,分数:{self.score}"

# 测试
s = Student("张三", 90)
s.show()       # 复用父类eat方法
print(s)       # 调用重写后的__str__:输出“张三是一个人,也是学生,分数:90”

5.2.4 方法解析顺序(MRO)

MRO(Method Resolution Order)是 Python 解决多继承方法查找的规则,核心是 “深度优先 + 左到右”,可通过 类名.__mro__类名.mro() 查看继承链。

核心规则

  1. 方法查找时,按 MRO 列表顺序依次查找,找到即执行;
  2. super() 并非仅调用 “直接父类”,而是按 MRO 列表调用下一个类的方法;
  3. 多继承构造方法需通过 super() 传递参数,让 MRO 自动处理初始化逻辑。

示例代码

"""
MRO核心演示:
1. 查看类的继承链;
2. super() 按MRO顺序调用父类方法;
"""
# 顶层父类:Animal
class Animal:
    def __init__(self, name, **kwargs):
        self.name = name
        print("Animal 构造方法执行")

    def eat(self):
        print("Animal 吃东西...")

# 父类1:Fish(继承Animal)
class Fish(Animal):
    def __init__(self, name=None, **kwargs):
        print("Fish 构造方法执行")
        super().__init__(name, **kwargs)  # 传递剩余参数给MRO下一个类

    def eat(self):
        print("Fish 吃东西...")
        super().eat()  # 调用MRO下一个类的eat方法

# 父类2:Person(继承Animal)
class Person(Animal):
    def __init__(self, name=None, gender=None, **kwargs):
        print("Person 构造方法执行")
        self.gender = gender
        super().__init__(name, **kwargs)

    def eat(self):
        print("Person 吃东西...")
        super().eat()

# 子类:美人鱼(多继承 Fish, Person)
class Mermaids(Fish, Person):
    def __init__(self, name, gender, age):
        print("Mermaids 子类构造方法执行")
        # 仅调用一次super(),MRO自动处理所有父类初始化
        super().__init__(name=name, gender=gender, age=age)

    def eat(self):
        print("Mermaids 吃东西...")
        super().eat()  # 按MRO调用Fish的eat

# 1. 查看MRO列表
print("Mermaids MRO:", Mermaids.__mro__)
# 输出:(Mermaids, Fish, Person, Animal, object)

# 2. 实例化测试(按MRO顺序执行构造方法)
m = Mermaids("小海燕", "女", 500)
# 输出顺序:Mermaids → Fish → Person → Animal

# 3. 调用eat方法(按MRO顺序执行)
m.eat()
# 输出:Mermaids吃东西... → Fish吃东西... → Person吃东西... → Animal吃东西...

5.3 多态

多态指 “同一方法在不同子类中呈现不同行为”,核心是 “父类定义方法,子类重写实现”,无需关注具体子类类型,仅需调用统一接口,大幅提升代码灵活性。

5.3.1 多态示例

"""
多态核心:
1. 父类定义统一接口(如go方法);
2. 子类重写接口实现不同逻辑;
3. 外部调用时,仅需传入子类实例,自动执行对应逻辑;
"""
# 父类:定义统一接口
class Animal:
    def go(self):
        """所有动物的移动行为(父类统一接口)"""
        pass

# 子类1:狗(重写go方法)
class Dog(Animal):
    def go(self):
        print("狗:四条腿跑")

# 子类2:鱼(重写go方法)
class Fish(Animal):
    def go(self):
        print("鱼:在水里游")

# 子类3:鸟(重写go方法)
class Bird(Animal):
    def go(self):
        print("鸟:张开翅膀飞")

# 通用调用函数(无需关注具体子类)
def move(animal):
    """接收Animal子类实例,调用统一接口"""
    animal.go()

# 测试:传入不同子类,执行不同行为
dog = Dog()
fish = Fish()
bird = Bird()

move(dog)   # 输出:狗:四条腿跑
move(fish)  # 输出:鱼:在水里游
move(bird)  # 输出:鸟:张开翅膀飞

Java 对比

  • Java 多态需基于 “继承 / 接口”,且方法需为非静态 / 非私有;
  • Python 是动态类型语言,无需显式继承,只要实例有对应方法即可(“鸭子类型”),更灵活。

5.4 总结

  1. 封装:通过私有化隐藏细节,@property 简化私有属性访问,核心是 “数据安全 + 接口简化”;
  2. 继承:单继承复用父类逻辑,多继承通过 Mixin 模式组合功能,MRO 解决多继承方法查找问题;
  3. 多态:父类定义统一接口,子类重写实现不同行为,核心是 “接口统一,实现多样”。

6. 综合小案例:JSON 通讯录

实现一个可持久化的通讯录管理器,数据存储在 contacts.json 文件中,支持联系人的新增、查询、列表展示,且通过 JSON 完成数据的保存与加载,结合类封装、文件操作、异常处理三大核心知识点。

业务流程:

image-20260106160855762

Java 对比

  • Python 用 list[dict] 存储联系人,Java 中更常用 List<Contact>(自定义 Contact 类建模);
  • Python 的 try/except 对应 Java 的 try/catchwith open 对应 Java 的 try-with-resources(自动关闭流);
  • JSON 序列化 / 反序列化逻辑一致,Python 用 json 模块,Java 常用 Jackson/Gson 库。

6.1 数据格式设计

contacts.json 文件采用 JSON 列表格式存储,每个元素是一个联系人字典(包含姓名、手机号),结构清晰且易读:

[
  {"name": "Tom", "phone": "13800000000"},
  {"name": "Jerry", "phone": "13900000000"}
]

6.2 核心类封装(ContactBook)

将通讯录的核心逻辑封装为 ContactBook 类,职责单一、接口清晰,便于维护和扩展:

import json

class ContactBook:
    """
    通讯录管理器类
    核心功能:加载/保存数据、新增/查询/列出/删除联系人
    """
    def __init__(self, file_path="contacts.json"):
        """
        初始化通讯录
        :param file_path: 数据文件路径,默认 contacts.json
        """
        self.file_path = file_path  # 数据文件路径
        self.contacts = []          # 内存中的联系人列表(list[dict])

    def load(self):
        """
        从 JSON 文件加载联系人数据
        异常处理:文件不存在/JSON 格式错误时,初始化空列表
        :return: 联系人列表(list[dict])
        """
        try:
            with open(self.file_path, "r", encoding="utf-8") as f:
                self.contacts = json.load(f)  # 反序列化 JSON 到列表
        except (FileNotFoundError, json.JSONDecodeError):
            # FileNotFoundError:首次运行无文件;JSONDecodeError:文件内容损坏/空文件
            self.contacts = []
        return self.contacts

    def save(self):
        """
        将内存中的联系人数据保存到 JSON 文件
        注意:ensure_ascii=False 支持中文,indent=2 格式化输出
        """
        with open(self.file_path, "w", encoding="utf-8") as f:
            json.dump(self.contacts, f, ensure_ascii=False, indent=2)

    def _validate_phone(self, phone):
        """
        私有方法:校验手机号格式(11位纯数字)
        :param phone: 待校验的手机号
        :raise ValueError: 格式不合法时抛出异常
        """
        if not (len(phone) == 11 and phone.isdigit()):
            raise ValueError("手机号必须是11位纯数字")

    def add(self, name, phone):
        """
        新增联系人(带参数校验)
        :param name: 联系人姓名(非空)
        :param phone: 联系人手机号(11位纯数字)
        :raise ValueError: 姓名/手机号为空、联系人已存在、手机号格式错误
        """
        # 基础非空校验
        if not name.strip():
            raise ValueError("联系人姓名不能为空")
        if not phone.strip():
            raise ValueError("联系人手机号不能为空")
        
        # 手机号格式校验
        self._validate_phone(phone.strip())
        
        # 重复校验(大小写不敏感)
        if self.find(name):
            raise ValueError(f"联系人【{name}】已存在,无法重复添加")
        
        # 新增联系人(去除首尾空格,保证数据整洁)
        self.contacts.append({
            "name": name.strip(),
            "phone": phone.strip()
        })

    def find(self, name):
        """
        查询联系人(大小写不敏感)
        :param name: 要查询的姓名(支持模糊匹配,如 tom → Tom)
        :return: 找到则返回联系人字典,未找到返回 None
        """
        target_name = name.strip().lower()  # 统一转为小写,实现大小写不敏感
        for contact in self.contacts:
            if contact["name"].lower() == target_name:
                return contact
        return None

    def remove(self, name):
        """
        删除联系人(大小写不敏感)
        :param name: 要删除的姓名
        :raise ValueError: 联系人不存在时抛出异常
        """
        contact = self.find(name)
        if not contact:
            raise ValueError(f"联系人【{name}】不存在,无法删除")
        self.contacts.remove(contact)

    def all(self):
        """
        获取所有联系人(返回列表副本,避免外部修改内部数据)
        :return: 联系人列表副本(list[dict])
        """
        return list(self.contacts)

6.3 模块拆分与交互入口

将代码拆分为两个模块,符合 “单一职责” 原则,便于管理和复用:

6.3.1 模块 1:contact_book.py(核心类)

将上述 ContactBook 类完整保存到 contact_book.py 文件中,作为通讯录核心模块。

6.3.2 模块 2:main.py(交互入口)

"""
通讯录交互入口
依赖 contact_book.py 中的 ContactBook 类
"""
from contact_book import ContactBook

def main():
    """主函数:演示通讯录基本操作"""
    # 1. 初始化通讯录并加载数据
    book = ContactBook("contacts.json")
    book.load()
    print("=== 初始化通讯录,加载已有数据 ===")
    print(f"当前联系人列表:{book.all()}\n")

    try:
        # 2. 新增联系人(带校验)
        book.add("Tom", "13800000000")
        book.add("Jerry", "13900000000")
        book.add("张三", "13712345678")  # 测试中文
        print("=== 新增联系人后 ===")
        print(f"当前联系人列表:{book.all()}\n")

        # 3. 查询联系人(大小写不敏感)
        print("=== 查询联系人 ===")
        print(f"查询 Tom:{book.find('tom')}")       # 小写匹配
        print(f"查询 李四:{book.find('李四')}\n")   # 未找到返回 None

        # 4. 删除联系人
        book.remove("Jerry")
        print("=== 删除 Jerry 后 ===")
        print(f"当前联系人列表:{book.all()}\n")

        # 5. 保存修改到文件
        book.save()
        print("=== 所有修改已保存到 contacts.json ===")

    except ValueError as e:
        # 捕获参数校验/业务逻辑异常,友好提示
        print(f"操作失败:{e}")

if __name__ == "__main__":
    main()

6.4 运行效果示例

=== 初始化通讯录,加载已有数据 ===
当前联系人列表:[]

=== 新增联系人后 ===
当前联系人列表:[{'name': 'Tom', 'phone': '13800000000'}, {'name': 'Jerry', 'phone': '13900000000'}, {'name': '张三', 'phone': '13712345678'}]

=== 查询联系人 ===
查询 Tom:{'name': 'Tom', 'phone': '13800000000'}
查询 李四:None

=== 删除 Jerry 后 ===
当前联系人列表:[{'name': 'Tom', 'phone': '13800000000'}, {'name': '张三', 'phone': '13712345678'}]

=== 所有修改已保存到 contacts.json ===

继续阅读

全部归档
01_python环境搭建&基础语法
01_python环境搭建&基础语法

Python的基础知识涵盖其起源、和设计理念,,两者均源自英国龟叔(吉范恩罗肌罗兹)的的的的兴趣和需求。Python设计哲学推崇“优雅、" 明确)和简单" 的原则,减少代码复杂度。" Python与) 优于CC 未编译) 和" 解释执行") 性能," 专注于快速增长的的" 脚本开发") 和" 科学计算") 。" 对比) Java ) ,Python') 其动态类型系统 和' 解释) 执行方式," 使Java的的静态类型系统" 和" 编译) 执行更。 " Python库丰富) ," 非常适合 ") 数据处理) ," 机器学习) ," 科学计算")" 。" 它的" 模块化设计") 使得功能可以。", " 扩展性强)," 且" 代码" 可行") " ,方便共享。" 版本") 与也表示") 了功能调试) ," 以及") 跨平台") 兼容性。" 操作") Python") 可以) 结合") C** 和"` Cannels库") ,进行进行、。"

评论