类的结构
实例
面向对象开发,第一步是设计类;
使用类名()
创建对象,创建对象的动作有两步:
1、在内存中为分配空间;
2、调用初始化方法__init__
为对象初始化;
对象创建后,内存中就有了一个对象的实实在在的存在——实例
因此,通常也会把创建出来的对象叫做类的实例;创建对象的动作叫做实例化;对象的属性叫做实例属性;对象调用的方法叫做实例方法;
结论
每一个对象都有自己独立的内存空间,保存各自不同的属性;
多个对象的方法,在内存中只有一份。在调用方法时,需要把对象的引用传递到方法内部;
类的定义
类是一个特殊的对象;在python中,一切皆对象;在linux中,一切皆文件;
class AAA: # 定义的类属于类对象
obj1 = AAA() # obj1属于实例对象
在程序运行时,类同样会被加载到内存;
在python中,类是一个特殊的对象——类对象;
在程序运行时,类对象在内存中只有一份,使用一个类可以创建出多个实例对象;
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法,用类属性和类方法进行区分;
通过类名.
的方式可以访问到类的属性或者调用类的方法
类属性和实例属性
类属性是类对象中定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征。
属性的获取机制
在python中属性的获取存在一个向上查找机制(当输入对象.属性
时,会先在对象内部中查找对象属性,若没有则向上寻找类属性)推荐使用类名.类属性
的方式来访问类属性。
类方法和静态方法
类方法
@classmethod # @classmethod是修饰器,用于告诉解释器这是一个类方法
def 类方法名(参数,常用cls): # 类方法的第一个参数应该为cls,与self类似
pass
通过类名.
调用类方法,调用方法时,不需要传递cls
参数;
在方法内部,可以通过cls.
访问类的属性,也可以通过cls.
调用其他类的类方法;
class Tool(object):
count = 0 # 类属性
@classmethod
def show_tools_count(cls): # 类方法与实例方法定义一样,只不过类方法需要在前面添加@classmethod
print("工具数量有%d个" % cls.count)
def __init__(self, name):
self.name = name # 创建出对象后,就是实例属性
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("砖头")
Tool.show_tools_count() # 输出结果'工具数量有2个'
静态方法
应用场景:不管哪个对象或者哪个类调用,都输出一样的内容。不访问实例属性/类属性;
class Dog(object):
def run(self): # 既不访问实例属性也不访问类属性,run下面会出现虚线提示可能是静态对象
print("小狗要跑")
class Dog(object): # 静态方法正确写法
@staticmethod # 声明该对象为静态对象
def run(): # 既然不需要访问实例/类属性,那也就需要self或者cls
print("小狗要跑")
Dog.run() # 调用静态对象时不需要创建对象,通过Dog.run()即可调用
案例
案例分析
- 设计一个
Game
类; - 属性:
- 定义一个类属性
top_score
记录游戏的历史最高分 - 定义一个实例属性
player_name
记录当前游戏的玩家排名
- 定义一个类属性
- 方法:
- 静态方法
show_help
显示游戏帮助信息 - 类方法
show_top_score
显示历史最高分 - 实例方法
start_game
开始当前玩家的游戏
- 静态方法
- 主程序步骤
- 查看帮助信息
- 查看历史最高分
- 创建游戏对象,开始游戏
案例演练
class Game(object):
top_score = 0
def __init__(self, player_name, top):
self.player_name = player_name
self.top = top
@staticmethod
def show_help():
print("帮助手册")
@classmethod
def show_top_score(cls):
print("该游戏的最高分是%s" % cls.top_score)
def start_game(self):
print("%s开始游戏,获得分数%d" % (self.player_name, self.top))
if self.top > Game.top_score:
Game.top_score = self.top
Game.show_help() # 静态方法,通过类名调用,使用@classmethod修饰器
Game.show_top_score() # 类方法,通过类名调用,使用@classmethod修饰器
feiji = Game('戴豪锐', 2) # 创建实例对象
feiji.start_game() # 实例方法,通过实例名调用,使用__init__进行初始化
Game.show_top_score() # 每次运行程序都会对top_score重新赋值0,要么使用while循环,要么保存到数据库中
⭐案例小结
- 如果一个对象需要访问实例属性可封装为实例方法;
- 如果一个对象只需要访问类属性可封装为类方法;
- 如果既不需要访问实例属性也不需要访问类属性,可将其封装为静态方法;
- 如果既要访问实例属性也要访问类属性,可将其封装为实例方法;
应用
单例
单例设计模式
设计模式是前人工作的总结和提炼,广为流传的设计模式通常都是针对某一个特定问题的成熟解决方案;使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性;
单例设计模式的目的:
- 让类创建的对象,在系统中只有唯一的一个实例;
- 每一次执行
类名()
返回的对象,内存地址是相同的;
应用场景:音乐播放对象、回收站对象、打印机对象;
__new__
方法
⭐使用类名()
创建对象时,python解释器会做两件事:
- 使用
__new__
方法为- 对象分配空间;
- 返回对象引用(给
__init__
方法提供);
- 使用
__init__
方法为- 对象进行初始化;
- 定义实例属性;
python的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__
方法。
重写__new__
方法一定要return super().__new__(cls)
,否则Python解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法。
__init__
是一个静态方法,在调用时需要主动传递cls参数。因为是静态方法,所以不会对类属性进行修改,而要对类属性进行修改,必须要设置成类方法也就是__init__(cls)
。
创建单例(让类创建的对象,在系统中只有唯一的一个实例)的方法:
- 定义一个类属性,初始化值为
None
,用于记录单例对象的引用; - 重写
__new__
方法; - 如果类属性
is None
,调用父类方法分配空间,并在类属性中记录结果; - 返回类属性中记录的对象引用;
class MusicPlayer(object):
#定义一个类属性instance,初始化值为None。用于单例判断
instance = None
#定义一个类属性init_flag,初始化值为False,用于判断是否进行过初始化
init_flag = False
#重写__new__方法
def __new__(cls, *args, **kwargs):
# 如果类属性is None,调用父类方法分配空间,并在类属性中记录结果;
if cls.instance is None:
cls.instance = super().__new__(cls)
# 返回类属性中记录的对象引用;
return cls.instance
# 可以使用def __init__(self),但改类属性时只能使用 类名.属性 的形式来修改
def __init__(cls):
# 如果类属性is False,进行第一次初始化操作;
if cls.init_flag is False:
print("进行初始化")
# 完成第一次初始化操作修改类属性,之后再有对象调用不会再触发初始化操作;
cls.init_flag = True
player1 = MusicPlayer()
print(player1) # 进行一次初始化,然后调用
player2 = MusicPlayer()
print(player2)
# 运行程序控制台输出
进行初始化
<__main__.MusicPlayer object at 0x000001409842BF10>
<__main__.MusicPlayer object at 0x000001409842BF10>
异常
程序在运行时,如果python解释器遇到错误(即使是用户导致的错误),会终止程序的运行,并且提示错误信息,这就是异常。终止程序并提示错误信息这个动作,我们称之为抛出(raise)异常。
程序在运行时,无法将所有特殊情况都处理到,就可以通过异常捕获对突发事件做集中处理。
捕获异常
简单的异常捕获
如果对某些代码的执行不能确定是否正确,可以使用try
来捕获异常。
try:
尝试执行的代码
except:
出现错误的处理,若代码执行正确,则执行该语句
工作原理
- 首先,执行 try 子句 (try 和 except 关键字之间的(多行)语句)。
- 如果没有触发异常,则跳过 except 子句,try 语句执行完毕。
- 如果执行 try 子句时发生了异常,则跳过该子句中剩下的部分。如果异常的类型与 except 关键字后面的异常匹配,则执行 except 子句,然后,继续执行 try 语句之后的代码。
- 如果发生的异常不是 except 子句中列示的异常,则将其传递到外部的 try 语句中;如果没有找到处理程序,则它是一个 未处理异常,语句执行将终止,并显示如上所示的消息。
错误类型捕获
根据不同类型的异常,做出不同的响应。当python解释器抛出异常时,最后一行错误信息的第一个单词就是错误类型。
try:
num = int(input("请输入整数:"))
num1 = 0 / num
print("%.2f" % num1)
except ValueError: # 当遇到ValueError类型错误时,执行以下操作
print("朋友你输入的数据类型错了")
except ZeroDivisionError:
print("0不能作为分母")
# Exception表示无论出现任何错误,程序都不会被终止
except Exception as result: # 异常处理程序能处理try语句中调用的函数内部发生的异常。记录错误
print("未知错误 %s" % result)
⭐完整异常处理
try:
pass
except ValueError as verror: # 出现指定类型时执行,参数存储在args变量中,可通过verror或verror.args获取值
pass
except Exception as result: # 出现其他任何错误执行
pass
else: # 没有任务错误时执行,可以避免意外捕获非try..except语句保护的代码触发的异常(不需要捕获异常的代码)
pass
finally: # 不管有没有错误均执行,可能用于记录日志,因为生命周期在try里面;可能用于关闭流
pass
异常传递
当 函数/方法
执行出现异常,首先将异常传递给 函数/方法
的调用一方。直到传递到主程序时,如果异常仍未被处理,程序才会被终止。
- 在开发时,可以在主函数中增加异常捕获;
- 而在主函数中调用其他气焊,只要出现异常,都会传递到主函数的异常捕获中;
- 这样就不需要在代码中,增加大量的异常捕获;
抛出raise异常
根据应用程序特有的业务需求主动抛出异常。例如判断用户登录模块,如果密码强度过低则抛出异常让程序无法继续执行。
- 创建一个
Exception
对象; - 使用
raise
关键字抛出异常对象;
程序的流程为:用户输入密码,先进行判断,若长度达到,返回给密码;若长度不足,报'强度过低'的错。将'强度过低'的异常返回给result参数,print输出。
# 定义input_password函数,提示用户输入密码;判断密码强度
def input_password():
password = input("请输入密码:")
if len(password) >= 8: # 先对输入的密码进行判断,直到retuen为止
return password # 若密码强度达到,则将该密码返回给password
ex = Exception("强度过低") # 创建Exception对象
raise ex # 抛出异常
try:
print(input_password()) #
except Exception as result:
print("你这%s的密码不行" % result) # 若报错则回显'你这强度过低的密码不行'
异常链
def func(): # 异常链,在用于转换异常时,该方法很有用。from None可禁用异常链
raise IOError
try:
func() # 触发IOError异常
except IOError as exc: # 当触发触发IOError异常时,再这基础上再触发一个异常
raise RuntimeError('Failed to open database') from exc # from语句用来启动链式异常
模块
import 模块名
是一次性把模块中所有工具全部导入,并且通过模块名/别名的方式来访问;
如果希望从某一个模块中,导入部分工具,可以使用from ... import
的方式;使用该方式导入不需要通过模块名.
的方式来调用,跟自带的函数一样可直接使用。
注意:如果两个模块,存在同名的函数,那么后导入模块的函数,会覆盖掉先导入的函数
python 模块命名使用大驼峰命名法;方法命名可全部小写;变量命名全部小写;包命名全部小写;
linux 变量命名全部大写;
模块搜索顺序
- 搜索当前目录有无指定模块名的文件,有则直接导入(由此可知在给文件起名时,不要与系统模块文件重名);
- 若当前目录没有,则搜索python的系统目录(可通过内置属性
__file__
查看模块的完整路径);
原则
- 每一个文件都应该是可以被导入的;
- 一个独立的python文件就是一个模块;
- 在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍;
__name__
属性
__name__
属性可以实现,测试模块的代码只在测试情况下被运行,而在导入时不会被运行;
__name__
是python的内置属性,记录着一个字符串;- 如果是被其他文件导入的,
__name__
就是模块名; - 如果是当前执行的程序,
__name__
就是__main__
;可以通过判断__name__
的返回值来决定是否执行代码;
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码最下方
def main():
# ... 测试代码
pass
# 根据__name__判断是否执行下方代码
if __name__ == "__main__": # 若是本地运行,则执行main(),若是被导入的,则不执行;
main()
包(Package)
包是一个包含多个模块的特殊目录,使用import
包名可以一次性导入包中的所有模块;
目录下有一个特殊的文件__init__.py
;
from . import send_message # 从当前目录导入模块列表
from . import receive_message
from .. import Singleton # 从上级目录导入模块列表,但并没用,因为又不把上级目录的模块给打包进去
包名的命名方式和变量名一致,小写字母+_
import hr_message # 导入包
hr_message.send_message.send("hello") # 输出'发送 hello'
hr_message.receive_message.receive() # 输出'接收信息'
发布模块
-
创建setup.py文件
# https://docs.python.org/2/distutils/apiref.html # 发布官方文档 from distutils.core import setup setup(name="hr_message", # 包名 version="1.0", # 版本 description="发送和接收模块", # 描述信息 long_description="完整的发送和接收模块", # 完整描述信息 author="daihaorui", # 作者 author_email="haorui_dai@163.com", # 作者邮箱 url="www.dhr2333.cn", # 主页 py_modules=["hr_message.send_message", # 发布的包包含的完整的模块名 "hr_message.receive_message"])
-
构建模块
python3 setup.py build # 生成build目录
-
生成发布压缩包
python3 setuo.py sdist # 生成dist目录,其中包含hr_message.tar.gz压缩包
目录结构
D:. # setup.py文件在此目录下,与hr_message目录处于同一目录
├─.idea # 使用pycharm作为编辑器时,创建项目会自动生成.idea文件夹
│ └─inspectionProfiles
├─build # 使用python3 setup.py build构建出来的
│ └─lib
│ └─hr_message
├─dist # 使用python3 setuo.py sdist生成的目录,压缩包存放在里面
├─hr_message # 包,其中包含模块
│ └─__pycache__
└─__pycache__ # python解释器将运行的程序转换成字节码保存在该目录中
安装模块
tar zxvf hr_message # 下载过来压缩包后解压
python3 setup.py install # cd到hr_message目录中进行安装
卸载模块
hr_message.__file__ # 进入python后使用内置参数__file__找到绝对路径,删除目录和一个info文件
cd <安装目录>
rm -rf hr_message # 直接从安装目录下,把安装模块的目录删除即可
pip安装第三方模块
pip install pygame # 安装pygame模块
pip uninstall pygame # 卸载pygame模块