python 继承和多态

python继承

概述

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”,继承的过程,就是从一般到特殊的过程。在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。

继承概念的实现方式主要有2类:实现继承、接口继承。

  1. 实现继承是指使用基类的属性和方法而无需额外编码的能力。
  2. 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法)。

在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

类的继承

继承的定义

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    pass

class Cat(Animal):
    pass

dog = Dog()
dog.run()

cat = Cat()
cat.run()

构造函数的继承

如果我们要给实例 c 传参,我们就要使用到构造函数,那么构造函数该如何继承,同时子类中又如何定义自己的属性?

继承类的构造方法:

​ 1.经典类的写法: 父类名称.init(self,参数1,参数2,…)

​ \2. 新式类的写法:super(子类,self).init(参数1,参数2,…)

class Person(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.weight = 'weight'

    def talk(self):
        print("person is talking....")


class Chinese(Person):

    def __init__(self, name, age, language):  # 先继承,在重构
        Person.__init__(self, name, age)  # 继承父类的构造方法,也可以写成:super(Chinese,self).__init__(name,age)
        self.language = language  # 定义类的本身属性

    def walk(self):
        print('is walking...')


class American(Person):
    pass


c = Chinese('bigberg', 22, 'Chinese')

如果我们只是简单的在子类Chinese中定义一个构造函数,其实就是在重构。这样子类就不能继承父类的属性了。所以我们在定义子类的构造函数时,要先继承再构造,这样我们也能获取父类的属性了。

子类构造函数基础父类构造函数过程如下:

实例化对象c ----> c 调用子类__init__() ---- > 子类__init__()继承父类__init__() ----- > 调用父类 init()

img

子类对父类方法的重写

如果我们对基类/父类的方法需要修改,可以在子类中重构该方法。如下的talk()方法

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def run(self):
        print('hello')

class Cat(Animal):
    pass

dog = Dog()
dog.run()

cat = Cat()
cat.run()

类继承的事例

class SchoolMember(object):
    '''学习成员基类'''
    member = 0
 
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex
        self.enroll()
 
    def enroll(self):
        '注册'
        print('just enrolled a new school member [%s].' % self.name)
        SchoolMember.member += 1
 
    def tell(self):
        print('----%s----' % self.name)
        for k, v in self.__dict__.items():
            print(k, v)
        print('----end-----')
 
    def __del__(self):
        print('开除了[%s]' % self.name)
        SchoolMember.member -= 1
 
 
class Teacher(SchoolMember):
    '教师'
    def __init__(self, name, age, sex, salary, course):
        SchoolMember.__init__(self, name, age, sex)
        self.salary = salary
        self.course = course
 
    def teaching(self):
        print('Teacher [%s] is teaching [%s]' % (self.name, self.course))
 
 
class Student(SchoolMember):
    '学生'
 
    def __init__(self, name, age, sex, course, tuition):
        SchoolMember.__init__(self, name, age, sex)
        self.course = course
        self.tuition = tuition
        self.amount = 0
 
    def pay_tuition(self, amount):
        print('student [%s] has just paied [%s]' % (self.name, amount))
        self.amount += amount
 
t1 = Teacher('Wusir', 28, 'M', 3000, 'python')
t1.tell()
s1 = Student('haitao', 38, 'M', 'python', 30000)
s1.tell()
s2 = Student('lichuang', 12, 'M', 'python', 11000)
print(SchoolMember.member)
del s2
 
print(SchoolMember.member)
 
 
 
# 输出
----end-----
just enrolled a new school member [haitao].
----haitao----
age 38
sex M
name haitao
amount 0
course python
tuition 30000
----end-----
just enrolled a new school member [lichuang].
3
开除了[lichuang]
2
开除了[Wusir]
开除了[haitao]

Python 类的多态

在说明多态是什么之前,我们在 Child 类中重写 print_title() 方法:若为male,print boy;若为female,print girl

class Person(object):
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex
        
    def print_title(self):
        if self.sex == "male":
            print("man")
        elif self.sex == "female":
            print("woman")

class Child(Person):                # Child 继承 Person
    def print_title(self):
        if self.sex == "male":
            print("boy")
        elif self.sex == "female":
            print("girl")
        
May = Child("May","female")
Peter = Person("Peter","male")

print(May.name,May.sex,Peter.name,Peter.sex)
May.print_title()
Peter.print_title()

当子类和父类都存在相同的 print_title()方法时,子类的 print_title() 覆盖了父类的 print_title(),在代码运行时,会调用子类的 print_title()

这样,我们就获得了继承的另一个好处:多态

多态的好处就是,当我们需要传入更多的子类,例如新增 Teenagers、Grownups 等时,我们只需要继承 Person 类型就可以了,而print_title()方法既可以直不重写(即使用Person的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Person的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:

  • 对扩展开放(Open for extension):允许子类重写方法函数
  • 对修改封闭(Closed for modification):不重写,直接继承父类方法函数

子类重写构造函数

子类可以没有构造函数,表示同父类构造一致;子类也可重写构造函数;现在,我们需要在子类 Child 中新增两个属性变量:mother 和 father,我们可以构造如下(建议子类调用父类的构造方法,参见后续代码):

class Person(object):
    def __init__(self,name,sex):
    self.name = name
    self.sex = sex

class Child(Person):                # Child 继承 Person
    def __init__(self,name,sex,mother,father):
        self.name = name
        self.sex = sex
        self.mother = mother
        self.father = father

May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)    

Person

若父类构造函数包含很多属性,子类仅需新增1、2个,会有不少冗余的代码,这边,子类可对父类的构造方法进行调用,参考如下:

class Person(object):
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex

class Child(Person):                          # Child 继承 Person
    def __init__(self,name,sex,mother,father):
        Person.__init__(self,name,sex)        # 子类对父类的构造方法的调用
        self.mother = mother
        self.father = father

May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)

python多重继承

Python虽然支持多继承,但是python支持的多继承也是有限的。

问题的提出

如果不同的父类中存在 同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
Python 中的 MRO —— 方法搜索顺序

  • Python 中针对 类 提供了一个内置属性 mro 可以查看方法搜索顺序

  • MRO 是 method resolution order,主要用于在多继承时判断 方法、属性 的调用 路径

    print(C.__mro__)  #C是多继承后的类名
    

    输出结果

    (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
    
    • 在搜索方法时,是按照 mro 的输出结果 从左至右 的顺序查找的
    • 如果在当前类中 找到方法,就直接执行,不再搜索
    • 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
    • 如果找到最后一个类,还没有找到方法,程序报错

多继承的使用

#1.多继承:子类有多个父类
 
class Human:
    def __init__(self, sex):
        self.sex = sex
 
    def p(self):
        print("这是Human的方法")
 
 
class Person:
    def __init__(self, name):
        self.name = name
 
    def p(self):
        print("这是Person的方法")
 
    def person(self):
        print("这是我person特有的方法")
 
 
class Teacher(Person):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age
 
 
class Student(Human, Person):
    def __init__(self, name, sex, grade):
    #要想调用特定父类的构造器可以使用父类名.__init__方式。多继承使用super,会有一个坑,具体参考后面
       Human.__init__(self,sex)
       Person.__init__(self,name)
       self.grade = grade
 
 
class Son(Human, Teacher):
    def __init__(self, sex, name, age, fan):
        Human.__init__(self, sex)
        Teacher.__init__(self, name, age)
        self.fan = fan
 
 
# ------创建对象 -------------
stu = Student("tom", "male", 88)
print(stu.name,stu.sex,stu.grade)
stu.p()  # 虽然父类Human和Person都有同名P()方法 ,但是调用的是括号里的第一个父类Human的方法
 
 
son1 = Son("jerry", "female", 18, "打球")
son1.person()  # 可以调用父类的父类的方法。
son1.p()  # 子类调用众多父类中同名的方法,按继承的顺序查找。
=====================================================================================
tom male 88
这是Human的方法
这是我person特有的方法
这是Human的方法

总结

  • 需要注意圆括号中继承父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

  • 支持多层父类继承,子类会继承父类所有的属性和方法,包括父类的父类的所有属性 和 方法。

多继承的使用注意事项

#1.多继承子类对父类构造方法的调用
class Human:
    def __init__(self,sex):
        self.sex = sex
    def p(self):
        print("这是Human的方法")
    def str1(self):
        print("this si"+str(self.sex))
 
class Person:
    def __init__(self,name):
        self.name = name
    def p(self):
        print("这是Person的方法")
    def person(self):
        print("这是我person特有的方法")
 
    def str2(self):
        print( "this is:" + str(self.name))
 
class Student(Human,Person): #注意子类如果没有构造方法时,按括号内父类的继承顺序去继承父类构造方法,只继承一个
    def prin(self):
        print("student")
#------创建对象 -------------
#stu1=Studnent("男","tom")报错。
stu = Student("sex") #这里继承的是Huma的构造方法。
stu.p()
stu.str1() 
#stu.str2()报错,因为即使human和person都是一个参数的构造方法,但是这里继承调用的是第一个Human的构造方法
====================================================================================
这是Human的方法
this sisex

总结:

  • 子类从多个父类派生,而子类又没有自己的构造函数时,
  • 按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数;
  • 如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。

多继承时使用super调用父类属性方法的注意事项

不使用super调用父类方法,使用父类名.方法名的形式。
class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')
 
class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        Parent.__init__(self, name) #直接使用父类名.方法名的方式调用父类的__init__方法
        print('Son1的init结束被调用')
 
class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init开始被调用')
        self.gender = gender
        Parent.__init__(self, name) #
        print('Son2的init结束被调用')
 
class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        Son1.__init__(self, name, age)  # 单独调用父类的初始化方法
        Son2.__init__(self, name, gender)
        print('Grandson的init结束被调用')
 
gs = Grandson('grandson', 12, '男') 
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
 
'''执行结果如下:
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
'''

注意:上面代码里当在子类中通过父类名调用时,parent被执行了2次

使用super调用父类中的方法,注意分析程序的执行顺序。
class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')
 
class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init结束被调用')
 
class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')
 
class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        # super(Grandson, self).__init__(name, age, gender) 效果和下面的一样
        super().__init__(name, age, gender)
        print('Grandson的init结束被调用')
 
print(Grandson.__mro__) #搜索顺序
 
gs = Grandson('grandson', 12, '男')
 
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
 
'''结果如下:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
'''

注意:在上面模块中,当在子类中通过super调用父类方法时,parent被执行了1次。

super调用过程:上面gs初始化时,先执行grandson中init方法, 其中的init有super调用,每执行到一次super时,都会从__mro__方法元组中顺序查找搜索。所以先调用son1的init方法,在son1中又有super调用,这个时候就就根据__mro__表去调用son2的init,然后在son2中又有super调用,这个就根据mro表又去调用parent中的init,直到调用object中的init. 所以上面的打印结果如此,要仔细分析执行过程。

提示:

  • super().__init__相对于类名.init,在单继承上用法基本无差
  • 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
  • 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
  • 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
  • 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行
  • 全部父类的方法,这也是为何多继承需要全部传参的一个原因
单继承使用super调用父类方法
class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name)  # 单继承不能提供全部参数
        print('Son1的init结束被调用')

class Grandson(Son1):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        super().__init__(name, age)  # 单继承不能提供全部参数
        print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)

print(‘parent的init开始被调用’)
self.name = name
print(‘parent的init结束被调用’)

class Son1(Parent):
def init(self, name, age):
print(‘Son1的init开始被调用’)
self.age = age
super().init(name) # 单继承不能提供全部参数
print(‘Son1的init结束被调用’)

class Grandson(Son1):
def init(self, name, age, gender):
print(‘Grandson的init开始被调用’)
super().init(name, age) # 单继承不能提供全部参数
print(‘Grandson的init结束被调用’)

gs = Grandson(‘grandson’, 12, ‘男’)
print(‘姓名:’, gs.name)
print(‘年龄:’, gs.age)


猜你喜欢

转载自blog.csdn.net/qq_45205390/article/details/104838059