python之面向对象介绍(四)——继承与多态


对象的三大特性分别是:

  1. 封装(确保对象中的数据安全)
  2. 继承(保证了对象的可扩展性)
  3. 多态(保证了程序的灵活)
    我们在面向对象介绍(三)中介绍了封装,现在就让我们来细说对象的继承和多态

1 继承

1.1 继承的简介

1.1.1 继承的引入

情形1:

# 情形1
# 创建一个医生的类
class Doctor():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def work(self):
        print('治疗病人.....')
#创建一个军人的类
class Soldie():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def work(self):
        print('保卫国家....')
#创建一个教师的类
class Teacher():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def work(self):
        print('教书育人....')

# 评价:三个类都存在一个共同点是初始化相同。作为人,都有姓名和名字
# 这样是否可以考虑创建一个人的类,然后让上面三个类继承人的类的方法或者属性
class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age

情形2:

# 情形2
# 已有一个动物类(跑、睡),需要一个狗类(跑、睡、叫)
# 方法1:直接修改动物类,在这个类中添加我们需要的功能 | 隐患:修改起来比较麻烦,违反了ocp原则
# 方法2:直接创建一个新的类,这个类就是狗类 | 隐患:创建一个新类比较麻烦,需要大量的复制粘贴。维护修改起来比较麻烦

# 方法1
class Animal:
    def run(self):
        print('动物跑.....')
    def sleep(self):
        print('动物睡觉....')
    # def speak(self):
    #
    #     print('旺旺.....')

# 方法2
class Dog:
    def run(self):
        print('动物跑.....')
    def sleep(self):
        print('动物睡觉....')
    def speak(self):
        print('旺旺.....')
# 通过以上情形,我们同样可以通过创建一个动物的类,承接所有动物的方法和属性,并让特定动物继承其功能

综上,我们大体就知道继承讲的是什么了?

  • 定义:面向对象的三大特性之一,通过继承我们可以使一个类获取到其他类中的属性和方法
  • 特点:(1)提高了代码的复用性;(2)让类与类之间产生了关系。有了这个关系,才有多态的特性
# 情形1 解决
class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
# 创建一个医生的类
class Doctor(Person):  # 继承Person的类
    def __init__(self,name,age):
        super().__init__(name,age) #  通过super()可以直接获取当前类的父类
    def work(self):
        print('治疗病人.....')

# 情形2 解决
class Animal:
    def run(self):
        print('动物跑.....')
    def sleep(self):
        print('动物睡觉....')
class Dog(Animal):  #继承Animal的类 
    def speak(self):
        print('旺旺.....')

1.1.2 继承的语法

  1. 语法就是 class 子类(父类):
# 1. 语法就是 class 子类(父类):
class Parent_Class:
    print('我是父类')
class Son_Class(Parent_Class):  # 语法就是 (父类)
    print('我是子类,继承了父类')
son_class = Son_Class()
print(son_class)  # 结果: 我是父类  我是子类,继承了父类  <__main__.Son_Class object at 0x000001CA8E405908>
# Son_Class()的实例化对象son_class继承了其自身和父类的语法
  1. 检验是否是其父类的语法:issubclass(子类,父类)
# 2.检验是否是其父类的语法:issubclass(子类,父类)
print(issubclass(Son_Class,Parent_Class))  # 结果为True
  1. object是所有对象的父类
# 3.object是所有对象的父类
print(issubclass(int,object))   # 结果为True
print(issubclass(Son_Class,object))   # 结果为True
print(issubclass(Parent_Class,object))   # 结果为True

1.1.3 继承的顺序

当我们调⽤⼀个对象的方法时:

  • 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
  • 如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法
  • 如果没有,则去父类中的父类寻找,以此类推,直到找到object,如果依然没有找到就报错了
# 继承顺序
'''
当我们调⽤⼀个对象的方法时:
- 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
- 如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法
- 如果没有,则去父类中的父类寻找,以此类推,直到找到object,如果依然没有找到就报错了
'''
class A(object):
    def test(self):
        print('A....')
class B(A):
    def test(self):
        print('B....')
class C(B):
    def test(self):
        print('C....')
c = C()
c.test()  # C....   (会优先去当前对象中寻找是否具有该方法,如果有则直接调用)

class A(object):
    def test(self):
        print('A....')
class B(A):
    def test(self):
        print('B....')
class C(B):
    pass
c = C()
c.test()  # B....   (如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法)

class A(object):
    def test(self):
        print('A....')
class B(A):
    pass
class C(B):
    pass
c = C()
c.test()  # A....   (如果没有,则去父类中的父类寻找,以此类推)
c.speak() #报错: AttributeError: 'C' object has no attribute 'speak'  # 子类和父类都没有 直到找到object,如果依然没有找到就报错了

1.2 继承的使用

1.2.1 方法重写

如果在子类中有和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点我们称之为方法的重写(覆盖, overrides)。原理在于继承的顺序

# 方法重写
# - 如果子类中有和父类同名的方法。则通过子类的实例去调用方法时,会调用子类的方法而不是父类的方法。这个特点我们也称之为方法的重写(覆盖)
class Animal:
    def run(self):
        print('动物跑.....')
    def sleep(self):
        print('动物睡觉....')

class Dog(Animal):
    def run(self):
        print('狗跑.....')
    def speak(self):
        print('旺旺.....')
d = Dog()
d.run()  # 结果为 狗跑.....  方法重写(overrides) 

1.2.2 多重继承

继承可以分为单继承和多继承

  • 单继承是:一个类继承一个父类(class 子类(父类):)
# 单继承: 一个类继承一个父类(class 子类(父类):)
class grandFather():
    print('我是爷爷')
class Parent(grandFather):
    print('我是父类')
class SubClass(Parent):
    print('我是子类')

sub = SubClass()  # 结果:我是爷爷  我是父类  我是子类
  • 多继承是:一个类可以继承多个父类(class 子类(父类1,父类2))
# 多重继承 :一个类可以继承多个父类(class 子类(父类1,父类2))
class Parent():
    print('我是第一个爹')
class Parent2():
    print('我是第二个爹')
class SubClass(Parent, Parent2):
    print('我是子类')

sub = SubClass()  # 结果:'我是第一个爹  我是第二个爹  我是子类

查找子类的所有的父类用方法__base__()
注意查询的主体是子类,而不是子类的实例化对象

# 查找子类的所有的父类用方法__base__()
# 注意查询的主体是子类,而不是子类的实例化对象
print(SubClass.__bases__)  # (<class '__main__.Parent'>, <class '__main__.Parent2'>)
print(sub2.__bases__)  # 报错:AttributeError: 'SubClass' object has no attribute '__bases__'

评价:

  • 在开发中没有特殊情况,应该尽量避免使用多重继承。因为多重继承会让我们的代码更加复杂
  • 如果多个父类中有同名的方法,则会先在第⼀个父类中寻找(父类没有,去object寻找,然后才开始第二个父类,以此同理类推),然后找第二个,找第三个…前面会覆盖后面的

1.2.3 super()函数

(1)定义

  • super() 函数是用于调用多重父类(超类)的一个方法。
  • super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表(先在第⼀个父类中寻找,然后找第二个,找第三个…前面会覆盖后面的)。
(2)语法:
super().xxx # xxx 是指父类的方法,或者属性
(3)特点:
super()可以获取当前类的父类并且通过super()返回对象调⽤父类方法时,不需要传递self

# 创建一个父类
class Animal:
    def __init__(self,name):  # 初始化
        self._name = name
    def run(self):
        print('动物跑.....')
    def sleep(self):
        print('动物睡觉....')
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self,name):
        self._name = name
# 父类中所有的方法都会被子类继承,包括特殊方法

class Dog(Animal):
    def __init__(self,name,age):  # age 是狗类自己私有的属性
        # 希望可以直接调用父类的__init__
        # 我希望有一种动态的方式来获取类的父类
        # Animal.__init__(self,name)  # 非动态调用,如果有多个类,需要不断修改前面的类名
        # 通过super()可以直接获取当前类的父类
        super().__init__(name)  # 语法 super().xxx  并且通过super()返回对象调⽤父类方法时,不需要传递self
        self._age = age
    @property
    def age(self):
        return self._age
    @age.setter
    def age(self,age):
        self._age = age
    def run(self):
        print('狗跑.....')
    def speak(self):
        print('旺旺.....')


d = Dog('二哈',8)
d.name = '德牧'
print(d.name)  # 德牧 封装修改的方法
print(d.age)  # 8

(4)综合练习
参考:类的继承中super的使用

# 创建父类学校成员SchoolMember
class SchoolMember:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def tell(self):
        # 打印个人信息
        print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")

# 创建子类老师 Teacher
class Teacher1(SchoolMember):
    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age) # 利用父类进行初始化
        self.salary = salary
    # 方法重写
    def tell(self):
        # 语法:print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
        # 继承父类的方法,并加以完善自己要的东西
        SchoolMember.tell(self)
        print('Salary: {}'.format(self.salary))

member1 = SchoolMember("John", 44)
teacher1 = Teacher1("John", 44, "$60000")
member1.tell()  # Name:"John" Age:"44"
teacher1.tell()  # 打印 Name:"John" Age:"44" Salary: $60000

评价:上述类的继承中,使用了SchoolMember.init(self, name, age)和SchoolMember.tell(self)两个方法实现实例初始化与tell函数,但更好的编程实践是使用super方法实现继承。
改进:

# 创建子类老师 Teacher2
class Teacher2(SchoolMember):
    def __init__(self, name, age, salary):
        super().__init__(name,age) # 利用父类进行初始化
        self.salary = salary
    def tell(self):
        super().tell()  # 等同于 SchoolMember.tell(self)
        print('Salary: {}'.format(self.salary))

teacher2 = Teacher2("Mary", 34, "$70000")
teacher2.tell()  # 打印 Name:"Mary" Age:"34" Salary: $70000
'''
# 在子类当中可以通过使用super关键字来直接调用父类的中相应的方法,简化代码。
# 在下面例子中,教师子类调用了父类的__init__()方法、tell()方法。
# super().__init__(name,age)等同于SchoolMember.__init__(self, name, age)
# super().tell()等同于SchoolMember.tell(self)。
# 当你使用Python super()关键字调用父类方法时时,注意去掉括号里self这个参数。
'''

2 多态

2.1 多态简介

多态是⾯向对象的三大特性之⼀。从字面理解就是多种形态,即一个对象⼀个对象可以以不同形态去呈现。

class A:
    def __init__(self,name):
        self._name = name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self,name):
        self._name = name
class B:
    def __init__(self,name):
        self._name = name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self,name):
        self._name = name
class C:
    pass

a = A('葫芦娃')
b = B('钢铁侠')
c = C()
def speak(obj):
    print('你好 %s'%obj.name)
# 对应speak()这个函数来说,只要对象中函数有name属性,它就可以作为参数传递
# 这个函数并不考虑对象的类型。只要有name属性就可以
# 对象以多种形态呈现,保证程序的灵活性

def speak2(obj):
    # 检查
    if isinstance(obj,A):
        print('你好 %s' % obj.name)
# 在speak2()中我们做了一个类型检查,也就是只有符合obj是A类型的对象时,才可以使用
# 其他类型的对象使用不了该函数。这个函数就违反了多态

2.2 动态的使用

(1)分类
静态多态性:如任何类型都可以用运算符+进行运算

print('你好!'+ '很开心认识你')

动态多态性

class People:
    def talk(self):
        print('People is talking')
class Cat:
    def talk(self):
        print('Cat is miaomiao')
class Dog:
    def talk(self):
        print('Dog is wangwang')
cat1 = Cat()
dog1 = Dog()
peo1 = People()
# peo、dog、pig都是动物,只要是动物肯定有talk方法
# 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo1.talk()
dog1.talk()
peo1.talk()
# 定义一个统一的接口来访问
def func(obj):
    obj.talk()
func(cat1)

2.3 多态特点

  • 增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(obj)

  • 增加了程序de 可扩展性通过继承父类创建了一个新的类,使用者无需更改自己的代码,还是用func(父类)去调用

3 属性和方法

3.1 属性

  • 类属性
    (1)类属性:直接在类中定义的属性是类属性
    (2)类属性可以通过类或类的实例访问到。但是类属性只能通过类对象来修改,无法通过实例对象修改
'''
    # 类属性 直接在类中定义的属性
    # 类属性可以通过类和类的实例访问到
    # 类属性只能通过类对象来修改,无法通过实例对象修改
'''
class A:
    name = 'claire'  # 创建类的属性

a = A() # 类的实例化对象a
print(A.name) # claire 类属性可以通过类和类的实例访问到
print(a.name) # claire  类属性可以通过类和类的实例访问到
a.name = 'chen'
print(a.name) # chen 类属性只能通过类对象来修改,无法通过实例对象修改
print(A.name) # claire  类属性只能通过类对象来修改,无法通过实例对象修改
  • 实例属性
    (1)实例属性:通过实例对象添加的属性属于实例属性
    (2)实例属性只能通过实例对象来访问和修改,类对象无法访问修改
'''
        # 实例属性 通过实例对象添加的属性
        # 实例属性只能通过实例对象访问和修改,类对象无法访问和修改
'''
class B:
    def __init__(self):
        self.name = 'claire' # 实例属性 通过实例对象添加的属性

b = B()
print(b.name)
# print(B.name) # 报错:type object 'B' has no attribute 'name'
B.name = 'chen'
print(B.name) # claire
b.name = 'chen'
print(b.name) # chen  实例属性只能通过实例对象访问和修改,类对象无法访问和修改

3.2 方法

  • 实例方法
    (1)在类中定义,以self为第⼀个参数的方法都是实例方法
    (2)实例方法在调⽤时,Python会将调用对象以self传入
    (3)实例方法可以通过类实例和类去调用:
    当通过实例调用时,会⾃动将当前调用对象作为self传⼊
    (4)当通过类调用时,不会自动传递self,我们必须手动传递self
'''
    # 在类中定义,以self第一个参数的方法都是实例方法
    # 实例方法在调用的时候 Python会将调用对象以self传入
    # 当通过类去调用时,不会自动传递self,此时需要手动传递self
'''
class C:
    def fn(self): # 在类中定义,以self第一个参数的方法都是实例方法
        print('我是实例方法')

c = C()
c.fn()  # 我是实例方法 # 实例方法在调用的时候 Python会将调用对象以self传入
# C.fn() # 报错:fn() missing 1 required positional argument: 'self'
# C.fn(self)  # 报错: name 'self' is not defined
C.fn(c) # 我是实例方法  self就是对象 # 当通过类去调用时,不会自动传递self,此时需要手动传递self
  • 类方法
    (1)类方法 在类的内容以@classmethod 来修饰的方法属性类方法
    (2)类方法第⼀个参数是cls 也会自动被传递。cls就是当前的类对象
    (3)类方法和实例方法的区别,实例方法的第⼀个参数是self,类方法的第⼀个参数是cls
    (4)类方法可以通过类去调用,也可以通过实例调用
''
    # 类方法和实例方法的区别 实例方法第一个是参数self 而类方法第一个参数是cls
    # 而且类方法需要用@classmethod
    # 类方法可以通过类去调用,也可以通过实例调用
'''
class D:
    count = 0
    @classmethod  # 类方法需要用@classmethod
    def fn(cls):
        print('我是类方法',cls.count)

D.fn() # 我是类方法 0
d = D()
d.fn() # 我是类方法 0
  • 静态方法
    (1)在类中用@staticmethod来修饰的方法属于静态方法
    (2)静态方法不需要指定任何的默认参数,静态方法可以通过类和实例调用
    (3)静态方法,基本上是⼀个和当前类无关的方法,它只是⼀个保存到当前类中的函数
    (4)静态方法⼀般都是些工具方法,和当前类无关
'''
    #  @staticmethod  修饰的方法是静态方法
    # 静态方法不需要指定任何默认参数。静态方法可以通过类或者实例调用
    # 静态方法 基本上是一个和当前类无关的方法。它只是一个保存到当前类的函数
    # 静态方法一般都是一些工具方法。和当前类无关
'''
class E:
    @staticmethod   #  @staticmethod  修饰的方法是静态方法
    def fn():
        print('我是静态方法')

E.fn()  # 我是静态方法
e = E()
e.fn() # 我是静态方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值