Python面向对象之三大特征-继承
继承
【一】概要
- 类继承是面向对象编程中的一种重要概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。继承使得子类能够重用父类的代码,同时可以在子类中添加新的方法或属性,或者重写父类的方法,以满足特定的需求。
- 在类继承中,子类继承了父类的属性和方法,这包括实例变量、类变量、以及父类中定义的方法。子类可以通过继承获得父类的特征,从而避免重复编写相似的代码。父类通常包含一般性的特征,而子类则可以根据需要添加、修改或覆盖这些特征。
【二】常用方法
'''单继承'''
class Person(object):
eyes = 2
@staticmethod
def eat():
print(f"需要吃饭")
class Chinese(Person): # 通过小括号中填入父类,实现单继承,【类Chinese】继承了【类Person】
skin = 'yellow'
@staticmethod
def eat_with():
print("吃饭用筷子")
c = Chinese()
print(c.eyes) # 可以调用父类的数据属性
c.eat() # 可以调用父类的函数属性(方法)
# 2
# 需要吃饭
'''多继承'''
class ShangHai(Chinese, Person): # 小括号中的值可以传入多个,实现多继承
# 【类shanghai】继承了【chinese和person两个类】
language = '侬好'
s = ShangHai()
print(s.eyes) # 可以调用【类Person】中的属性
s.eat_with() # 可以调用【类Chinese】中的方法
print(s.language)
# 2
# 需要吃饭
# 吃饭用筷子
# 侬好
【三】详解
【1】什么是继承
- 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
- 子类会“”遗传”父类的属性,从而解决代码重用问题(去掉冗余的代码)
【2】经典类和新式类
- 在Python2时,会有区分经典类和新式类的区别
- 经典类:没有显示的继承【基类object】的类,以及该类的子类
class 类名
- 新式类:显示的声明继承【基类object】的类,以及该类的子类,都是新式类
class 类名(object)
- 经典类:没有显示的继承【基类object】的类,以及该类的子类
- 在Python3中,统一都是新式类
- 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
class Earth: # 如果运行环境是Python2,那么【类Earth】就是经典类
pass
class Person(): # 没有显式的继承object,默认继承object
pass
class Animal(object):
pass
'''isinstance()可以判断第一个参数是否属于第二个参数类型'''
print(isinstance(Earth, object)) # True
print(isinstance(Person, object)) # True
print(isinstance(Animal, object)) # True
【3】查看继承__base__
和__bases__
__base__
:查看父类__bases__
:查看所有的父类
class Earth(object):
pass
class Person(object):
pass
class Chinese(Person):
pass
class ShangHai(Earth,Chinese):
pass
print(Person.__base__)
# <class 'object'>
print(ShangHai.__base__) # 如果继承了多个类,将会按照就近原则取最近的父类
# <class '__main__.Earth'>
print(ShangHai.__bases__)
# (<class '__main__.Earth'>, <class '__main__.Chinese'>)
【4】继承的重用性
- 类继承,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。继承使得子类能够重用父类的代码,同时可以在子类中添加新的方法或属性,或者重写父类的方法
# 定义一个父类
class Father(object):
eyes = 2
mouth = 1
def run(self):
print("这是父类中的run函数")
class Son(Father):
print(f"子类继承父类中的属性。父子都有{Father.eyes} 只眼睛,{Father.mouth} 张嘴。")
'''也可以重写父类中的方法'''
def run(self):
print("这是子类中的run函数")
def read(self):
print("这是子类中的read函数,是父类所没有的")
s = Son() # 子类继承父类中的属性。父子都有2 只眼睛,1 张嘴。
s.run() # 这是子类中的run函数
s.read() # 这是子类中的read函数,是父类所没有的
Father.run(self=s) # 这是父类中的run函数
- 当我们想要定义一些类,这些类中都有一些相似的特征,我们就可以想到将这些特征抽象出来,为他们定义一个父类
【e.g.】我需要猫和狗两个类
# 定义猫类
class Cat():
def eat(self):
print("猫可以吃东西")
def drink(self):
print("猫可以喝水")
def miaow(self):
print("猫会喵喵叫~")
# 定义狗类
class Dog():
def eat(self):
print("狗可以吃东西")
def drink(self):
print("狗可以喝水")
def bark(self):
print("狗会旺旺叫~")
'''通过查看代码,我们会发现,猫和狗都会吃和喝,以及所有的动物都是会吃喝的,唯一的区别在于,猫是喵,狗是汪'''
'''于是,我们就可以根据相似的特征,定义一个父类Animal,将吃喝作为动物类共有的特征(属性)'''
# 定义动物类
class Animal(object):
def eat(self):
print(f"{self.__class__.__name__}可以吃东西")
def drink(self):
print(f"{self.__class__.__name__}可以喝水")
# 定义猫类
class Cat(Animal):
def miaow(self):
print("猫会喵喵叫~")
# 定义狗类
class Dog(Animal):
def bark(self):
print("狗会旺旺叫~")
# 实例化猫猫
cat = Cat()
cat.eat() # Cat可以吃东西
cat.drink() # Cat可以喝水
cat.miaow() # 猫会喵喵叫~
# 实例化狗子
dog = Dog()
dog.eat() # Dog可以吃东西
dog.drink() # Dog可以喝水
dog.bark() # 狗会旺旺叫~
'''这样就可以减少代码冗余'''
class Hero(object):
# 也是可以继承魔法方法的
def __init__(self, name, attack, health):
self.name = name
self.attack = attack
self.health = health
def hero_attack(self, enemy):
enemy.health -= self.attack
print(f"{self.name} 攻击了 {enemy.name} {self.attack} 滴血!")
class Hero_one(Hero):
pass
class Hero_two(Hero):
pass
one = Hero_one("one", 10, 50)
two = Hero_two("two", 5, 75)
one.hero_attack(two) # one 攻击了 two 10 滴血!
two.hero_attack(one) # two 攻击了 one 5 滴血!
【5】隐藏父类属性
- 父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
'''小练习:最后打印出的内容是什么?'''
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print('Bar.f1')
# 实例化类得到对象
b = Bar()
b.f2() # ?
class Foo:
# 【4】在父类中找到了f2
def f1(self):
print('Foo.f1')
def f2(self):
# 【5】执行f2
print('Foo.f2')
self.f1()
# 【6】调用了f1,思考,此时的self是哪个对象,self.f1,是Foo.f1还是Bar.f1?
# 【7】此时,函数是由Bar的实例化对象b调用的,谁调用的类,self参数就是谁
# 【8】所以,self.f1() 调用 Bar.f1 ,输出 Bar.f1
class Bar(Foo):
# 【2】并没有找到f2
def f1(self):
print('Bar.f1')
# 实例化类得到对象
b = Bar()
b.f2()
# 【1】先从自己本身的属性中查找
# 【3】在本身没有找到,就去父类中查找
- 如果父类,将属性隐藏起来,将属性私有化了,那么子类便无法调用属性了
class Foo:
def __f1(self):
# 将f1属性,私有化
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1() # 此时,self.f1()是否还与未隐藏时输出内容一致?
class Bar(Foo):
def f1(self):
print('Bar.f1')
# 实例化类得到对象
b = Bar()
b.f2()
# 输出
# Foo.f2
# Bar.f1
【6】多继承
- python支持多继承,用逗号分隔开多个继承的类
class 类名(父类1,父类2,......)
,理论上不限制继承的父类数量,但一般建议不要继承那么多父类
class A():
'''代码块'''
pass
class B():
'''代码块'''
pass
class C():
'''代码块'''
pass
class D(A,B,C): # 类D就是继承了A,B,C三个类,可以调用A,B,C中的属性
pass
【7】类属性查找顺序
- 类属性查找顺序,Python2 与 Python3 有所区别
- 在 Python 2 中,类属性的查找顺序是按照深度优先(Depth-First Search,DFS)来进行的。这意味着在多重继承的情况下,首先查找当前类,然后再递归地查找父类,一直沿着继承链向下查找,直到找到属性或者到达继承链的末端。
- Python 3 引入了 C3 线性化算法(C3 Linearization),该算法使用广度优先的方式来确定方法和属性的查找顺序,解决了多重继承中的一些问题,提供了更一致和可预测的属性查找顺序。在 Python 3 中,类属性的查找顺序更符合直觉,并且遵循 C3 线性化算法。
(1)Python2的查找顺序
- 当类是经典类时,按照深度优先的方式查找下去
- 当类是新式类时,按照广度优先的方式查找下去
(2)Python3的查找顺序
__mro__
:通过__mro__
方法来查看属性的查找顺序__mro__
是一个类的元类顺序(Method Resolution Order,MRO)元组。MRO 定义了在多继承环境中,Python 解释器查找方法和属性的顺序。__mro__
元组显示了类的基类搜索顺序。
- 需要注意,当多重继承过于复杂,可能会触发TypeError
- 在 Python 中,MRO 是按照 C3 线性化算法来确定的,它确保了在多继承中的方法查找顺序。但是,在某些情况下,由于继承关系的复杂性,可能会导致无法创建一致的 MRO,从而引发
TypeError
。
- 在 Python 中,MRO 是按照 C3 线性化算法来确定的,它确保了在多继承中的方法查找顺序。但是,在某些情况下,由于继承关系的复杂性,可能会导致无法创建一致的 MRO,从而引发
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D, E):
pass
f1 = F()
f1.test()
print(F.__mro__)
# 简单来说:# 新式类继承顺序:F->D->B->E->C->A
# (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
'''当继承类型并没有变成菱形时,将会按照深度优先查找至基类'''
# F - D - B- Z - E - C - A - object
class A(object):
def test(self):
print('from A')
class Z(object): # 新增了类Z
def test(self):
print('from Z')
class B(Z): # B(A)变为了B(Z) # 这样将导致菱形结构未闭合
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D, E):
pass
f1 = F()
f1.test()
print(F.__mro__)
在 C3 线性化规则中,属性查找的顺序如下:
- 深度优先: 首先按照继承树的深度进行查找,即首先查找当前类,然后是它的父类,再是父类的父类,以此类推。
- 从左到右: 在同一深度级别上,按照继承列表中的顺序进行查找。这是指在类定义时,继承的父类列表中,从左到右的顺序。
【8】Mixins机制:减少多继承分支
- 多继承的设计,常常被人诟病,因为又复杂,又容易产生属性重名等问题
- 所以引入了Minxins(混入)机制
(1)概念和特点
- Mixins 是一种设计模式,它是一种通过组合多个小而独立的类来实现代码重用和组件化的方法。Mixins 不是一种特定的技术栈,而是一种通用的编程概念。
- 在面向对象编程中,Mixins 通过将一些通用的功能模块化,然后通过多重继承的方式将它们组合到一个类中,从而实现了代码的重用和可组合性。Mixins 不独立存在,而是以一种松耦合的方式与其他类组合在一起,使得代码更加灵活和易于维护。
主要特点包括:
- 独立性低: Mixin 类通常不是独立的实体,它们不应该被单独实例化。它们旨在与其他类组合使用,提供额外的功能。
- 单一职责: 每个 Mixin 类应该具有清晰的、单一的功能,而不是试图实现太多不相关的功能。
- 代码复用: Mixins 通过多继承的方式将功能注入到其他类中,从而实现了代码的复用。
(2)举栗子
-
民航飞机、直升飞机、轿车都是一个交通工具
-
前两者都有一个功能是飞行fly,但是轿车没有
-
如果民航飞机和直升机都各自写自己的飞行fly方法,又违背了代码尽可能重用的原则
- (如果以后飞行工具越来越多,那会重复代码将会越来越多)。
class Vehicle(object):
pass
class airplane(Vehicle):
def fly(self):
pass
class Helicopter(Vehicle):
def fly(self): # 代码冗余
pass
class Car(Vehicle):
pass
- Mixins并不是一种新的技术栈,而只是设计模式
class Vehicle(object):
pass
class FlyMixins(): # 遵循命名规范,以Mixins作为结尾,告知他人这是一个Mixins类
def fly(self):
pass
class Airplane(Vehicle, FlyMixins): # 将Mixins类加入父类,便可以调用功能
pass
class Helicopter(Vehicle, FlyMixins):
pass
class Car(Vehicle):
pass
airplane = Airplane()
airplane.fly()