面向对象的三大属性:
如图:使用继承时,最左端为父类,右端为子类(图摘自黑马程序员)
单继承的代码结构如图所示:
专业术语,在上图中:
例:Animal 是父类,Dog是子类,Dog类 可以享用 Animal类 中的方法和属性
class Animal:
def eat(self):
pass
def run(self):
pass
def drink(self):
pass
class Dog(Animal):
def shout(self):
pass
(1)情况1:覆盖父类的方法
有时候,子类的某些属性或方法的实现可能并不想和父类相同,如子狗的毛色和父狗的毛色可能不一样,这个时候就需要用 方法的重写 来覆盖父类中的属性或方法,如图:
覆盖方法:直接在子类中重新编写与 “需要覆盖的方法” 同名的方法即可
在重写之后,在子类对象调用方法或属性时,只会调用子类重写后的方法(因为已覆盖)
例:
class Animal:
def eat(self):
pass
def run(self):
pass
class Dog(Animal):
def run(self):
pass
(2)情况2:对父类的方法进行扩展
对于父类的一个方法,若子类除 父类原有方法的实现 需要实现外,仍有另一部分也需要实现(而父类没有的那部分),则子类需要在父类的方法的基础上进行扩展
super().父类方法
可以在覆盖父类方法后的方法里,调用原父类方法,实现父类方法的再现
我们只需先覆盖掉原父类的方法,写入需要扩展的代码;再在覆盖父类方法的基础上,在需要的位置利用 super().父类方法 调用回原父类方法(父类方法的再现)即可
例:
class Animal:
def eat(self):
pass
def run(self):
pass
class Dog(Animal):
def run(self):
"""另外补充需要扩展的代码即可"""
super().run()
"""另外补充需要扩展的代码即可"""
pass
例:通过父类的公有方法间接访问到父类的私有属性和私有方法
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()
模糊判断:一般是按定义多继承时 class 子类(父类1…) 从左至右的顺序查找
*对上述问题调用优先级的清晰解答——MRO方法搜索顺序(了解):
Python中针对类提供了一个内置属性 __mro__ 可以查看方法搜索顺序,主要用与在多继承时判断方法、属性的调用路径
print(子类.__mro__)
显示调用方法/属性时的顺序,从左到右显示的父类来顺序查找,找到即执行,找不到即向右查找
其中,中,object类是所有类的基类
object类是一些对象通用的方法,如:字符串的方法…
在定义父类时,可能会看到有 class 父类(object): ,这是因为在Python3.X中,类默认是基于object的新类来定义的,在Python2.X中,默认用的是经典类,一般不推荐使用经典类,因此在创建父类时,可以加上(object)
不同的子类对象调用相同的父类方法时,可以通过方法的重写和继承来产生不同的执行效果
在其他类创建的对象中调用多态类创建的对象时:其他类创建的对象调用多态类创建的对象,由于方法/属性的重写的继承,调用相同的方法可能产生不同的执行结果,形成多态
class Transportation:
"""运输父类"""
def __init__(self, vehicle):
"""属性:交通工具名"""
self.vehicle = vehicle
def arrive(self):
print("%s成功到达指定地点" % self.vehicle)
class Plane(Transportation):
"""飞机子类,基于运输父类"""
def arrive(self):
"""方法的重写"""
print("%s以最快的速度到达指定地点" % self.vehicle)
class Person:
"""人类"""
def __init__(self, name):
"""属性:人名"""
self.name = name
def choice(self, vehicle_choice):
"""选择的交通工具"""
vehicle_choice.arrive()
"""波士顿:飞机"""
Boston = Plane("Boston")
"""泰坦尼克号:轮船"""
Titanic = Transportation("Titanic")
"""约翰:人"""
John = Person("John")
"""约翰选择不同的交通工具,会带来不同的结果,形成多态"""
John.choice(Boston)
John.choice(Titanic)
在Python中,一切皆对象,类是一个特殊的对象,class 属于类对象,object = class() 属于实例对象,因此Python同样会为类分配内存
在创建类时,我们会用 __init__ 来定义对象的属性,但另外,类也有它的属性,类的属性不需要写在方法内,而直接写在类的内部(class定义类的下方)
类属性就是给类对象中定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征
例:
class Tool:
"""使用赋值语句,定义类属性,记录创建工具对象的总数"""
count = 0
def __init__(self, name):
self.name = name
"""每创建一个类,做一个计数"""
Tool.count += 1
tool1 = Tool("锤子")
tool2 = Tool("扳手")
tool3 = Tool("斧头")
print("可以用类名来访问类属性:%d" % Tool.count)
for temp_object in (tool1, tool2, tool3):
print("而且可以通过对象(实例)来访问类属性:%d" % temp_object.count)
类名.类属性
可以在类内部或外部访问到类属性
实例名.类属性
也可以访问到类属性,但不推荐
注:赋值陷阱
在类的外部可以通过 对象.属性 = 属性值 会给对象定义一个对象属性
类方法就是针对类的对象定义的方法
类名.类方法名(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 += 1
tool1 = Tool("锤子")
tool2 = Tool("扳手")
tool3 = Tool("斧头")
Tool.show_tool_count()
在开发时,如果需要在类中封装一个方法,这个方法:
这个时候,可以把这个方法封装成类方法
类名.静态方法名(参数列表)
可以通过调用静态方法
需求:
class Game(object):
"""Game类"""
"""类属性:历史最高分"""
top_score = 0
"""类属性:最高分玩家"""
top_player: str = None
def __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_name
else:
print("[%s]的最终得分是:%d" % (self.player_name, temp_score))
"""调用类方法无需创建对象"""
Game.show_help()
Game.show_top_score()
lcx = Game("~宪宪")
lcx.start_game()
lcx.show_top_score()
如果方法内部既需要访问类属性,又要访问实例属性时,这个方法应该定义成实例方法
所谓设计模式,即前人的工作总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案——套路
使用设计模式是为了可复用代码、让代码易读性更高、保证代码的可靠性
单例设计模式:
应用场景:
上述例子都符合只有唯一的一个实例的单例设计模式
单例——重写 __new__ 方法:
例:
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
print("创建对象,分配空间")
return super().__new__(cls)
def __init__(self):
print("音乐播放器初始化")
"""创建播放器对象"""
player = MusicPlayer()
print(player)
让类创建的对象,在系统中只有唯一的一个实例:
提示:如果已经首次创建过实例了,则上述类属性就不为None了,因此只要不重新分配空间,instance 就一直是首次创建实例时的引用,直接返回 instance,即直接返回首次创建实例时的引用,那么每次创建对象时的地址都是唯一的,实现单例
例:
class MusicPlayer(object):
"""音乐播放器"""
"""记录实例的引用情况"""
instance = None
def __new__(cls, *args, **kwargs):
"""如果没有实例,就调用父类方法分配空间"""
if cls.instance is None:
cls.instance = super().__new__(cls)
"""如果已有实例,就分配内存,直接返回原有实例,实现地址唯一"""
return cls.instance
def __init__(self):
print("音乐播放器初始化")
"""创建多个对象验证地址是否相同"""
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
冗余性解决:既然单例场景中创建的对象都是同一个实例,那么如果每次创建一个实例,类中每次都会自动进行一次 __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()
player2 = MusicPlayer()
player3 = MusicPlayer()
于是,我们即使创建了多个实例, __init__ 初始化动作也只被执行一次了