单例设计模式

基本概念

  • 设计模式是前人工作的总结和提炼,通常广泛流传的设计模式都是针对某一特定问题的成熟解决方案
  • 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性和可扩展性
  • 单例设计模式的目的是为了在系统中只有唯一的一个实例对象,每一次执行类名()返回的对象的内存地址是相同的

    new内置方法

  • 使用类名()创建对象时,Python的解释器首先会调用new方法为对象分配空间

  • new是一个由object基类提供的内置的静态方法,主要作用有两个
    • 在内存中为对象分配空间
    • 返回对象的引用
  • Python的解释器获得对象的引用后,将引用作为第一个参数传递给init方法
  • 重写new方法的代码非常固定
    • 一定要return super().__new__(cls),否则Python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
    • 注意:new是一个静态方法,在调用时需要主动传递cls参数

image.png

单例设计模式的实现

  • 步骤

    1. 定义一个类属性,初始值为None,用于记录单例对象的引用
    2. 重写new方法
    3. 如果类属性 is None,调用父类方法分配空间,并在类属性中记录结果
    4. 返回类属性中记录的对象引用

      1. class MusicPlayer:
      2. instance = None
      3. def __new__(cls, *args, **kwargs):
      4. if cls.instance is None:
      5. cls.instance = super().__new__(cls)
      6. return cls.instance
      7. def __init__(self, music_name):
      8. print('初始化音乐播放器')
  • 改进

    • 上文对new方法重写之后,每次都会得到第一次被创建对象的引用,但是初始化方法init还会被再次调用
    • 解决方法:

      1. 定义一个类属性 init_flag 标记是否执行过初始化动作,初始值为False
      2. init方法中判断init_flag,如果为False就执行初始化动作并将 init_flag 修改为True
      3. 这样,再次自动调用init方法时,初始化动作就不会再被执行 ```python class MusicPlayer: instance = None init_flag = False

      def new(cls, args, *kwargs): if cls.instance is None:

         cls.instance = super().__new__(cls)
      

      return cls.instance

      def init(self, music_name): if not MusicPlayer.init_flag:

         print('初始化音乐播放器')
         MusicPlayer.init_flag = True
      

player1 = MusicPlayer(‘青花瓷’) player2 = MusicPlayer(‘本草纲目’) print(player1 is player2) # True

<a name="c8W2y"></a>
## 异常
<a name="x004l"></a>
### 捕获异常

- 抛出异常:程序停止执行并且提示错误信息这个动作
- 捕获异常:捕抓抛出的异常,然后做集中处理,从而保证程序的稳定性和健壮性
   - 语法格式

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654689029152-a0c0d272-f849-44c2-8c4b-c69cfdc8b663.png#clientId=u477a32f5-7002-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=80&id=u91bb25e4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=211&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6303&status=done&style=none&taskId=uae2b41c9-be05-4602-a403-ad3e33c3f53&title=&width=140.66666666666666)

   - 说明
      - 首先执行try语句,如果没有异常发生,则忽略except语句,try语句执行后结束
      - 如果在执行try语句的过程中发生了异常,那么try语句余下的部分将被忽略
         - 如果异常的类型和except之后的名称相符,那么对应的except语句将被执行
         - 如果异常的类型没有和任何的except之后的名称相符,那么这个异常将会传递给上层的try中
      - 异常处理不仅仅能处理那些直接发生在try语句中的异常,也能处理子句中调用的函数(直接或间接)里抛出的异常
- 捕获异常类型:在程序执行时,可能会遇到不同类型的异常,并且需要针对不同类型的异常做出不同的响应,这个时候就需要捕获异常类型
   - 语法格式

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654689125179-acc5073b-ea40-4916-83a1-3dc0c08eb47b.png#clientId=u477a32f5-7002-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=123&id=u75eb16bc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=184&originWidth=341&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13968&status=done&style=none&taskId=u53369b7b-c697-4382-829d-489d52aba1d&title=&width=227.33333333333334)

   - 可以在 excpet 后增加 as,用于获得异常信息
      - 举例

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654690515778-b98dcb55-93c5-4f57-aec2-e5d31384883b.png#clientId=u477a32f5-7002-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=185&id=u32847eb0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=277&originWidth=640&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13793&status=done&style=none&taskId=u342a70ce-b9b5-416b-bd69-c82825c7b0f&title=&width=426.6666666666667)

- else语句:else语句是可选语句,将在try子句没有发生任何异常的时候执行
   - 语法格式

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654690729092-27c6911b-8851-4812-b190-5fbd58d7357b.png#clientId=u477a32f5-7002-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=163&id=u921fe6f3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=244&originWidth=340&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17730&status=done&style=none&taskId=u6fe951d1-3915-4905-a0d7-cf568183ed5&title=&width=226.66666666666666)

- finally语句:finally语句也是可选语句,无论异常是否发生,finally语句都会被执行
   - 语法格式

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654691385513-516c28ac-8360-4139-b18e-fdec5ea39456.png#clientId=u477a32f5-7002-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=205&id=u310c8f89&margin=%5Bobject%20Object%5D&name=image.png&originHeight=307&originWidth=351&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22828&status=done&style=none&taskId=u13773312-b3b0-4fa0-bd2c-dac5e2f373c&title=&width=234)
```python
try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除0错误")
except Exception as result:
    print("未知错误:%s" % result)
else:
    print("正常执行")
finally:
    print("执行完成,但是不保证正确")

异常传递

  • 异常传递:当函数/方法执行时出现异常,会将异常传递给函数/方法的调用方。如果传递到主程序,仍然没有异常处理,程序才会被终止
  • 开发技巧:在主函数中写异常捕获
    • 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中
    • 这样就不需要在代码中增加大量的异常捕获,从而保证代码的整洁 ```python def demo1(): return int(input(‘输入整数:’))

def demo2(): return demo1()

try: print(demo2()) except Exception as result: print(“未知错误: %s” % result)

<a name="DmKuw"></a>
### 使用raise抛出指定异常

- 方法
   1. 创建一个Exception的对象
   1. 使用raise关键字抛出异常对象
```python
def input_password():
    pwd = input('请输入密码:')
    if len(pwd) >= 8:
        return pwd
    raise Exception('密码长度不够')

try:
    print(input_password())
except Exception as result:
    print(result)

# 输入小于八位的密码,输出'密码长度不够'

自定义异常类

自定义异常类需要继承Exception类,可以直接继承,也可以间接继承

class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


try:
    raise MyError(2 * 2)
except MyError as e:
    print(e)  # 4
    print('value: ', e.value)  # value: 4

断言异常

  • 语法格式:assert expression [, arguments]
  • 作用:用于判断一个表达式,在表达式为false的时候直接返回错误
  • 等价于

image.png

try:
    assert 1 == 0, "发生了错误"
except Exception as e:
    print(e)  # 输出'发生了错误'

模块和包

模块

  • 模块的概念
    • 每一个以扩展名py结尾的Python源代码文件都是一个模块
    • 模块名同样是一个标识符,需要符合标识符的命名规则
    • 在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具
    • 模块就好比是工具包,要想使用这个工具包中的工具,就需要先导入这个模块
  • 模块的导入方式
    • import导入
      • 不使用别名,语法格式:import 模块名1, 模块名2
        • 导入之后,通过模块名.使用模块提供的工具
      • 使用别名,语法格式:import 模块名 as 模块别名
        • 用于模块名太长的情形
        • 导入之后,需要通过模块别名.使用模块提供的工具
        • 注意:模块别名应该符合大驼峰命名法
    • from…import导入
      • 语法格式:form 模块名 import 工具名
      • 用于希望从某个模块导入部分工具的情形
      • 导入之后,可以直接使用模块提供的工具,而不用加模块名.
      • 注意:如果两个模块存在同名的工具,那么后导入的模块的工具会覆盖先导入的模块的工具,为了避免发生冲突,可以使用as关键字给其中一个工具起一个别名
        • 语法格式:from 模块名 import 工具名 as 工具别名
      • 此外,可以通过from 模块名 import *导入所有工具
  • 模块的搜索顺序:搜索当前目录指定模块名的文件,如果有就直接导入;如果没有,再搜索系统目录
    • 说明:给文件起名时,不要和系统的模块文件重名,否则系统的模块文件无法正常导入,因为导入的是当前目录中的同名文件
  • 注意:在导入模块时,模块中所有没有任何缩进的代码都会被执行一遍
  • Python中每个模块都有一个内置属性file,可以查看模块的完整路径 ```python import wd_01 as DogModule

DogModule.say_hello() # say_hello()是个方法 wd_01.say_hello() print(DogModule.Dog() # Dog是个类

from wd_01 import say_hello from wd_02 import say_hello as say_hello2 say_hello() say_hello2()

import random print(random.file) # D:\Program\Anaconda\lib\random.py

<a name="sY8Bm"></a>
### 包

- 基本概念
   - 包是一个包含多个模块的特殊目录
   - 包内有一个特殊的文件__init__.py,用于指定对外界提供的模块列表
      - 语句格式:`from . import 模块名1, 模块名2...`
   - 包名的命名方式和变量名一样,使用下划线表示法
   - 使用`import 包名`可以一次性导入此包提供给外界的所有模块
   - 举例

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654753945274-81fa8e59-1290-416a-805f-bb572b11e5f2.png#clientId=ua946c4cb-1c32-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=145&id=jw3ED&margin=%5Bobject%20Object%5D&name=image.png&originHeight=217&originWidth=609&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18162&status=done&style=none&taskId=u0b14b116-4821-42f1-bb85-586ae0c7f36&title=&width=406)

- 发布包
   1. 新建setup.py,将其和要发布的包放在同一级目录,setup.py的内容如下

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654754243225-38026d21-d5af-4ca4-99a2-b99c15d7df67.png#clientId=ua946c4cb-1c32-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=179&id=RN3tc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=269&originWidth=670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=90387&status=done&style=none&taskId=ue12a2631-fc1e-454b-8f05-64f85d98723&title=&width=446.6666666666667)

   2. 构建包:`python3 setup.py build`
   2. 生成发布的压缩包:`python3 setup.py sdist`
- 安装包

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654754489733-b4fdbfae-67e2-4d09-b9f2-97e076932849.png#clientId=ua946c4cb-1c32-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=76&id=uc3f75d87&margin=%5Bobject%20Object%5D&name=image.png&originHeight=114&originWidth=334&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17726&status=done&style=none&taskId=uf158f319-7c7c-452d-809c-12d8c5653f9&title=&width=222.66666666666666)

- 卸载包

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654754541326-04bdf7c8-7f9c-493a-935c-0152433e23b8.png#clientId=ua946c4cb-1c32-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=33&id=u7ab1a497&margin=%5Bobject%20Object%5D&name=image.png&originHeight=49&originWidth=474&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15108&status=done&style=none&taskId=ud7363cfc-ad0c-4936-a4a2-b84ad3f3698&title=&width=316)

- pip安装第三方包
   - 第三方包通常是指由知名的第三方团队开发的并且被程序员广泛使用的Python包
   - pip是一个通过的Python包管理工具,提供了对Python包的查找、下载、安装、卸载等功能
   - 验证是否存在pip:`pip3 -V`
   - 在ubuntu下安装pip:`sudo apt install python3-pip`
   - 安装包:`sudo pip3 install 包名`
   - 卸载包:`sudo pip3 uninstall 包名`
      - 注意:在windows下安装包和卸载包的语法与ubuntu类似,只是没有sudo,并且将pip3换成pip
   - 查看已经安装了哪些包:`pip3 list`
- ubuntu下修改pip安装源
   1. `mkdir ~/.pip`
   1. 在~./pip目录下,新建pip.conf文件,内容如下

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654755054602-1c376be8-bfde-43fe-b75c-d3ffd42ad83c.png#clientId=ua946c4cb-1c32-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=97&id=u9db551b9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=145&originWidth=396&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20371&status=done&style=none&taskId=u44bcb3bc-9287-4a28-afb1-ab20ef9567b&title=&width=264)

- windows下修改pip安装源
   1. 在cmd输入%APPDATA%就可以进入:C:\Users\Administrator\AppData\Roaming
   1. 新建pip文件夹
   1. 进入pip文件夹,新建pip.ini文件,内容如下

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654755339200-d6507f89-3047-4ed6-b988-4444ba1a0da0.png#clientId=ua946c4cb-1c32-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=60&id=uf0abf4a5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=90&originWidth=494&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5923&status=done&style=none&taskId=u81ce8c47-646b-4d96-94f7-4e313139930&title=&width=329.3333333333333)
<a name="BmaBa"></a>
## 文件

- 在计算机中,文件是以二进制的方式保存在磁盘上
- 文件可分为文本文件和二进制文件
   - 文本文件:可以使用文本编辑软件查看
   - 二进制文件:不能使用文本编辑软件查看,例如图片、音频、视频
<a name="gkqa5"></a>
### 文件的基本操作

- 操作文件的套路非常固定,分三个步骤
   1. 打开文件,返回文件对象
   1. 读 / 写文件
      - 读文件:操作文件对象,将文件内容读入内存
      - 写文件:操作文件对象,将内存内容写入文件
   3. 关闭文件:关闭文件对象
- 打开文件
   - 语法格式:`f = open('文件名', '访问方式', encoding='utf8')`
   - 完整的语法格式

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654773544758-6dd76ef4-4839-4c3b-a39b-3db4473f70d9.png#clientId=u70d08c99-d049-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=37&id=u9b2ea47c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=55&originWidth=848&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7071&status=done&style=none&taskId=u5a491d70-fe4f-4201-9aa9-8cf0535b36a&title=&width=565.3333333333334)

   - 访问方式
      - r:以**只读方式**打开文件,是默认方式
         - 如果文件存在,文件指针将会位于文件的开头
         - 如果文件不存在,会抛出异常
      - r+:以**读写方式**打开文件
         - 如果文件存在,文件指针将会位于文件的开头
         - 如果文件不存在,会抛出异常
      - w:以**只写方式**打开文件
         - 如果文件存在,文件对应内容将会被覆盖
         - 如果文件不存在,则创建新文件
      - w+:以**读写方式**打开文件
         - 如果文件存在,文件对应内容将会被覆盖
         - 如果文件不存在,则创建新文件
      - a:以**追加方式**打开文件,
         - 如果文件存在,文件指针将会位于文件的结尾
         - 如果文件不存在,则创建新文件
      - a+:以**追加方式**打开文件
         - 如果文件存在,文件指针将会位于文件的结尾
         - 如果文件不存在,则创建新文件
      - 对于非文本文件需要以二进制模式打开,相应的访问方式为rb、rb+、wb、wb+、ab、ab+
   - encoding:一般使用 'utf8'
- 读 / 写文件
   - 相关常用方法
      - file.read([size]):从文件读取指定的字符数,如果未给定或为负则读取所有,返回读取的内容
      - file.readline([size]):读取整行,包括 '\n' 字符,返回读取的内容
      - file.write(str):将字符串写入文件,返回写入的字符长度
      - file.seek(offset[, whence]):移动文件指针到指定位置
         - 如果操作成功,返回新的文件位置;如果操作失败,返回-1
         - offset:文件指针偏移量,单位是字节,文本模式下只能为正,二进制模式下才能为负值
         - whence:可选参数,表示从哪个位置开始偏移,三种取值
            - 0(os.SEEK_SET):从文件开头开始算起,是默认值
            - 1(os.SEEK_CUR):从当前位置开始算起
            - 2(os.SEEK_END):从文件末尾算起
   - 文件指针
      - 文件指针标记从哪个位置开始读取数据
      - 注意:当执行了read方法后,文件指针会移动到读取内容的末尾
      - 频繁的移动文件指针,会影响文件的读写效率
```python
# 文件Readme的初始内容是:hellopython

def write_read():
    f = open('Readme', 'r+', encoding='utf8')
    f.write('world')
    # f.seek(0, os.SEEK_CUR)
    print(f.read(6))
    f.close()
'''
如果注释行被注释了,文件内容变为hellopythonworld,输出结果为hellop,不符合预期
如果注释行没被注释,文件内容变为worldpython,输出结果为python,符合预期
'''

def read_write():
    f = open('Readme', 'r+', encoding='utf8')
    text = f.read(6)
    print(text)
    # f.seek(0, os.SEEK_CUR)
    f.write('java')
    f.close()
'''
如果注释行被注释了,输出结果为hellop,文件内容变为hellopythonjava,不符合预期
如果注释行没被注释,输出结果为hellop,文件内容变为hellopjavan,符合预期
'''
file_read = open('Readme', encoding='utf8')
file_write = open('Readme复件', 'w', encoding='utf8')

while True:
    text = file_read.readline()
    if not text:
        break
    file_write.write(text)

file_read.close()
file_write.close()

文件 / 目录的常用管理操作

  • 以下操作都需要导入os模块:import os

    • os.rename(源文件名, 目标文件名):重命名文件 / 目录
    • os.remove(文件名):只能删除文件,不能删除目录
    • os.listdir(目录名):获取目录中的子文件 / 子目录构成的列表
    • os.mkdir(目录名):创建目录
    • os.rmdir(目录名):删除目录,目录需要为空,否则只能迭代删除
    • os.getcwd():获取当前目录
    • os.chdir(目标目录):修改工作目录
    • os.path.isdir(文件路径):判断是否是目录
    • os.stat(文件路径):用于在给定的路径上执行一个系统stat的调用,返回的stat结构有以下常用属性
      • st_size:普通文件以字节为单位的大小
      • st_atime:上次访问的时间
      • st_mtime:最后一次修改的时间
      • st_ctime:创建时间
    • 注意:均可以使用相对路径和绝对路径
      def dfs(path_name, width):
      file_names = os.listdir(path_name)
      for i in file_names:
         print(' ' * width + i)
         current_name = path_name + '/' + i
         if os.path.isdir(current_name):
             dfs(current_name, width + 4)
      

      传入配置参数

  • 需要导入sys模块:import sys

  • 举例代码

image.png

  • 两种传入方式

image.png
image.png

  • 输出结果

image.png

eval函数

  • 作用:将字符串当成有效的表达式来求值并返回计算结果
  • 语法格式:eval(expression)
  • 举例

image.png
image.png