Python 类属性和类方法、单例、异常

一、类的结构

1、术语 —— 实例

使用面相对象开发,第 1 步 是设计
使用 类名() 创建对象,创建对象 的动作有两步:

  • 在内存中为对象 分配空间
  • 调用初始化方法 init 为 对象初始化

对象创建后,内存 中就有了一个对象的 实实在在 的存在 —— 实例
017_类的结构示意图I

因此,通常也会把:

  • 创建出来的 对象 叫做 类 的 实例
  • 创建对象的 动作 叫做 实例化
  • 对象的属性 叫做 实例属性
  • 对象调用的方法 叫做 实例方法

在程序执行时:

  • 对象各自拥有自己的 实例属性
  • 调用对象方法,可以通过 self.
    • 访问自己的属性
    • 调用自己的方法

结论

  • 每一个对象 都有自己 独立的内存空间,保存各自不同的属性
  • 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法内部

2、类是一个特殊的对象

Python 中 一切皆对象:

  • class AAA: 定义的类属于
  • 类对象 obj1 = AAA() 属于 实例对象

除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法

  • 类属性
  • 类方法

通过 类名. 的方式可以 访问类的属性 或者 调用类的方法
017_类的结构示意图II

二、类属性和实例属性

1、 概念和使用

  • 类属性 就是给 类对象 中定义的 属性
  • 通常用来记录 与这个类相关 的特征
  • 类属性 不会用于记录 具体对象的特征

示例需求

  • 定义一个 工具类
  • 每件工具都有自己的 name
  • 需求 —— 知道使用这个类,创建了多少个工具对象?
    018_类属性案例I
class Tool(object):

    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

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

        # 针对类属性做一个计数+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 知道使用 Tool 类到底创建了多少个对象?
print("现在创建了 %d 个工具" % Tool.count)

2、属性的获取机制(科普)

在 Python 中 属性的获取 存在一个 向上查找机制
在这里插入图片描述

因此,要访问类属性有两种方式:

  • 类名.类属性
  • 对象.类属性 (不推荐)
    注意

如果使用 对象.类属性 = 值 赋值语句,只会 给对象添加一个属性,而不会影响到 类属性的值

三、类方法和静态方法

1、 类方法

  • 类属性 就是针对 类对象 定义的属性
    • 使用 赋值语句 在 class 关键字下方可以定义 类属性
    • 类属性 用于记录 与这个类相关 的特征
  • 类方法 就是针对 类对象 定义的方法
    • 在 类方法 内部可以直接访问 类属性 或者调用其他的 类方法

语法如下

@classmethod
def 类方法名(cls):
    pass

类方法需要用 修饰器 @classmethod 来标识,告诉解释器这是一个类方法

类方法的 第一个参数 应该是 cls

  • 由 哪一个类 调用的方法,方法内的 cls 就是 哪一个类的引用
  • 这个参数和 实例方法 的第一个参数是 self 类似
  • 提示 使用其他名称也可以,不过习惯使用 cls

通过 类名. 调用 类方法,调用方法时,不需要传递 cls 参数

在方法内部

  • 可以通过 cls. 访问类的属性
  • 也可以通过 cls. 调用其他的类方法

示例需求

  • 定义一个 工具类
  • 每件工具都有自己的 name
  • 需求 —— 在 类 封装一个 show_tool_count 的类方法,输出使用当前这个类,创建的对象个数

在这里插入图片描述

@classmethod
def show_tool_count(cls):
    """显示工具对象的总数"""
    print("工具对象的总数 %d" % cls.count)

在类方法内部,可以直接使用 cls 访问 类属性 或者 调用类方法

2、 静态方法

在开发时,如果需要在 类 中封装一个方法,这个方法:

  • 既 不需要 访问 实例属性 或者调用 实例方法
  • 也 不需要 访问 类属性 或者调用 类方法
  • 这个时候,可以把这个方法封装成一个 静态方法

语法如下

@staticmethod
def 静态方法名():
    pass

静态方法 需要用 修饰器 @staticmethod 来标识,告诉解释器这是一个静态方法
通过 类名. 调用 静态方法

class Dog(object):
    
    # 狗对象计数
    dog_count = 0
    
    @staticmethod
    def run():
        
        # 不需要访问实例属性也不需要访问类属性的方法
        print("狗在跑...")

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

四、 方法综合案例

1、需求

设计一个 Game 类

属性:

  • 定义一个 类属性 top_score 记录游戏的 历史最高分
  • 定义一个 实例属性 player_name 记录 当前游戏的玩家姓名

方法:

  • 静态方法 show_help 显示游戏帮助信息
  • 类方法 show_top_score 显示历史最高分
  • 实例方法 start_game 开始当前玩家的游戏

主程序步骤

  • 查看帮助信息
  • 查看历史最高分
  • 创建游戏对象,开始游戏
    在这里插入图片描述

2、案例小结

  • 实例方法 —— 方法内部需要访问 实例属性
  • 实例方法 内部可以使用 类名. 访问类属性
  • 类方法 —— 方法内部 只 需要访问 类属性
  • 静态方法 —— 方法内部,不需要访问 实例属性 和 类属性

提问

  • 如果方法内部 即需要访问 实例属性,又需要访问 类属性,应该定义成什么方法?

答案

  • 应该定义 实例方法 因为,类只有一个,在 实例方法 内部可以使用 类名. 访问类属性
class Game(object):

    # 游戏最高分,类属性
    top_score = 0

    @staticmethod
    def show_help():
        print("帮助信息:让僵尸走进房间")
        
    @classmethod
    def show_top_score(cls):
        print("游戏最高分是 %d" % cls.top_score)

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

    def start_game(self):
        print("[%s] 开始游戏..." % self.player_name)
        
        # 使用类名.修改历史最高分
        Game.top_score = 999

# 1. 查看游戏帮助
Game.show_help()

# 2. 查看游戏最高分
Game.show_top_score()

# 3. 创建游戏对象,开始游戏
game = Game("小明")

game.start_game()

# 4. 游戏结束,查看游戏最高分
Game.show_top_score()

五、单例

1、作用

设计模式

  • 设计模式 是 前辈们工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对 某一特定问题 的成熟的解决方案;
  • 使用 设计模式 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性;

单例设计模式

  • 目的 —— 让 创建的对象,在系统中 只有 唯一的一个实例;
  • 每一次执行 类名() 返回的对象,内存地址是相同的 。

2、 new 方法

使用 类名() 创建对象时,Python 的解释器 首先 会 调用__new__方法为对象 分配空间
__new__ 是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:

  • 在内存中为对象 分配空间
  • 返回 对象的引用

Python 的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给__init__方法

重写 __new__方法 的代码非常固定!

重写 new 方法 一定要return super().__new__(cls)
否则 Python 的解释器 得不到 分配了空间的 对象引用,就不会调用对象的初始化方法
注意:__new__是一个静态方法,在调用时需要 主动传递 cls 参数
022_对象分配空间和初始化

示例代码

class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        # 如果不返回任何结果,
        return super().__new__(cls)

    def __init__(self):
        print("初始化音乐播放对象")

player = MusicPlayer()

print(player)

3、Python 中的单例

单例 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例

  • 定义一个 类属性,初始值是 None,用于记录 单例对象的引用

  • 重写 __new__方法

  • 如果 类属性 is None,调用父类方法分配空间,并在类属性中记录结果

  • 返回 类属性 中记录的 对象引用

023_单例流程

class MusicPlayer(object):

    # 定义类属性记录单例对象引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否已经被赋值
        if cls.instance is None:
            cls.instance = super().__new__(cls)

        # 2. 返回类属性的单例引用
        return cls.instance

只执行一次初始化工作

在每次使用 类名() 创建对象时,Python 的解释器都会自动调用两个方法:

  • __new__分配空间
  • __init__对象初始化

在上一小节对 new 方法改造之后,每次都会得到 第一次被创建对象的引用
但是:初始化方法还会被再次调用

需求

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

解决办法

  • 定义一个类属性init_flag标记是否 执行过初始化动作,初始值为 False
  • __init__ 方法中,判断init_flag,如果为 False 就执行初始化动作
  • 然后将init_flag 设置为 True
  • 这样,再次 自动 调用__init__方法时,初始化动作就不会被再次执行 了
class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    instance = None
    # 记录是否执行过初始化动作
    init_flag = False

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否是空对象
        if cls.instance is None:
            # 2. 调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)

        # 3. 返回类属性保存的对象引用
        return cls.instance

    def __init__(self):

        if not MusicPlayer.init_flag:
            print("初始化音乐播放器")

            MusicPlayer.init_flag = True


# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

六、异常

1、异常的概念

程序在运行时,如果 Python 解释器 遇到一个错误,会停止程序的执行,并且提示一些错误信息,这就是 异常

程序停止执行并且提示错误信息 这个动作,我们通常称之为:抛出(raise)异常
001_异常示意图

程序开发时,很难将 所有的特殊情况 都处理的面面俱到,通过 异常捕获 可以针对突发事件做集中的处理,从而保证程序的 稳定性和健壮性

2、Python异常类

Python是面向对象语言,所以程序抛出的异常也是类。常见的Python异常有以下几个,大家只要大致扫一眼,有个映像,等到编程的时候,相信大家肯定会不只一次跟他们照面(除非你不用Python了)。

异常描述
NameError尝试访问一个没有申明的变量
ZeroDivisionError除数为0
SyntaxError语法错误
IndexError索引超出序列范围
KeyError请求一个不存在的字典关键字
IOError输入输出错误(比如你要读的文件不存在)
AttributeError尝试访问未知的对象属性
ValueError传给函数的参数类型不正确,比如给int()函数传入字符串形

3、捕获异常

(1)try…except…语句语法

在程序开发中,如果 对某些代码的执行不能确定是否正确,可以增加 try(尝试) 来 捕获异常
捕获异常最简单的语法格式:

try:
    尝试执行的代码
except:
    出现错误的处理
  • try 尝试,下方编写要尝试代码,不确定是否能够正常执行的代码
  • except 如果不是,下方编写尝试失败的代码

简单异常捕获演练 —— 要求用户输入整数

try:
    # 提示用户输入一个数字
    num = int(input("请输入数字:"))
except:
    print("请输入正确的数字")
(2)错误类型捕获

在程序执行时,可能会遇到不同类型的异常,并且需要针对不同类型的异常,做出不同的响应,这个时候,就需要捕获错误类型了

语法如下:

try:
    # 尝试执行的代码
    pass
except 错误类型1:
    # 针对错误类型1,对应的代码处理
    pass
except (错误类型2, 错误类型3):
    # 针对错误类型2 和 3,对应的代码处理
    pass
except Exception as result:
    print("未知错误 %s" % result)

当 Python 解释器 抛出异常 时,最后一行错误信息的第一个单词,就是错误类型

异常类型捕获演练 —— 要求用户输入整数

需求

  • 提示用户输入一个整数
  • 使用 8 除以用户输入的整数并且输出
try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除 0 错误")

捕获未知错误

  • 在开发时,要预判到所有可能出现的错误,还是有一定难度的
  • 如果希望程序 无论出现任何错误,都不会因为 Python 解释器 抛出异常而被终止,可以再增加一个
except
语法如下:

except Exception as result:
    print("未知错误 %s" % result)
(3)异常捕获

在实际开发中,为了能够处理复杂的异常情况,完整的异常语法如下:

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 无论是否有异常,都会执行的代码

之前一个演练的 完整捕获异常 的代码如下:

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("执行完成,但是不保证正确")
(4)异常的传递

异常的传递 —— 当 函数/方法 执行 出现异常,会 将异常传递 给 函数/方法 的 调用一方
如果 传递到主程序,仍然 没有异常处理,程序才会被终止

提示

  • 在开发中,可以在主函数中增加 异常捕获 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的 异常捕获
  • 这样就不需要在代码中,增加大量的 异常捕获,能够保证代码的整洁

需求

  • 定义函数 demo1() 提示用户输入一个整数并且返回
  • 定义函数 demo2() 调用 demo1()
  • 在主程序中调用 demo2()
def demo1():
    return int(input("请输入一个整数:"))


def demo2():
    return demo1()

try:
    print(demo2())
except ValueError:
    print("请输入正确的整数")
except Exception as result:
    print("未知错误 %s" % result)
(5)抛出异常 raise

Python 中提供了一个 Exception 异常类
在开发时,如果满足 特定业务需求时,希望 抛出异常,可以:

  • 创建 一个 Exception 的 对象
  • 使用 raise 关键字 抛出 异常对象

需求

  • 定义 input_password 函数,提示用户输入密码
  • 如果用户输入长度 < 8,抛出异常
  • 如果用户输入长度 >=8,返回输入的密码
def input_password():

    # 1. 提示用户输入密码
    pwd = input("请输入密码:")

    # 2. 判断密码长度,如果长度 >= 8,返回用户输入的密码
    if len(pwd) >= 8:
        return pwd

    # 3. 密码长度不够,需要抛出异常
    # 1> 创建异常对象 - 使用异常的错误信息字符串作为参数
    ex = Exception("密码长度不够")

    # 2> 抛出异常对象
    raise ex


try:
    user_pwd = input_password()
    print(user_pwd)
except Exception as result:
    print("发现错误:%s" % result)
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值