Python_05_类的结构


目录:

类的结构

实例

面向对象开发,第一步是设计类

使用类名()创建对象,创建对象的动作有两步:

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()即可调用

案例

案例分析

  1. 设计一个Game类;
  2. 属性:
    1. 定义一个类属性top_score记录游戏的历史最高分
    2. 定义一个实例属性player_name记录当前游戏的玩家排名
  3. 方法:
    1. 静态方法show_help显示游戏帮助信息
    2. 类方法show_top_score显示历史最高分
    3. 实例方法start_game开始当前玩家的游戏
  4. 主程序步骤
    1. 查看帮助信息
    2. 查看历史最高分
    3. 创建游戏对象,开始游戏

image-20221118161353504案例演练

 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循环,要么保存到数据库中

⭐案例小结

  1. 如果一个对象需要访问实例属性可封装为实例方法
  2. 如果一个对象需要访问类属性可封装为类方法
  3. 如果既不需要访问实例属性也不需要访问类属性,可将其封装为静态方法
  4. 如果既要访问实例属性也要访问类属性,可将其封装为实例方法

应用

单例

单例设计模式

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

单例设计模式的目的:

  1. 创建的对象,在系统中只有唯一的一个实例
  2. 每一次执行类名()返回的对象,内存地址是相同的

应用场景:音乐播放对象、回收站对象、打印机对象;

__new__方法

⭐使用类名()创建对象时,python解释器会做两件事:

  1. 使用__new__方法为
    1. 对象分配空间;
    2. 返回对象引用(给__init__方法提供);
  2. 使用__init__方法为
    1. 对象进行初始化;
    2. 定义实例属性;

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

重写__new__方法一定要return super().__new__(cls),否则Python解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法。

__init__是一个静态方法,在调用时需要主动传递cls参数。因为是静态方法,所以不会对类属性进行修改,而要对类属性进行修改,必须要设置成类方法也就是__init__(cls)

创建单例(让类创建的对象,在系统中只有唯一的一个实例)的方法:

  1. 定义一个类属性,初始化值为None,用于记录单例对象的引用;
  2. 重写__new__方法;
  3. 如果类属性is None,调用父类方法分配空间,并在类属性中记录结果;
  4. 返回类属性中记录的对象引用;
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:
    出现错误的处理若代码执行正确则执行该语句

工作原理

  1. 首先,执行 try 子句 (try 和 except 关键字之间的(多行)语句)。
  2. 如果没有触发异常,则跳过 except 子句,try 语句执行完毕。
  3. 如果执行 try 子句时发生了异常,则跳过该子句中剩下的部分。如果异常的类型与 except 关键字后面的异常匹配,则执行 except 子句,然后,继续执行 try 语句之后的代码。
  4. 如果发生的异常不是 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 变量命名全部大写;

模块搜索顺序

  1. 搜索当前目录有无指定模块名的文件,有则直接导入(由此可知在给文件起名时,不要与系统模块文件重名);
  2. 若当前目录没有,则搜索python的系统目录(可通过内置属性__file__查看模块的完整路径);

原则

  1. 每一个文件都应该是可以被导入的;
  2. 一个独立的python文件就是一个模块;
  3. 在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍;

__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()  # 输出'接收信息'

发布模块

  1. 创建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"])
    
  2. 构建模块

    python3 setup.py build  # 生成build目录
    
  3. 生成发布压缩包

    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模块