• 面向对象(二)


    单例

    目标

    • 单例设计模式
    • __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__ 方法改造之后,每次都会得到 第一次被创建对象的引用
    • 但是:初始化方法还会被再次调用

    需求

    • 让 初始化动作 只被 执行一次

    解决办法

    1. 定义一个类属性 init_flag 标记是否 执行过初始化动作,初始值为 False
    2. 在 __init__ 方法中,判断 init_flag,如果为 False 就执行初始化动作
    3. 然后将 init_flag 设置为 True
    4. 这样,再次 自动 调用 __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 异常类
    • 在开发时,如果满足 特定业务需求时,希望 抛出异常,可以:
      1. 创建 一个 Exception 的 对象
      2. 使用 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
    
        # 每读取一行的末尾已经有了一个 `
    `
        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("终端命令")
    

     

  • 相关阅读:
    封装

    如何通过命令行窗口查看sqlite数据库文件
    标签控件
    信息提示框
    循环
    数组
    switch
    成员局部变量
    变量
  • 原文地址:https://www.cnblogs.com/linyuhong/p/10041511.html
Copyright © 2020-2023  润新知