面向对象特性

面向对象特性 面向对象的三大特性:封装、继承、多态 1 封装 根据职责将属性和方法封装到一个抽象的类中 2 继承 实现代码的重用,相同的代码不需要重复的编
面向对象特性
面向对象的三大特性:封装继承多态
1. 封装 根据职责将属性和方法封装到一个抽象的类中
2. 继承 实现代码的重用,相同的代码不需要重复的编写
3. 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
 
01-单继承
1.1 继承语法
子类继承父类,拥有父类的所有属性和方法,开发时只需要根据职责在子类中封装子类独有的属性和方法即可
class 类名(父类名)
若C类从B类继承,B类从A类继承,那么C类具有B类和A的所有属性和方法
子类=派生类,父类=基类,继承=派生
1.2 方法的重写
如果父类的方法无法满足子类的需求,可以对方法进行重写,重写情况有两种,分别为覆盖父类的方法,对父类方法的扩展
覆盖:在子类中重新定义一个和父类同名的方法进行实现,这样运行时只会调用子类的方法,而不会调用父类的方法
扩展:父类的方法只是子类的一部分,就可以先对子类进行重写,在需要父类功能的地方使用 super().父类方法 进行调用
super().父类方法 可以理解为对父类方法的全部继承
eg:super().bark()
# 另一种方式
父类名.父类方法(self)
Dog.run(self)
注意:父类名和super()不可混用,且不可使用当前子类名调用当前方法,否则会陷入递归调用,出现死循环
# 错误案例
class Dog:
    def bark(self):
        print("吠")
        Dog.bark(self)
1.3 父类的私有属性和方法
和之前的私有属性和私有方法一致,子类和子类的对象无法通过直接方法访问父类的私有属性和方法,父类的对象也无法直接访问父类的私有属性和方法
但仍可以用间接的方法访问,可用间接方法于子类方法的继承和父类对象的调用
# 子类继承父类的私有方法
class Father:
    def __way(self):
        print("This is father's private property.")
 
class Son(Father):
    def __way(self):
        super()._Father__way()
 
# 父类对象调用父类私有方法
father = Father()
father._Father__way()
 
# 无限套娃——子类对象调用子类私有方法且子类私有方法调用父类私有方法
son = Son()
son._Son__way()
注意:实际开发中同样不推荐
 
02-多继承
2.1 多继承的使用和注意事项
一个子类可以有多个父类进行继承
class 子类名(父类1, 父类2, ...)
如果不同子类存在同名方法,会按照父类顺序进行查找,找到了就直接返回
class Father_1:
    def hello(self):
        print("hello world")
 
 
class Father_2:
    def hello(self):
        print("hello python")
 
 
class Son(Father_1, Father_2):
    def say_hello(self):
        super().hello()
 
 
son = Son()
son.say_hello()
# 在这里输出的就是hello world
2.2 Python中的方法搜索顺序
可以使用 类名.mro() 的方法查询方法的搜索顺序,也可以在多继承时判断方法属性调用路径
# 以上述为例
print(Son.mro())
# 输出结果:[<class '__main__.Son'>, <class '__main__.Father_1'>, <class '__main__.Father_2'>, <class 'object'>]
所以我们也可以看出来多继承的执行顺序是先查找当前类,再查看父类1,再看父类2,最后再去object类,找不到才会报错
2.3 新式类与旧式类
在python 2.x 版本中定义类,如果没有指定父类,则默认不使用object类(object类是所有类的父),需要使用自己打
在python 3.x 版本中定义类,如果没有指定父类,则默认使用object类,即可以调用__str__, __init__ 等方法
如果为了让代码在python 2.x和python 3.x中同时运行,建议没有父类时统一继承object类
class 类名(object)
 
03-多态
通过不同子类对象,调用相同父类方法,产生不同执行结果,就是多态
多态的目的就是提升代码的灵活性,相当于父类的方法是这类方法的主体框架,子类只需要根据不同的对象需求进行调整,对相同功能的父类代码进行继承与重写
class Dog(object):
    def __init__(self, name):
        self.name = name
 
    def game(self):
        print("%s在玩耍" % self.name)
 
 
class XiaoTianDog(Dog):
    def game(self):
        print("%s在天上玩" % self.name)
 
 
class Person(object):
    def __init__(self, name):
        self.name = name
 
    def game_with_dog(self, dog):
        print("%s与%s在快乐的玩耍" % (self.name, dog.name))
 
 
# xiaohu = Dog("旺财")
# xiaohu.game()
xiaohu = XiaoTianDog("飞天旺财")
xiaohu.game()
xiaoming = Person("小明")
xiaoming.game_with_dog(xiaohu)
在这里我们可以看到,虽然都是game方法,但是由于是由不同的子类产生的不同的对象,且game方法在不同的子类中已经进行了重写,所以产生了不同的结果
 
03-类属性和类方法
3.1 类的结构
在面向对象的开发中,首先需要创建类;在创建类后,通过 类名() 创建对象,称为类的实例化,创建出来的对象称为实例
对象的属性称为实例属性,对象调用的方法称为实例方法
对象拥有自己的实例属性,并且可以用 self 调用自己的方法和属性或者其他的对象方法
每一个对象都有自己的独立内存,保存着各自的属性,但是多个对象的方法在内存中只有一份,调用时是需要将对象的引用传递到方法内部的self
3.2 类对象
类是一个特殊的对象,称为类对象,在程序运行时也会被加载到内存,且仅有一份,通过类对象可以创建出实例对象
类对象有自己的属性和方法,称为类方法类属性,通过 类名.属性/类名.方法 进行调用,该类的所有对象都可以通过类名直接访问
3.3 类属性和实例属性
类属性是给类对象定义的属性,记录这个类相关的特征,并不会影响到具体对象的特征,直接在类名下使用赋值语句即可,调用是使用 类名.类属性 进行调用
class Tool(object):
    count = 0
 
    def __init__(self, name):
        self.name = name
        Tool.count += 1
由于python对于属性的获取有向上查找的机制,即在当前对象找不到属性会向上寻找类属性,所以还可以使用 对象名.类属性 进行调用,但并不推荐
注意:对象.类属性 = 值 的赋值语句只会给对象添加属性,而不会改变类属性的值
tieqiao.count = 0
print(tieqiao.count)
print(Tool.count)
3.4 类方法和静态方法
3.4.1 类方法
类方法是针对类定义的方法,可在类方法内直接访问类属性或者调用其他类方法
@classmethod
def 类方法(cls):
    pass
类方法要用@classmethod来修饰,第一个参数任意,习惯使用cls,可以理解为实例方法的self,cls调用的方法是当前类的引用
使用类方法是,不需要传入cls参数,直接 类名. 即可;在方法内部,可以通过 cls. 访问类属性和其他类方法
3.4.2 静态方法
如果在类中封装的方法既不需要访问实例属性和实例方法,也不需要访问类属性和类方法,就可以封装成静态方法,通过 类名. 调用静态方法
@staticmethod
def 静态方法():
    pass
3.4.3 案例示范
需求:
1.设计一个 Game
2.属性:①定义一个 类属性 top_score 记录游戏的 历史最高分
         ②定义一个 实例属性 player_game 记录 当前游戏的玩家姓名
3.方法:①静态方法 show_help 显示游戏帮助信息
         ②类方法 show_top_score 显示历史最高分
         ③实例方法 start_game 开始当前玩家的游戏
4.主程序步骤:①查看帮助信息
                     ②查看历史最高分
                 ③创建游戏对象,开始游戏
class Game(object):
    top_score = 0
 
    def __init__(self, player_name):
        self.player_name = player_name
 
    def start_game(self):
        print("开始游戏...")
        Game.top_score = 999
 
    @classmethod
    def show_top_score(cls):
        print("最高分是%d" % cls.top_score)
 
    @staticmethod
    def show_help():
        print("This is help.")
 
 
Game.show_help()
Game.show_top_score()
xiaoming = Game("小明")
xiaoming.start_game()
Game.show_top_score()
 
04-单例
单例是为了让类创建的对象,在系统中只有唯一的一个实例,每一次执行 类名() 返回的对象,内存地址是相同的
如音乐播放器,每次只能播放一首歌曲,每一个歌曲(实例)在结束(返回)后都返回相同的内存地址,以便下一首歌的播放
4.1 __new__ 方法
使用 类名() 创建对象的时候,首先使用object类内置的静态方法__new__来分配空间,并返回对象的引用到python解释器,解释器接收到
对象的引用后,将引用作为参数传递给__init__方法,由它进行对象的初始化并定义实例属性
而为了达到单例设计模式,需要对__new__方法进行重写,使得无论执行多少次,返回相同的内存地址,使得只创建一个对象实例
4.2 重写 __new__ 方法
重写 __new__ 方法需要在最后加上 return super().__new__(cls) 来返回内存地址,否则python解释器得不到对象引用就无法进行对象的初始化
def __new__(cls, *args, **kwargs):
    print("创建对象,分配空间")
    # 在object类中,默认有实现分配内存空间的功能,所以只需要用super()调用父类方法即可,并在 return 中返回
    return super().__new__(cls)
注意:__new__方法是一个静态方法,需要主动传递cls参数
4.3 Python中的单例
4.3.1 实现单例的相同引用
定义一个类属性(instance),初始值定为None,用于记录单例对象的引用,同时重写__new__方法
刚开始定义对象实例时,调用父类__new__方法分配空间,并返回,之后的定义跳过分配空间,直接使用原引用,实现单例
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, name):
        self.name = name
 
 
player1 = MusicPlayer("1")
player2 = MusicPlayer("2")
print(player1)
print(player2)
可以发现这里即使名字不一样,引用也是相同的
4.3.2 实现单例的一次初始化
4.3.1实现了相同引用,但是每次还要初始化,略显冗杂,先仿照上述方法对__init__方法进行修改,同样定义一个用来记录初始化的类属性(init_flag),初始值为False
def __init__(self):
    if not MusicPlayer.init_flag:
        print("初始化")
        MusicPlayer.init_flag = True