一、类的结构
1、术语 —— 实例
使用面相对象开发,第 1 步 是设计 类 使用 类名() 创建对象,创建对象 的动作有两步:
- 在内存中为对象 分配空间
- 调用初始化方法 init 为 对象初始化
对象创建后,内存 中就有了一个对象的 实实在在 的存在 —— 实例
因此,通常也会把:
- 创建出来的 对象 叫做 类 的 实例
- 创建对象的 动作 叫做 实例化
- 对象的属性 叫做 实例属性
- 对象调用的方法 叫做 实例方法
在程序执行时:
- 对象各自拥有自己的 实例属性
- 调用对象方法,可以通过 self.
结论
- 每一个对象 都有自己 独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法内部
2、类是一个特殊的对象
Python 中 一切皆对象:
- class AAA: 定义的类属于
- 类对象 obj1 = AAA() 属于 实例对象
除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法
通过 类名. 的方式可以 访问类的属性 或者 调用类的方法
二、类属性和实例属性
1、 概念和使用
- 类属性 就是给 类对象 中定义的 属性
- 通常用来记录 与这个类相关 的特征
- 类属性 不会用于记录 具体对象的特征
示例需求
- 定义一个 工具类
- 每件工具都有自己的 name
- 需求 —— 知道使用这个类,创建了多少个工具对象?
class Tool(object):
count = 0
def __init__(self, name):
self.name = name
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = 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
Game.show_help()
Game.show_top_score()
game = Game("小明")
game.start_game()
Game.show_top_score()
五、单例
1、作用
设计模式
- 设计模式 是 前辈们工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对 某一特定问题 的成熟的解决方案;
- 使用 设计模式 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性;
单例设计模式
- 目的 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例;
- 每一次执行 类名() 返回的对象,内存地址是相同的 。
2、 new 方法
使用 类名() 创建对象时,Python 的解释器 首先 会 调用__new__ 方法为对象 分配空间 __new__ 是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:
Python 的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给__init__ 方法
重写 __new__ 方法 的代码非常固定!
重写 new 方法 一定要return super().__new__(cls) 否则 Python 的解释器 得不到 分配了空间的 对象引用,就不会调用对象的初始化方法 注意:__new__ 是一个静态方法,在调用时需要 主动传递 cls 参数
示例代码
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
return super().__new__(cls)
def __init__(self):
print("初始化音乐播放对象")
player = MusicPlayer()
print(player)
3、Python 中的单例
单例 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例
class MusicPlayer(object):
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
只执行一次初始化工作
在每次使用 类名() 创建对象时,Python 的解释器都会自动调用两个方法:
在上一小节对 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):
if cls.instance is None:
cls.instance = super().__new__(cls)
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)异常
程序开发时,很难将 所有的特殊情况 都处理的面面俱到,通过 异常捕获 可以针对突发事件做集中的处理,从而保证程序的 稳定性和健壮性
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:
pass
except (错误类型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:
pass
except 错误类型2:
pass
except (错误类型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():
pwd = input("请输入密码:")
if len(pwd) >= 8:
return pwd
ex = Exception("密码长度不够")
raise ex
try:
user_pwd = input_password()
print(user_pwd)
except Exception as result:
print("发现错误:%s" % result)
|