一、面向对象
(一)基本概念
(1)面向对象编程 —— Object Oriented Programming
简写 OOP
(2)面向对象三大特性
- 封装 根据 职责 将 属性 和 方法封装 到一个抽象的 类 中
- 定义类的准则
- 封装 是面向对象编程的一大特点
- 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
- 外界 使用 类 创建 对象,然后 让对象调用方法
- 对象方法的细节 都被 封装 在 类的内部
- 继承实现代码的重用,相同的代码不需要重复的编写
- 设计类的技巧
- 子类针对自己特有的需求,编写特定的代码
- 多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
- 多态 可以 增加代码的灵活度
- 以 继承 和 重写父类方法 为前提
- 是调用方法的技巧,不会影响到类的内部设计
(二)面相过程 和 面相对象
(1)面相过程 —— 怎么做?
- 把完成某一个需求的
所有步骤
从头到尾
逐步实现 - 根据开发需求,将某些 功能独立 的代码 封装 成一个个 函数
- 最后完成的代码,顺序地调用 不同的函数
特点
- 注重 步骤与过程,不注重职责分工
- 若需求复杂,则代码复杂
- 开发复杂项目,没有固定的套路,开发难度很大!
(2)面向对象 —— 谁来做?
根据 职责 在 一个对象中 封装 多个方法
- 在完成某一个需求前,首先确定 职责 —— 要做的事情(方法)
- 根据 职责 确定 对象,在 对象 内部封装 方法(多个)
- 最后完成的代码,顺序地让 不同的对象 调用 不同的方法
特点
- 注重 对象和职责,不同的对象承担不同的职责
- 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
二、类和对象
(一)类和对象的概念
(1)类
- 类 是对一群具有 相同特征 或者 行为 的事物的统称,抽象的,不能直接使用
- 特征 被称为 属性
- 行为 被称为 方法
- 类 就相当于 模板, 负责创建对象的
(2)对象
- 对象 是 由类创建出来的一个具体存在,可以直接使用
- 由 哪一个类 创建出来的 对象,就拥有在 哪一个类 中定义的:
- 属性
- 方法
开发中,应该 先有类,再有对象
- 在 对象的方法内部,是可以 直接访问对象的属性 的
- 同一个类 创建的 多个对象 之间,属性 互不干扰!
(二)类和对象的关系
- 类是模板,对象 是根据 类 创建出来的, 先有类,再有对象
- 类 只有一个,而 对象 可以有多个
- 不同的对象 之间 属性 可能会各不相同
- 类 中定义了什么 属性和方法,对象 中就有什么属性和方法,不可能多,也不可能少
(三)类的设计
设计一个类,需满足以下三个要素:
- 类名 满足大驼峰命名法
- 属性 :这类事物具有什么样的特征
- 方法 :这类事物具有什么样的行为
大驼峰命名法
- 每个单词首字母大写
- 单词之间没有下划线
(四)类的属性和方法的确定
- 对 对象的特征描述,可定义为 属性
- 对象具有的行为(动词),可定义为 方法
需求中未涉及的属性或者方法在设计类时,不需要考虑
三、类属性和类方法
(一)类的结构
(1)术语 —— 实例
- 使用面相对象开发,第 1 步 是设计 类
- 使用 类名() 创建对象,创建对象 有两步:
- 在内存中为对象 分配空间
- 调用初始化方法
__init__
为 对象初始化
- 对象创建后,内存 中就有了一个对象的 实实在在 的存在 —— 实例
会把:
- 创建出来的 对象 叫做 类 的 实例
- 创建对象的 动作 叫做 实例化
- 对象的属性 叫做 实例属性
- 对象调用的方法 叫做 实例方法
在程序执行时:
- 对象各自拥有自己的 实例属性
- 调用对象方法,可以通过
self.
- 访问自己的属性
- 调用自己的方法
结论
- 每一个对象 都有自己 独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法内部
(2)类是一个特殊的对象
Python
中 一切皆对象:
class AAA:
定义的类属于 类对象obj1 = AAA()
属于 实例对象
- 在程序运行时,类 同样 会被加载到内存
- 在
Python
中,类 是一个特殊的对象 —— 类对象 - 在程序运行时,类对象 在内存中 只有一份,使用 一个类 可以创建出 很多个对象实例
- 除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法
- 类属性
- 类方法
- 通过 类名. 的方式可以 访问类的属性 或者 调用类的方法
(二)类属性和实例属性
(1)概念和使用
- 类属性 就是给 类对象 中定义的 属性
- 通常用来记录 与这个类相关 的特征
- 类属性 不会用于记录 具体对象的特征
(2)属性的获取机制
- 在
Python
中 属性的获取 存在一个 向上查找机制 - 因此,要访问类属性有两种方式:
- 类名.类属性
- 对象.类属性 (不推荐)
注:
- 如果使用
对象.类属性 = 值
赋值语句,只会 给对象添加一个属性,而不会影响到 类属性的值
(三)类方法和静态方法
(1)类方法
- 类属性 就是针对 类对象 定义的属性
- 使用 赋值语句 在
class
关键字下方可以定义 类属性 - 类属性 用于记录 与这个类相关 的特征
- 类方法 就是针对 类对象 定义的方法
- 在 类方法 内部可以直接访问 类属性 或者调用其他的 类方法
语法如下
@classmethod
def 类方法名(cls):
pass
- 类方法需要用 修饰器
@classmethod
来标识,告诉解释器这是一个类方法 - 类方法的 第一个参数 应该是
cls
- 由 哪一个类 调用的方法,方法内的
cls
就是 哪一个类的引用 - 这个参数和 实例方法 的第一个参数是
self
类似 - 提示 使用其他名称也可以,不过习惯使用
cls
- 通过 类名. 调用 类方法,调用方法时,不需要传递
cls
参数 - 在方法内部
- 可以通过
cls.
访问类的属性 - 也可以通过
cls.
调用其他的类方法
在类方法内部,可以直接使用
cls
访问 类属性 或者 调用类方法
(2)静态方法
- 在开发时,如果需要在 类 中封装一个方法,这个方法:
- 既 不需要 访问 实例属性 或者调用 实例方法
- 也 不需要 访问 类属性 或者调用 类方法
- 这个时候,可以把这个方法封装成一个 静态方法
语法
@staticmethod
def 静态方法名():
pass
- 静态方法 需要用 修饰器
@staticmethod
来标识,告诉解释器这是一个静态方法 - 通过 类名. 调用 静态方法
四、面相对象基础语法
(一) dir
内置函数
在 Python
中可以使用以下两个方法验证:
- 在 标识符 / 数据 后输入一个
.
,然后按TAB
键,iPython
会提示该对象能够调用的 方法列表 - 使用内置函数
dir
传入 标识符 / 数据,可以查看对象内的 所有属性及方法
提示 __方法名__
格式的方法是 Python
提供的 内置方法 / 属性,稍后会给大家介绍一些常用的 内置方法 / 属性
方法名 | 类型 | 作用 |
| 方法 | 创建对象时,会被 自动 调用 |
| 方法 | 对象被初始化时,会被 自动 调用 |
| 方法 | 对象被从内存中销毁前,会被 自动 调用 |
| 方法 | 返回对象的描述信息, |
(二)定义简单的类(只包含方法)
面向对象 是 更大 的 封装,在 一个类中 封装 多个方法,这样 通过这个类创建出来的对象,就可以直接调用这些方法了!
(1)定义只包含方法的类
- 在
Python
中要定义一个只包含方法的类,语法格式如下:
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
- 方法 的定义格式和之前学习过的函数 几乎一样
- 区别在于第一个参数必须是
self
注意:类名 的 命名规则 要符合 大驼峰命名法
(2)创建对象
- 当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:
对象变量 = 类名()
(3)引用概念的强调
在面向对象开发中,引用的概念是同样适用的!
- 在
Python
中使用类 创建对象之后,变量中 记录的是 对象在内存中的地址 - 使用
print
输出 对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)
提示:在计算机中,通常使用 十六进制 表示 内存地址
- 十进制 和 十六进制 都是用来表达数字的,只是表示的方式不一样
- 十进制 和 十六进制 的数字之间可以来回转换
-
%d
可以以 10 进制 输出数字 -
%x
可以以 16 进制 输出数字
(三)方法中的 self
参数
(1)给对象增加属性
- 在
Python
中,要 给对象设置属性,非常的容易,但是不推荐使用
- 因为:对象属性的封装应该封装在类的内部
- 只需要在 类的外部的代码 中直接通过
.
设置一个属性即可
注意:这种方式虽然简单,但不推荐使用!
(2)使用 self
在方法
由 哪一个对象 调用的方法,方法内的
self
就是 哪一个对象的引用
- 在类封装的方法内部,
self
就表示 当前调用方法的对象自己 - 调用方法时,不需要传递
self
参数 - 在方法内部
- 可以通过
self.
访问对象的属性 - 也可以通过
self.
调用其他的对象方法
- 在 类的外部,通过
变量名.
访问对象的 属性和方法 - 在 类封装的方法中,通过
self.
访问对象的 属性和方法
(四)初始化方法
(1)在类的外部给对象增加属性
提示
- 在开发中,不推荐在 类的外部 给对象增加属性
- 如果在运行时,没有找到属性,程序会报错
- 对象应该包含有哪些属性,应该 封装在类的内部
(2)初始化方法
- 当使用
类名()
创建对象时,会 自动 执行以下操作:
- 为对象在内存中 分配空间 —— 创建对象
- 为对象的属性 设置初始值 —— 初始化方法(
init
)
- 这个 初始化方法 就是
__init__
方法,__init__
是对象的内置方法
__init__
方法是 专门 用来定义一个类 具有哪些属性的方法!
(3)在初始化方法内部定义属性
- 在
__init__
方法内部使用self.属性名 = 属性的初始值
就可以 定义属性 - 定义属性之后,再使用该类创建的对象,都会拥有该属性
(4)改造初始化方法 —— 初始化的同时设置初始值
- 在开发中,若希望在 创建对象的同时,就设置对象的属性,可以对
__init__
方法进行 改造
- 把希望设置的属性值,定义成
__init__
方法的参数 - 在方法内部使用
self.属性 = 形参
接收外部传递的参数 - 在创建对象时,使用
类名(属性1, 属性2...)
调用
(四)内置方法和属性
方法名 | 类型 | 作用 |
| 方法 | 对象被从内存中销毁前,会被 自动 调用 |
| 方法 | 返回对象的描述信息, |
(1) __del__
方法
Python
中- 当使用
类名()
创建对象时,为对象 分配完空间后,自动 调用__init__
方法 - 当一个 对象被从内存中销毁 前,会 自动 调用
__del__
方法 - 应用场景
-
__init__
改造初始化方法,可以让创建对象更加灵活 -
__del__
如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__
方法
- 生命周期
- 一个对象从调用
类名()
创建,生命周期开始 - 一个对象的
__del__
方法一旦被调用,生命周期结束 - 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
(2)__str__
方法
- 在
Python
中,使用print
输出 对象变量,默认情况下,会输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)
注意:
__str__
方法必须返回一个字符串
五、面向对象中的私有属性和私有方法
(一)应用场景及定义方式
(1)应用场景
- 对象 的 某些属性或方法 可能只希望 在对象的内部被使用,而 不希望在外部被访问到
- 私有属性 就是 对象 不希望公开的 属性
- 私有方法 就是 对象 不希望公开的 方法
(2)定义方式
- 在 定义属性或方法时,在 属性名或者方法名前 增加 两个下划线
(二)伪私有属性和私有方法
Python
中,并没有 真正意义 的 私有
- 在给 属性、方法 命名时,实际是对 名称 做了一些特殊处理,使得外界无法访问到
- 处理方式:在 名称 前面加上
_类名
=>_类名__名称
私有属性,外部不能直接访问到
私有方法,外部不能直接调用
六、单例
(一)单例设计模式
- 设计模式
- 设计模式 是 前人工作的总结和提炼,都是针对 某一特定问题 的成熟的解决方案
- 使用 设计模式 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
- 单例设计模式
- 目的 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例
- 每一次执行
类名()
返回的对象,内存地址是相同的
(二)__new__
方法
- 使用 类名() 创建对象时,
Python
的解释器 首先 会 调用__new__
方法为对象 分配空间 __new__
是一个 由object
基类提供的 内置的静态方法,主要作用:
- 在内存中为对象 分配空间
- 返回 对象的引用
-
Python
的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给__init__
方法
重写
__new__
方法 的代码非常固定!
- 重写
__new__
方法 一定要return super().__new__(cls)
- 否则 Python 的解释器 得不到 分配了空间的 对象引用,就不会调用对象的初始化方法
- 注:
__new__
是一个静态方法,在调用时需要 主动传递cls
参数
示例代码
class 类名(object):
def __new__(cls, *args, **kwargs):
# 如果不返回任何结果,
return super().__new__(cls)
def __init__(self):
pass
py = 类名()
print(py)
(3)Python 中的单例
- 单例 —— 让 类 创建的对象,在系统中 只有唯一的一个实例
- 定义一个 类属性,初始值是
None
,用于记录 单例对象的引用 - 重写
__new__
方法 - 如果 类属性
is None
,调用父类方法分配空间,并在类属性中记录结果 - 返回 类属性 中记录的 对象引用
八、继承
(一)单继承
(1)继承的概念
让子类 拥有 父类 的所有 方法 和 属性
(2)继承的语法
class 类名(父类名):
pass
- 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
- 子类 中应该根据 职责,封装 子类特有的 属性和方法
(3) 继承的传递性
-
C
类从B
类继承,B
类又从A
类继承 - 那么
C
类就具有B
类和A
类的所有属性和方法
子类 拥有 父类 以及 父类的父类 中封装的所有 属性 和 方法
(二)方法的重写
- 子类 拥有 父类 的所有 方法 和 属性
- 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
应用场景
- 当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)
重写 父类方法有两种情况:
- 覆盖 父类的方法
- 对父类方法进行 扩展
(1) 覆盖父类的方法
在 子类中 定义一个 和父类同名的方法并且实现
重写之后,在运行时,只会调用 子类中重写的方法
(2) 对父类方法进行 扩展
- 如果在开发中,子类的方法实现 中 包含父类的方法实现
- 父类原本封装的方法实现 是 子类方法的一部分
- 就可以使用 扩展 的方式
- 在子类中 重写 父类的方法
- 在需要的位置使用
super().父类方法
来调用父类方法的执行 - 代码其他的位置针对子类的需求,编写 子类特有的代码实现
(3) super
- 在
Python
中super
是一个 特殊的类 -
super()
就是使用super
类创建出来的对象 - 最常 使用的场景就是在 重写父类方法时,调用 在父类中封装的方法实现
(4)调用父类方法的另外一种方式
在
Python 2.x
时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
- 这种方式,目前在
Python 3.x
还支持这种方式 - 不推荐使用,因为一旦 父类发生变化,方法调用位置的 类名 同样需要修改
注:
- 在开发时,
父类名
和super()
两种方式不要混用 - 如果使用 当前子类名 调用方法,会形成递归调用,出现死循环
(三)父类的 私有属性 和 私有方法
- 子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法
- 子类对象 可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法
(四)多继承
概念
- 子类 可以拥有 多个父类,并且具有 所有父类 的 属性 和 方法
语法
class 子类名(父类名1, 父类名2...)
pass
多继承的使用注意事项
若 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承
Python 中的 MRO —— 方法搜索顺序
Python
中针对 类 提供了一个 内置属性__mro__
可以查看 方法 搜索顺序- MRO 是
method resolution order
,主要用于 在多继承时判断 方法、属性 的调用 路径 - 在搜索方法时,是按照
__mro__
的输出结果 从左至右 的顺序查找的 - 如果在当前类中 找到方法,就直接执行,不再搜索
- 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
- 如果找到最后一个类,还没有找到方法,程序报错
(五)新式类与旧式(经典)类
object
是Python
为所有对象提供的 基类,提供有一些内置的属性和方法,可以使用dir
函数查看
- 新式类:以
object
为基类的类,推荐使用 - 经典类:不以
object
为基类的类,不推荐使用 - 在
Python 3.x
中定义类时,如果没有指定父类,会 默认使用object
作为该类的 基类 ——Python 3.x
中定义的类都是 新式类 - 在
Python 2.x
中定义类时,如果没有指定父类,则不会以object
作为 基类
新式类 和 经典类 在多继承时 —— 会影响到方法的搜索顺序
为了保证编写的代码能够同时在 Python 2.x
和 Python 3.x
运行!
在定义类时,若没有父类,则统一继承自 object
class 类名(object):
pass
九、模块和包
(一)模块
(1)模块的概念
模块是 Python 程序架构的一个核心概念
- 每一个以扩展名
py
结尾的Python
源代码文件都是一个 模块 - 模块名 也是一个 标识符,需要符合标识符的命名规则
- 在模块中定义的 全局变量 、函数、类 都是提供给外界直接使用的 工具
- 模块 就好比是 工具包,要想使用这个工具包中的工具,就需要先 导入 这个模块
(2)模块的两种导入方式
(1)import 导入
import 模块名1, 模块名2
在导入模块时,每个导入应该独占一行
import 模块名1
import 模块名2
- 导入之后
- 通过
模块名.
使用 模块提供的工具 —— 全局变量、函数、类
使用 as
指定模块的别名
如果模块的名字太长,可以使用
as
指定模块的名称,以方便在代码中的使用
import 模块名1 as 模块别名
注:模块别名 应符合 大驼峰命名法
(2)from…import 导入
- 如果希望 从某一个模块 中,导入 部分 工具,就可以使用
from ... import
的方式 -
import 模块名
是 一次性 把模块中 所有工具全部导入,并且通过 模块名/别名 访问
# 从 模块 导入 某一个工具
from 模块名1 import 工具名
- 导入之后
- 不需要 通过
模块名.
- 可以直接使用 模块提供的工具 —— 全局变量、函数、类
注:
若 两个模块,存在 同名的函数,那么 后导入模块的函数,会 覆盖掉先导入的函数
- 开发时
import
代码应该统一写在 代码的顶部,更容易及时发现冲突 - 一旦发现冲突,可以使用
as
关键字 给其中一个工具起一个别名
(3)from…import *
# 从 模块 导入 所有工具
from 模块名1 import *
注意
这种方式不推荐使用,因为函数重名并没有任何的提示,出现问题不好排查
(3)模块的搜索顺序[扩展]
Python
的解释器在 导入模块 时,会:
- 搜索 当前目录 指定模块名的文件,如果有就直接导入
- 如果没有,再搜索 系统目录
在开发时,给文件起名,不要和 系统的模块文件 重名
Python
中每一个模块都有一个内置属性 __file__
可以 查看模块 的 完整路径
示例
import random
# 生成一个 0~10 的数字
rand = random.randint(0, 10)
print(rand)
注意:若当前目录下,存在一个
random.py
的文件,程序就无法正常执行了!
- 这个时候,
Python
的解释器会 加载当前目录 下的random.py
而不会加载 系统的random
模块
(4)原则 —— 每一个文件都应该是可以被导入的
- 一个 独立的
Python
文件 就是一个 模块 - 在导入文件时,文件中 所有没有任何缩进的代码 都会被执行一遍!
实际开发场景
- 在实际开发中,每一个模块都是独立开发的,大多都有专人负责
- 开发人员 通常会在 模块下方增加一些测试代码
- 仅在模块内使用,而被导入到其他文件中不需要执行
(5)__name__
属性
__name__
属性可以做到,测试模块的代码 只在测试情况下被运行,而在 被导入时不会被执行!
-
__name__
是Python
的一个内置属性,记录着一个 字符串 - 如果 是被其他文件导入的,
__name__
就是 模块名 - 如果 是当前执行的程序
__name__
是__main__
在很多 Python
文件中都会看到以下格式的代码:
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码的最下方
def main():
# ...
pass
# 根据 __name__ 判断是否执行下方代码
if __name__ == "__main__":
main()
(二)包(Package)
概念
- 包 是一个 包含多个模块 的 特殊目录
- 目录下有一个 特殊的文件
__init__.py
- 包名的 命名方式 和变量名一致,小写字母 +
_
好处
- 使用
import 包名
可以一次性导入 包 中 所有的模块
__init__.py
- 要在外界使用 包 中的模块,需要在
__init__.py
中指定 对外界提供的模块列表
# 从 当前目录 导入 模块列表
from . import send_message
from . import receive_message
十、文件
(一)文件的概念
(1)文件的概念和作用
- 计算机的 文件,就是存储在某种 长期储存设备 上的一段 数据
- 长期存储设备包括:硬盘、U 盘、移动硬盘、光盘…
(2)文件的作用
将数据长期保存下来,在需要的时候使用
(3)文件的存储方式
- 在计算机中,文件是以 二进制 的方式保存在磁盘上的
(4)文本文件和二进制文件
- 文本文件
- 可以使用 文本编辑软件 查看
- 本质上还是二进制文件
- 二进制文件
- 保存的内容 不是给人直接阅读的,而是 提供给其他软件使用的
- 例如:图片文件、音频文件、视频文件等等
- 二进制文件不能使用 文本编辑软件 查看
(二)文件的基本操作
(1)操作文件的套路
计算机 中操作文件,三个步骤:
- 打开文件
- 读、写文件
- 读 将文件内容读入内存
- 写 将内存内容写入文件
- 关闭文件
(2)操作文件的函数/方法
- 在
Python
中要操作文件的 1 个函数和 3 个方法
函数/方法 | 说明 |
open | 打开文件,并且返回文件操作对象 |
read | 将文件内容读取到内存 |
write | 将指定内容写入文件 |
close | 关闭文件 |
-
open
函数负责打开文件,并且返回文件对象 -
read
/write
/close
三个方法都需要通过 文件对象 来调用
(3)read 方法 —— 读取文件
open
函数的第一个参数是要打开的文件名(文件名区分大小写)
- 如果文件 存在,返回 文件操作对象
- 如果文件 不存在,会 抛出异常
-
read
方法可以一次性 读入 并 返回 文件的 所有内容 close
方法负责 关闭文件
- 如果 忘记关闭文件,会造成系统资源消耗,而且会影响到后续对文件的访问
- 注意:
read
方法执行后,会把 文件指针 移动到 文件的末尾
# 1. 打开 - 文件名需要注意大小写
file = open("README")
# 2. 读取
text = file.read()
print(text)
# 3. 关闭
file.close()
注:
- 在开发中,通常会先编写 打开 和 关闭 的代码,再编写中间针对文件的 读/写 操作!
(4)文件指针
- 文件指针 标记 从哪个位置开始读取数据
- 第一次打开 文件时,通常 文件指针会指向文件的开始位置
- 当执行了
read
方法后,文件指针 会移动到 读取内容的末尾
- 默认情况下会移动到 文件末尾
(5)打开文件的方式
-
open
函数默认以 只读方式 打开文件,并且返回文件对象
语法如下:
f = open("文件名", "访问方式")
访问方式 | 说明 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。若文件不存在,抛出异常 |
w | 以只写方式打开文件。若文件存在会被覆盖。若文件不存在,创建新文件 |
a | 以追加方式打开文件。若该文件已存在,文件指针将会放在文件的结尾。若文件不存在,创建新文件进行写入 |
r+ | 以读写方式打开文件。文件的指针将会放在文件的开头。若文件不存在,抛出异常 |
w+ | 以读写方式打开文件。若文件存在会被覆盖。若文件不存在,创建新文件 |
a+ | 以读写方式打开文件。若该文件已存在,文件指针将会放在文件的结尾。若文件不存在,创建新文件进行写入 |
注:
- 频繁的移动文件指针,会影响文件的读写效率,开发中更多的时候会以 只读、只写 的方式来操作文件
(6)按行读取文件内容
-
read
方法默认会把文件的 所有内容 一次性读取到内存 - 如果文件太大,对内存的占用会非常严重
readline
方法
-
readline
方法可以一次读取一行内容 - 方法执行后,会把 文件指针 移动到下一行,准备再次读取
读取大文件的正确姿势
# 打开文件
file = open("README")
while True:
# 读取一行内容
text = file.readline()
# 判断是否读到内容
if not text:
break
# 每读取一行的末尾已经有了一个 `\n`
print(text, end="")
# 关闭文件
file.close()
(三)文件/目录的常用管理操作
- 在 终端 / 文件浏览器、 中可以执行常规的 文件 / 目录 管理操作,例如:
- 创建、重命名、删除、改变路径、查看目录内容、……
- 在
Python
中,如果希望通过程序实现上述功能,需要导入os
模块
文件操作
方法名 | 说明 | 示例 |
rename | 重命名文件 |
|
remove | 删除文件 |
|
目录操作
方法名 | 说明 | 示例 |
listdir | 目录列表 |
|
mkdir | 创建目录 |
|
rmdir | 删除目录 |
|
getcwd | 获取当前目录 |
|
chdir | 修改工作目录 |
|
path.isdir | 判断是否是文件 |
|
提示:文件或者目录操作都支持 相对路径 和 绝对路径
(四)文本文件的编码格式
- 文本文件存储的内容是基于 字符编码 的文件,常见的编码有
ASCII
编码,UNICODE
编码等
Python 2.x 默认使用
ASCII
编码格式
Python 3.x 默认使用UTF-8
编码格式
4.1 ASCII 编码和 UNICODE 编码
ASCII
编码
- 计算机中只有
256
个ASCII
字符 - 一个
ASCII
在内存中占用 1 个字节 的空间
-
8
个0/1
的排列组合方式一共有256
种,也就是2 ** 8
UTF-8
编码格式
- 计算机中使用 1~6 个字节 来表示一个
UTF-8
字符,涵盖了 地球上几乎所有地区的文字 - 大多数汉字会使用 3 个字节 表示
-
UTF-8
是UNICODE
编码的一种编码格式
4.2 Ptyhon 2.x 中如何使用中文
Python 2.x 默认使用
ASCII
编码格式
Python 3.x 默认使用UTF-8
编码格式
- 在 Python 2.x 文件的 第一行 增加以下代码,解释器会以
utf-8
编码来处理 python 文件
# *-* coding:utf8 *-*
官方推荐使用
- 也可以使用
# coding=utf8
unicode 字符串
- 在
Python 2.x
中,即使指定了文件使用UTF-8
的编码格式,但是在遍历字符串时,仍然会 以字节为单位遍历 字符串 - 要能够 正确的遍历字符串,在定义字符串时,需要 在字符串的引号前,增加一个小写字母
u
,告诉解释器这是一个unicode
字符串(使用UTF-8
编码格式的字符串)
# *-* coding:utf8 *-*
# 在字符串前,增加一个 `u` 表示这个字符串是一个 utf8 字符串
hello_str = u"你好世界"
print(hello_str)
for c in hello_str:
print(c)
十一、异常
(一)异常的概念
- 程序在运行时,如果
Python 解释器
遇到 到一个错误,会停止程序的执行,并且提示一些错误信息,这就是 异常 - 程序停止执行并且提示错误信息 这个动作,称为:抛出(raise)异常
(二)捕获异常
(1)简单的捕获异常语法
- 在程序开发中,如果 对某些代码的执行不能确定是否正确,可以增加
try(尝试)
来 捕获异常 - 捕获异常最简单的语法格式:
try:
尝试执行的代码
except:
出现错误的处理
-
try
尝试,下方编写要尝试代码,不确定是否能够正常执行的代码 -
except
如果不是,下方编写尝试失败的代码
(2)错误类型捕获
- 在程序执行时,可能会遇到 不同类型的异常,并且需要 针对不同类型的异常,做出不同的响应,这个时候,就需要捕获错误类型了
- 语法如下:
try:
# 尝试执行的代码
pass
except 错误类型1:
# 针对错误类型1,对应的代码处理
pass
except (错误类型2, 错误类型3):
# 针对错误类型2 和 3,对应的代码处理
pass
except Exception as result:
print("未知错误 %s" % result)
- 当
Python
解释器 抛出异常 时,最后一行错误信息的第一个单词,就是错误类型
(3)捕获未知错误
- 在开发时,要预判到所有可能出现的错误,还是有一定难度的
- 如果希望程序 无论出现任何错误,都不会因为
Python
解释器 抛出异常而被终止,可以再增加一个except
语法如下:
except Exception as result:
print("未知错误 %s" % result)
(4)异常捕获完整语法
- 在实际开发中,为了能够处理复杂的异常情况,完整的异常语法如下:
提示:
- 有关完整语法的应用场景,在后续学习中,结合实际的案例会更好理解
- 现在先对这个语法结构有个印象即可
try:
# 尝试执行的代码
pass
except 错误类型1:
# 针对错误类型1,对应的代码处理
pass
except 错误类型2:
# 针对错误类型2,对应的代码处理
pass
except (错误类型3, 错误类型4):
# 针对错误类型3 和 4,对应的代码处理
pass
except Exception as result:
# 打印错误信息
print(result)
else:
# 没有异常才会执行的代码
pass
finally:
# 无论是否有异常,都会执行的代码
print("无论是否有异常,都会执行的代码")
-
else
只有在没有异常时才会执行的代码 -
finally
无论是否有异常,都会执行的代码
(三)异常的传递
- 异常的传递 —— 当 函数/方法 执行 出现异常,会 将异常传递 给 函数/方法 的 调用一方
- 如果 传递到主程序,仍然 没有异常处理,程序才会被终止
提示
- 在开发中,可以在主函数中增加 异常捕获
- 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的 异常捕获 中
- 这样就不需要在代码中,增加大量的 异常捕获,能够保证代码的整洁