Python基础|六、Python基础(封装、继承、多态)

六、Python基础(封装、继承、多态)
目录:

  • 六、Python基础(封装、继承、多态)
        • 一、继承与多态
          • 1.单继承
          • 2.方法的重写
    • super().父类方法
          • 3.父类的私有属性和私有方法
          • 4.多继承
    • print(子类.\_\_mro__)
          • 5.多态
          • 6.术语
        • 二、类属性和方法
          • 1.类属性
    • 类名.类属性
    • 实例名.类属性
          • 2.@classmethod类方法
    • 类名.类方法名(cls, 参数列表)
          • 3.@staticmethod静态方法
    • 类名.静态方法名(参数列表)
          • 4.阶段小结:游戏计分板
          • 5.补充:当方法要同时访问类属性和实例属性时
        • 三、单例
          • 1.单例设计模式
          • 2.\_\_new__ 方法
          • 3.Python中的单例
        • 上一篇文章
        • 下一篇文章

面向对象的三大属性:
  • 封装 根据职责将属性和方法封装在一个个类中,并基于这些类来创建对象
  • 继承 实现代码的复用,相同的代码不需要重复编写
  • 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
一、继承与多态 1.单继承 如图:使用继承时,最左端为父类,右端为子类(图摘自黑马程序员)
Python基础|六、Python基础(封装、继承、多态)
文章图片

单继承的代码结构如图所示:
Python基础|六、Python基础(封装、继承、多态)
文章图片

  • 子类继承拥有了父类的所有属性和方法,可以直接享用,无须再次开发
  • 子类里可额外增加父类里没有的属性和方法
  • 子类会全部继承父类的所有属性和方法,并且可以在 父——子 之间不断传递下去,即子类继承的属性和方法会越来越多,越来越强大——进化论
专业术语,在上图中:
  • Dog类 是 Animal类 的子类,Animal类 是 Dog类 的父类
  • Dog类 是 Animal类 的派生类,Animal类 是 Dog类 的基类
例:Animal 是父类,Dog是子类,Dog类 可以享用 Animal类 中的方法和属性
class Animal: def eat(self): passdef run(self): passdef drink(self): passclass Dog(Animal): def shout(self): pass

2.方法的重写 (1)情况1:覆盖父类的方法
有时候,子类的某些属性或方法的实现可能并不想和父类相同,如子狗的毛色和父狗的毛色可能不一样,这个时候就需要用 方法的重写 来覆盖父类中的属性或方法,如图:
【Python基础|六、Python基础(封装、继承、多态)】覆盖方法:直接在子类中重新编写与 “需要覆盖的方法” 同名的方法即可
Python基础|六、Python基础(封装、继承、多态)
文章图片

在重写之后,在子类对象调用方法或属性时,只会调用子类重写后的方法(因为已覆盖)
例:
class Animal: def eat(self): passdef run(self): passclass Dog(Animal): def run(self): pass

(2)情况2:对父类的方法进行扩展
对于父类的一个方法,若子类除 父类原有方法的实现 需要实现外,仍有另一部分也需要实现(而父类没有的那部分),则子类需要在父类的方法的基础上进行扩展
super().父类方法 可以在覆盖父类方法后的方法里,调用原父类方法,实现父类方法的再现
我们只需先覆盖掉原父类的方法,写入需要扩展的代码;再在覆盖父类方法的基础上,在需要的位置利用 super().父类方法 调用回原父类方法(父类方法的再现)即可
例:
class Animal: def eat(self): passdef run(self): passclass Dog(Animal): def run(self): """另外补充需要扩展的代码即可""" super().run() """另外补充需要扩展的代码即可""" pass

3.父类的私有属性和私有方法
  • 子类对象不能在自己的方法内部,直接访问父类的私有属性和私有方法
  • 子类对象可以通过 父类的公有方法 间接访问到父类的私有属性和私有方法
例:通过父类的公有方法间接访问到父类的私有属性和私有方法
class Animal: """定义属性""" def __init__(self): self.__name = "狗蛋" self.__variety = "Huskie""""定义一个私有方法""" def __run(self): print("访问私有方法:这是一个父类的私有方法")"""定义一个访问父类私有属性的方法""" def visit(self): print("访问私有属性:__name:%s,__variety:%s" % (self.__name, self.__variety)) self.__run()class Dog(Animal): def text(self): self.visit()example = Dog() example.text()

Python基础|六、Python基础(封装、继承、多态)
文章图片

4.多继承
  • 子类可以拥有多个父类,并且具有所有父类的属性和方法
多继承的代码结构如图所示:
Python基础|六、Python基础(封装、继承、多态)
文章图片

多继承使用的注意事项:
  • 如果不同的父类中存在同名的方法,子类对象在调用父类的方法时,会调用哪个父类的方法呢?
  • 在开发中,应该尽量避免上述这种容易造成混淆的情况,如果父类之间存在同名的属性或方法时,应该尽量避免使用多继承
模糊判断:一般是按定义多继承时 class 子类(父类1…) 从左至右的顺序查找
*对上述问题调用优先级的清晰解答——MRO方法搜索顺序(了解):
Python中针对类提供了一个内置属性 __mro__ 可以查看方法搜索顺序,主要用与在多继承时判断方法、属性的调用路径
print(子类.__mro__) 显示调用方法/属性时的顺序,从左到右显示的父类来顺序查找,找到即执行,找不到即向右查找
其中, 中,object类是所有类的基类
object类是一些对象通用的方法,如:字符串的方法…
在定义父类时,可能会看到有 class 父类(object): ,这是因为在Python3.X中,类默认是基于object的新类来定义的,在Python2.X中,默认用的是经典类,一般不推荐使用经典类,因此在创建父类时,可以加上(object)
5.多态 不同的子类对象调用相同的父类方法时,可以通过方法的重写和继承来产生不同的执行效果
在其他类创建的对象中调用多态类创建的对象时:其他类创建的对象调用多态类创建的对象,由于方法/属性的重写的继承,调用相同的方法可能产生不同的执行结果,形成多态
  • 调用相同的方法可能产生不同的执行结果,形成多态(重要
例:结构示意图
Python基础|六、Python基础(封装、继承、多态)
文章图片

class Transportation: """运输父类""" def __init__(self, vehicle): """属性:交通工具名""" self.vehicle = vehicledef arrive(self): print("%s成功到达指定地点" % self.vehicle)class Plane(Transportation): """飞机子类,基于运输父类""" def arrive(self): """方法的重写""" print("%s以最快的速度到达指定地点" % self.vehicle)class Person: """人类""" def __init__(self, name): """属性:人名""" self.name = namedef choice(self, vehicle_choice): """选择的交通工具""" vehicle_choice.arrive()"""波士顿:飞机""" Boston = Plane("Boston") """泰坦尼克号:轮船""" Titanic = Transportation("Titanic") """约翰:人""" John = Person("John")"""约翰选择不同的交通工具,会带来不同的结果,形成多态""" John.choice(Boston) John.choice(Titanic)

Python基础|六、Python基础(封装、继承、多态)
文章图片

6.术语
  • 基于类创建的对象通常也称为类的实例
  • 创建对象的过程通常也称为实例化
  • 对象的属性通常也称为实例属性
  • 对象调用的方法通常也称为实例方法
在Python中,一切皆对象,类是一个特殊的对象,class 属于类对象,object = class() 属于实例对象,因此Python同样会为类分配内存
二、类属性和方法 1.类属性 在创建类时,我们会用 __init__ 来定义对象的属性,但另外,类也有它的属性,类的属性不需要写在方法内,而直接写在类的内部(class定义类的下方)
类属性就是给类对象中定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征
例:
class Tool: """使用赋值语句,定义类属性,记录创建工具对象的总数""" count = 0def __init__(self, name): self.name = name """每创建一个类,做一个计数""" Tool.count += 1tool1 = Tool("锤子") tool2 = Tool("扳手") tool3 = Tool("斧头")print("可以用类名来访问类属性:%d" % Tool.count) for temp_object in (tool1, tool2, tool3): print("而且可以通过对象(实例)来访问类属性:%d" % temp_object.count)

Python基础|六、Python基础(封装、继承、多态)
文章图片

  • 对于通过对象名来访问属性的——Python优先查找对象属性,若对象属性中不存在,则访问类属性(即可能存在对象属性和类属性同名的情况)
  • 通过类名来访问属性的,则只会查找类属性,因此在查找类属性时,不推荐通过对象名来查找属性
类名.类属性 可以在类内部或外部访问到类属性
实例名.类属性 也可以访问到类属性,但不推荐
注:赋值陷阱
在类的外部可以通过 对象.属性 = 属性值 会给对象定义一个对象属性
2.@classmethod类方法 类方法就是针对类的对象定义的方法
定义类方法的代码结构如图所示:
Python基础|六、Python基础(封装、继承、多态)
文章图片

  • 类方法需要用 修饰器@classmethod 来标识,告诉解释器这是一个类方法
  • 类方法的第一个参数应该是 cls
  • 由哪一个类调用的方法,方法内的 cls 就是哪一个类的引用,与 self 是十分类似的
  • cls 可以用其他名称代替,但是习惯上用 cls
类名.类方法名(cls, 参数列表) 可以调用类方法
注:cls 不传递参数
例:
class Tool: """使用赋值语句,定义类属性,记录创建工具对象的总数""" count = 0"""定义一个类方法,用于打印工具的数量count""" @classmethod def show_tool_count(cls): print("工具对象的总数为:%d" % cls.count)def __init__(self, name): self.name = name """每创建一个类,做一个计数""" Tool.count += 1tool1 = Tool("锤子") tool2 = Tool("扳手") tool3 = Tool("斧头")Tool.show_tool_count()

Python基础|六、Python基础(封装、继承、多态)
文章图片

3.@staticmethod静态方法 在开发时,如果需要在类中封装一个方法,这个方法:
  • 既不需要访问实例属性或者调用实例方法
  • 也不需要访问类属性或者调用类方法
这个时候,可以把这个方法封装成类方法
定义静态方法的代码结构如图所示:
Python基础|六、Python基础(封装、继承、多态)
文章图片

  • 静态方法需要用 修饰器@staticmethod 来标识,告诉解释器这是一个静态方法
类名.静态方法名(参数列表) 可以通过调用静态方法
4.阶段小结:游戏计分板 需求:
  • 设计一个Game类
  • 其中包含两个属性:
    定义一个类属性:top_score 记录游戏的历史最高分
    定义一个实例属性:player_name 记录当前游戏的玩家姓名
  • 其中包含三个方法:
    定义一个静态方法:show_help 显示游戏帮助信息
    定义一个类方法:show_top_score 显示历史最高分
    实例方法:start_game 开始当前玩家的游戏
  • 主程序步骤:
    (1)查看游戏帮助信息
    (2)查看游戏历史最高分——最高分玩家
    (3)创建游戏对象,开始游戏
class Game(object): """Game类""""""类属性:历史最高分""" top_score = 0 """类属性:最高分玩家""" top_player: str = Nonedef __init__(self, player_name): """实例属性:当前游戏的玩家名""" self.player_name = player_name@staticmethod def show_help(): print("--这是游戏帮助信息--")@classmethod def show_top_score(cls): print("历史最高分:%d,玩家:[%s]" % (cls.top_score, cls.top_player))def start_game(self): print("游戏开始") temp_score = int(input("玩家[%s]的最终分数是:")) if temp_score >= Game.top_score: print("恭喜[%s],游戏分数:%d,突破游戏记录" % (self.player_name, temp_score)) Game.top_score = temp_score Game.top_player = self.player_nameelse: print("[%s]的最终得分是:%d" % (self.player_name, temp_score))"""调用类方法无需创建对象""" Game.show_help() Game.show_top_score()lcx = Game("~宪宪") lcx.start_game() lcx.show_top_score()

Python基础|六、Python基础(封装、继承、多态)
文章图片

5.补充:当方法要同时访问类属性和实例属性时 如果方法内部既需要访问类属性,又要访问实例属性时,这个方法应该定义成实例方法
  • 实例方法 方法内部需要访问实例属性,也可以访问类属性
  • 类方法 方法内部只需要访问类属性
  • 静态方法 方法内部不需要访问类属性和实例属性
三、单例 1.单例设计模式 所谓设计模式,即前人的工作总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案——套路
使用设计模式是为了可复用代码、让代码易读性更高、保证代码的可靠性
单例设计模式:
  • 目的 让类创建的对象,在系统中只有唯一的一个实例
  • 每一次执行 类名() 返回的对象,内存地址是相同的
应用场景:
  • 音乐播放对象:同时只能播放一首音乐(一个实例/对象)
  • 回收站对象:所有被清理的软件都被放入同一回收站(一个实例/对象)
  • 打印机对象:打印机只能同时打印一份文件(一个实例/对象)
上述例子都符合只有唯一的一个实例的单例设计模式
2.__new__ 方法
  • 使用 类名() 创建对象时,Python解释器会首先调用 __new__ 方法为对象分配内存空间
  • __new__ 方法是一个由 object 基类提供的内置静态方法,主要作用有两个:
    在内存中为对象分配空间
    返回对象的引用
  • 重写 __new__ 方法的代码十分固定
  • 重写 __new__ 方法一定要 return super().__new__(cls),否则Python解释器得不到分配了空间的对象引用
  • super() 可以访问父类,因此 super().__new__(cls) 实际上是访问基类的 __new__ 方法,用于分配一片内存空间
  • __new__ 是一个静态方法,在调用时需要主动传递 cls 参数
单例——重写 __new__ 方法:
例:
class MusicPlayer(object): def __new__(cls, *args, **kwargs): print("创建对象,分配空间")return super().__new__(cls)def __init__(self): print("音乐播放器初始化")"""创建播放器对象""" player = MusicPlayer() print(player)

Python基础|六、Python基础(封装、继承、多态)
文章图片

3.Python中的单例 让类创建的对象,在系统中只有唯一的一个实例:
  • 重写 __new__ 方法 ,其中:
    如果类属性 is None,调用父类方法分配空间,并在类属性中获得返回结果
  • 返回类属性中记录的对象引用
Python基础|六、Python基础(封装、继承、多态)
文章图片

(图摘自黑马程序员)
提示:如果已经首次创建过实例了,则上述类属性就不为None了,因此只要不重新分配空间,instance 就一直是首次创建实例时的引用,直接返回 instance,即直接返回首次创建实例时的引用,那么每次创建对象时的地址都是唯一的,实现单例
例:
class MusicPlayer(object): """音乐播放器""""""记录实例的引用情况""" instance = Nonedef __new__(cls, *args, **kwargs): """如果没有实例,就调用父类方法分配空间""" if cls.instance is None: cls.instance = super().__new__(cls) """如果已有实例,就分配内存,直接返回原有实例,实现地址唯一"""return cls.instancedef __init__(self): print("音乐播放器初始化")"""创建多个对象验证地址是否相同""" player1 = MusicPlayer() print(player1) player2 = MusicPlayer() print(player2)

Python基础|六、Python基础(封装、继承、多态)
文章图片

Python基础|六、Python基础(封装、继承、多态)
文章图片

冗余性解决:既然单例场景中创建的对象都是同一个实例,那么如果每次创建一个实例,类中每次都会自动进行一次 __init__ 初始化动作,造成冗余,那么有没有一种办法,可以让初始化动作只被执行一次呢?
解决方法:
  • 定义一个类属性 init_flag,标记是否执行过初始化动作,初始值为 False
  • 在 __init__ 方法中,判断 init_flag 是否为 False,是则执行初始化动作,然后将 init_flag 设置为 True,限制下一次初始化动作,这样,当下一次再创建实例时,就不会再执行初始化动作了
例:
class MusicPlayer(object): """音乐播放器""""""记录实例的引用情况""" instance = None """记录是否执行过初始化方法""" init_flag = Falsedef __new__(cls, *args, **kwargs): """如果没有实例,就调用父类方法分配空间""" if cls.instance is None: cls.instance = super().__new__(cls) """如果已有实例,就分配内存,直接返回原有实例,实现地址唯一"""return cls.instancedef __init__(self): if not MusicPlayer.init_flag: print("执行初始化动作") MusicPlayer.init_flag = True"""创建多个对象验证地址是否相同""" player1 = MusicPlayer() player2 = MusicPlayer() player3 = MusicPlayer()

于是,我们即使创建了多个实例, __init__ 初始化动作也只被执行一次了
Python基础|六、Python基础(封装、继承、多态)
文章图片

上一篇文章
  • 五、Python基础(类与对象)
下一篇文章

    推荐阅读