单例
目标
- 单例设计模式
__new__
方法- Python 中的单例
01. 单例设计模式
-
设计模式
- 设计模式 是 前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对 某一特定问题 的成熟的解决方案
- 使用 设计模式 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
-
单例设计模式
- 目的 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例
- 每一次执行
类名()
返回的对象,内存地址是相同的
单例设计模式的应用场景
- 音乐播放 对象
- 回收站 对象
- 打印机 对象
- ……
02. __new__
方法
- 使用 类名() 创建对象时,
Python
的解释器 首先 会 调用__new__
方法为对象 分配空间 __new__
是一个 由object
基类提供的 内置的静态方法,主要作用有两个:- 1) 在内存中为对象 分配空间
- 2) 返回 对象的引用
Python
的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给__init__
方法
重写
__new__
方法 的代码非常固定!
- 重写
__new__
方法 一定要return super().__new__(cls)
- 否则 Python 的解释器 得不到 分配了空间的 对象引用,就不会调用对象的初始化方法
- 注意:
__new__
是一个静态方法,在调用时需要 主动传递cls
参数
我们之所以要学习__new__方法,就是为了改变分配空间的方法,让用这个类创建实例时,无论执行多少次,在内存中永远只会创建一个对象的实例。达到单例设计模式的目的。
class MusicPlay(object):
# 记录第一个被创建对象的引用
instance = None
def __new__(cls, *args, **kwargs):
# 1、判断是否分配了内存空间
if cls.instance is None:
# 2、调用父类方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3、返回类属性保存的对象引用
return cls.instance
play1 = MusicPlay()
print(play1)
play2 = MusicPlay()
print(play2)
输出
<__main__.MusicPlay object at 0x0390EC90>
<__main__.MusicPlay object at 0x0390EC90>
执行一次初始化工作
- 在每次使用
类名()
创建对象时,Python
的解释器都会自动调用两个方法:__new__
分配空间__init__
对象初始化
- 在上一小节对
__new__
方法改造之后,每次都会得到 第一次被创建对象的引用 - 但是:初始化方法还会被再次调用
需求
- 让 初始化动作 只被 执行一次
解决办法
- 定义一个类属性
init_flag
标记是否 执行过初始化动作,初始值为False
- 在
__init__
方法中,判断init_flag
,如果为False
就执行初始化动作 - 然后将
init_flag
设置为True
- 这样,再次 自动 调用
__init__
方法时,初始化动作就不会被再次执行 了
class MusicPlay(object):
# 记录第一个被创建对象的引用
instance = None
# 判断是否执行过初始化动作
init_flag = False
def __init__(self):
if not MusicPlay.init_flag:
MusicPlay.init_flag = True
print("初始化播放器")
else:
# print("不初始化播放器")
pass
def __new__(cls, *args, **kwargs):
# 1、判断是否分配了内存空间
if cls.instance is None:
# 2、调用父类方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3、返回类属性保存的对象引用
return cls.instance
play1 = MusicPlay()
print(play1)
play2 = MusicPlay()
print(play2)
异常处理
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("无论是否有异常,都会执行的代码")
异常的传递
- 异常的传递 —— 当 函数/方法 执行 出现异常,会 将异常传递 给 函数/方法 的 调用一方
- 如果 传递到主程序,仍然 没有异常处理,程序才会被终止
提示
- 在开发中,可以在主函数中增加 异常捕获
- 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的 异常捕获 中
- 这样就不需要在代码中,增加大量的 异常捕获,能够保证代码的整洁
def demo1():
return int(input("请输入一个整数:"))
def demo2():
return demo1()
try:
print(demo2())
except ValueError:
print("请输入正确的整数")
except Exception as result:
print("未知错误 %s" % result)
抛出异常
Python
中提供了一个Exception
异常类- 在开发时,如果满足 特定业务需求时,希望 抛出异常,可以:
- 创建 一个
Exception
的 对象 - 使用
raise
关键字 抛出 异常对象
__name__
属性
__name__
属性可以做到,测试模块的代码 只在测试情况下被运行,而在 被导入时不会被执行!
__name__
是Python
的一个内置属性,记录着一个 字符串- 如果 是被其他文件导入的,
__name__
就是 模块名 - 如果 是当前执行的程序
__name__
是__main__
操作文件的函数/方法
- 在
Python
中要操作文件需要记住 1 个函数和 3 个方法
序号 | 函数/方法 | 说明 |
---|---|---|
01 | open | 打开文件,并且返回文件操作对象 |
02 | read | 将文件内容读取到内存 |
03 | write | 将指定内容写入文件 |
04 | close | 关闭文件 |
open
函数负责打开文件,并且返回文件对象read
/write
/close
三个方法都需要通过 文件对象 来调用
注意:read
方法执行后,会把 文件指针 移动到 文件的末尾
文件指针(知道)
- 文件指针 标记 从哪个位置开始读取数据
- 第一次打开 文件时,通常 文件指针会指向文件的开始位置
- 当执行了
read
方法后,文件指针 会移动到 读取内容的末尾- 默认情况下会移动到 文件末尾
思考
- 如果执行了一次
read
方法,读取了所有内容,那么再次调用read
方法,还能够获得到内容吗?
答案
- 不能
- 第一次读取之后,文件指针移动到了文件末尾,再次调用不会读取到任何的内容
打开文件的方式
open
函数默认以 只读方式 打开文件,并且返回文件对象
语法如下:
f = open("文件名", "访问方式")
访问方式 | 说明 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常 |
w | 以只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
a | 以追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
r+ | 以读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常 |
w+ | 以读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
a+ | 以读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
按行读取文件内容
read
方法默认会把文件的 所有内容 一次性读取到内存- 如果文件太大,对内存的占用会非常严重
readline
方法
readline
方法可以一次读取一行内容- 方法执行后,会把 文件指针 移动到下一行,准备再次读取
读取大文件的正确姿势
# 打开文件
file = open("README")
while True:
# 读取一行内容
text = file.readline()
# 判断是否读到内容
if not text:
break
# 每读取一行的末尾已经有了一个 `\n`
print(text, end="")
# 关闭文件
file.close()
文件读写案例 —— 复制文件
小文件复制
- 打开一个已有文件,读取完整内容,并写入到另外一个文件
# 1. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")
# 2. 读取并写入文件
text = file_read.read()
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()
大文件复制
- 打开一个已有文件,逐行读取内容,并顺序写入到另外一个文件
# 1. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")
# 2. 读取并写入文件
while True:
# 每次读取一行
text = file_read.readline()
# 判断是否读取到内容
if not text:
break
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()
文件/目录的常用管理操作
- 在 终端 / 文件浏览器、 中可以执行常规的 文件 / 目录 管理操作,例如:
- 创建、重命名、删除、改变路径、查看目录内容、……
- 在
Python
中,如果希望通过程序实现上述功能,需要导入os
模块
文件操作
序号 | 方法名 | 说明 | 示例 |
---|---|---|---|
01 | rename | 重命名文件 | os.rename(源文件名, 目标文件名) |
02 | remove | 删除文件 | os.remove(文件名) |
目录操作
序号 | 方法名 | 说明 | 示例 |
---|---|---|---|
01 | listdir | 目录列表 | os.listdir(目录名) |
02 | mkdir | 创建目录 | os.mkdir(目录名) |
03 | rmdir | 删除目录 | os.rmdir(目录名) |
04 | getcwd | 获取当前目录 | os.getcwd() |
05 | chdir | 修改工作目录 | os.chdir(目标目录) |
06 | path.isdir | 判断是否是文件 | os.path.isdir(文件路径) |
提示:文件或者目录操作都支持 相对路径 和 绝对路径
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)
eval
函数
eval()
函数十分强大 —— 将字符串 当成 有效的表达式 来求值 并 返回计算结果
# 基本的数学计算
In [1]: eval("1 + 1")
Out[1]: 2
# 字符串重复
In [2]: eval("'*' * 10")
Out[2]: '**********'
# 将字符串转换成列表
In [3]: type(eval("[1, 2, 3, 4, 5]"))
Out[3]: list
# 将字符串转换成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict
不要滥用 eval
在开发时千万不要使用
eval
直接转换input
的结果
__import__('os').system('ls')
- 等价代码
import os
os.system("终端命令")