Python面向对象之继承和多态详解

本文介绍了Python的继承、方法重写、多态、属性和方法、垃圾回收、魔术方法。也是Python系列文章的第6篇。


系列文章
【Python 基础】一文补齐Python基础知识
【趣学Python:B站四大恶人】一文掌握列表、元组、字典、集合
【Python进阶】一文掌握Python函数用法
【Python进阶】Python面向对象之类与对象详解
【Python进阶】Python面向对象之装饰器与封装
【Python进阶】Python面向对象之继承和多态详解
【Python进阶】Python异常处理和模块详解
【Python进阶】Python文件(I/O)操作详解


文章目录

  • 1. 继承
    • 1.1 继承简介
    • 1.2 方法重写
    • 1.3 继承
    • 1.4 super()
    • 1.4 多重继承
  • 2. 多态
    • 总结
  • 3. 属性和方法
    • 3.1 实例方法
    • 3.2 类方法
    • 3.3 静态方法
  • 4. 垃圾回收
  • 5. 特殊方法(魔术方法)


1. 继承

1.1 继承简介

# 定义一个类 Animal(动物)
# 这个类中需要两个方法:run() sleep() 
class Animal:
    def run(self):
        print('动物会跑~~~')

    def sleep(self):
        print('动物睡觉~~~')

假设我们需要狗叫的方法,那么上边这个类能够实现我们需要的大部分功能,但是不能实现全部功能。如何让这个类来实现全部的功能呢?
1.直接修改这个类,在这个类中添加我们需要的功能;但是修改起来会比较麻烦,并且会违反OCP原则(对扩展是开放的,对修改是关闭的);

# 定义一个类 Dog(狗)
# 这个类中需要三个方法:run() sleep() bark()
class Dog:
    def run(self):
        print('狗会跑~~~')

    def sleep(self):
        print('狗睡觉~~~')

    def bark(self):
        print('汪汪汪~~~') 

2.以上代码,通过创建一个新的类解决了添加狗叫功能的问题,但是直接创建一个新的类会有问题:

  • 创建一个新的类比较麻烦,并且需要大量的进行复制粘贴,会出现大量的重复性代码。

3.直接从Animal类中来继承它的属性和方法。

  • 继承是面向对象三大特性之一;
  • 通过继承我们可以使一个类获取到其他类中的属性和方法;
  • 在定义类时,可以在类名后的括号中指定当前类的父类(超类、基类、super);
  • 子类(衍生类)可以直接继承父类中的所有的属性和方法。

通过继承可以直接让子类获取到父类的方法或属性,避免编写重复性的代码,并且也符合OCP原则,所以我们经常需要通过继承来对一个类进行扩展
继承实例:

class Dog(Animal):
    def bark(self):
        print('汪汪汪~~~') 
d = Dog()
r = isinstance(d , Dog)
print(r)
r = isinstance(d , Animal)
print(r)

输出:

True
True

class Person(object):
    pass

print(issubclass(Animal , Dog))
print(issubclass(Animal , object))
print(issubclass(Person , object))
  • issubclass() 检查一个类是否是另一个类的子类;
  • isinstance()用来检查一个对象是否是一个类的实例;
  • 如果这个类是这个对象的父类,也会返回True
  • 所有的对象都是object的实例;print(isinstance(print , object))

1.2 方法重写

如果父类与子类都有名称相同的方法,在调用方法的时候,会调用哪一个?

class Animal:
    def run(self):
        print('动物会跑~~~')

    def sleep(self):
        print('动物睡觉~~~')


class Dog(Animal):
    def bark(self):
        print('汪汪汪~~~') 

    def run(self):
        print('狗跑~~~~')    

d = Dog()
d.run()
# 输出:狗跑~~~~ (方法重写)
  • 如果在子类中如果有和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点我们成为叫做方法的重写(覆盖,override);

class A(object):
    def test(self):
        print('AAA')

class B(A):
    def test(self):
        print('BBB')

class C(B):
    def test(self):
        print('CCC') 

# 创建一个c的实例
c = C()
c.test()

当我们调用一个对象的方法时,会优先去当前对象中寻找是否具有该方法,如果有则直接调用;如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法;如果没有,则去父类的父类中寻找,以此类推,直到找到object,如果依然没有找到,则报错。


1.3 继承

父类中的所有方法都会被子类继承,包括特殊方法,也可以重写特殊方法;

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):
        super().__init__(name)

    def bark(self):
        print('汪汪汪~~~') 

    def run(self):
        print('狗跑~~~~')        

# d = Dog() #报错:TypeError: __init__() missing 1 required positional argument: 'name'
d = Dog('旺财') 

1.4 super()

子类继承父类,如果不使用super(),父类的属性需要在子类的__init__中初始化参数,或者直接在__init__中继承调用的父类(Animal.__init__(self,name)),但是这样会有问题,即继承的父类Animal是写死的,不是动态获取的,不方便维护。

class Dog(Animal):
    def __init__(self,name,age):
    	self._name = name
        self._age = age

    def bark(self):
        print('汪汪汪~~~') 

    def run(self):
        print('狗跑~~~~')   

    @property
    def age(self):
        return self._age

    @age.setter    
    def age(self,age):
        self._age = name  

d = Dog('旺财',18) 
print(d.name,d.age)       

d.name = '蛋挞'
print(d.name,d.age)             

为了解决上述问题,使用 super(),可以获取当前的父类,并且通过 super() 返回对象调用父类方法时,不需要传递 self

class Dog(Animal):

    def __init__(self,name,age):
        # 希望可以直接调用父类的__init__来初始化父类中定义的属性
        # super() 可以用来获取当前类的父类,
        # 并且通过super()返回对象调用父类方法时,不需要传递self
        super().__init__(name)
        self._age = age

    def bark(self):
        print('汪汪汪~~~') 

    def run(self):
        print('狗跑~~~~')   

    @property
    def age(self):
        return self._age

    @age.setter    
    def age(self,age):
        self._age = name        

d = Dog('旺财',18)
print(d.name, d.age)

1.4 多重继承

class A(object):
    def test(self):
        print('正在调用A中的test()方法~')

class B(object):
    def test2(self):
        print('正在调用B中的test2()方法~')

class C(B):
    pass

print(B.__bases__)
print(C.__bases__)
  • 在Python中是支持多重继承的,也就是我们可以为一个类同时指定多个父类
  • 可以在类名的()中添加多个类,来实现多重继承
  • 在开发中没有特殊的情况,应该尽量避免使用多重继承,因为多重继承会让代码过于复杂;
class C(A, B):
    pass
c = C()
c.test()
c.test2()

# 输出:
# 正在调用A中的test()方法~
# 正在调用B中的test2()方法~

如果多个父类中有同名的方法,则会现在第一个父类中寻找,然后找第二个,然后找第三个…前边父类的方法会覆盖后边父类的方法。


2. 多态

  • 多态是面向对象的三大特征之一;
  • 多态从字面上理解是多种形态,比如狗(狼狗、藏獒、哈士奇、古牧…);
  • 一个对象可以以不同的形态去呈现;
# 定义两个类
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 say_hello(obj):
    print('你好 %s'%obj.name)

say_hello(a) #-->你好 孙悟空
say_hello(b) #-->你好 猪八戒
say_hello(c) #-->AttributeError: 'C' object has no attribute 'name'

对于 say_hello() 这个函数来说,只要对象中含有 name 属性,它就可以作为参数传递,这个函数并不会考虑对象的类型,只要有 name 属性即可。

def say_hello_2(obj):
    # 做类型检查
    if isinstance(obj , A):
        print('你好 %s'%obj.name)    
say_hello(b) #-->你好 猪八戒
say_hello_2(b) #-->没有输出
  • say_hello_2 中我们做了一个类型检查,也就是只有 obj 是A类型的对象时,才可以正常使用,其他类型的对象都无法使用该函数,这个函数就违反了多态。违反了多态的函数,只适用于一种类型的对象,无法处理其他类型对象,这样导致函数的适应性非常的差。注意,像 isinstance() 这种函数,在开发中一般是不会使用的!

len()
之所以一个对象能通过len()来获取长度,是因为对象中具有一个特殊方法__len__;换句话说,只要对象中具有 __len__ 特殊方法,就可以通过 len() 来获取它的长度。

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

    def __len__(self):
        return 10

    @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()

print(len(b)) #-->10
print(len(c)) #-->TypeError: object of type 'C' has no len()

总结

面向对象的三大特征:
封装

  • 确保对象中的数据安全

继承

  • 保证了对象的可扩展性

多态

  • 保证了程序的灵活性

3. 属性和方法

# 定义一个类
class A(object):

    # 类属性,直接在类中定义的属性是类属性
    #   类属性可以通过类或类的实例访问到
    #   但是类属性只能通过类对象来修改,无法通过实例对象修改
    count = 0

    def __init__(self):
        # 实例属性,通过实例对象添加的属性属于实例属性
        #   实例属性只能通过实例对象来访问和修改,类对象无法访问修改
        self.name = '孙悟空'

a = A()
# 实例属性,通过实例对象添加的属性属于实例属性
a.count = 10
A.count = 100

print('a_count:', a.count) #-->
print('A_count:', A.count) #-->
print('a_name:', a.name) #-->
print('A_name', A.name) #-->AttributeError: type object 'A' has no attribute 'name'

3.1 实例方法

在类中定义,以 self 为第一个参数的方法都是实例方法;
实例方法在调用时,Python会将调用对象作为 self 传入;
实例方法可以通过实例和类去调用;

  • 当通过实例调用时,会自动将当前调用对象作为self传入;
  • 当通过类调用时,不会自动传递self,此时我们必须手动传递self;
class A(object):
    def __init__(self):
        self.name = '孙悟空'
        
    def test(self):
        print('这是test方法~~~ ', self)   

a = A()
a.test() #-->这是test方法~~~  <__main__.A object at 0x000001BB04D7D5C8>
A.test(a) #-->这是test方法~~~  <__main__.A object at 0x000001BB04D7D5C8>

3.2 类方法

  • 在类内部使用 @classmethod 来修饰的方法属于类方法;
  • 类方法的第一个参数是 cls,也会被自动传递,cls 就是当前的类对象;
  • 类方法和实例方法的区别,实例方法的第一个参数是 self,而类方法的第一个参数是 cls
  • 类方法可以通过类去调用,也可以通过实例调用,没有区别;
@classmethod
def test_2(cls):
	print('这是test_2方法,是一个类方法~~~ ',cls)
    print(cls.count)

3.3 静态方法

  • 在类中使用 @staticmethod 来修饰的方法属于静态方法 ;
  • 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例去调用 ;
  • 静态方法,基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数;
  • 静态方法一般都是一些工具方法,和当前类无关;
@staticmethod
def test_3():
    print('test_3执行了~~~')

A.test_3()
a.test_3()

完整代码:

class A(object):

    # 类属性
    # 实例属性
    # 类方法
    # 实例方法
    # 静态方法

    # 类属性,直接在类中定义的属性是类属性
    #   类属性可以通过类或类的实例访问到
    #   但是类属性只能通过类对象来修改,无法通过实例对象修改
    count = 0

    def __init__(self):
        # 实例属性,通过实例对象添加的属性属于实例属性
        #   实例属性只能通过实例对象来访问和修改,类对象无法访问修改
        self.name = '孙悟空'

    # 实例方法
    #   在类中定义,以self为第一个参数的方法都是实例方法
    #   实例方法在调用时,Python会将调用对象作为self传入  
    #   实例方法可以通过实例和类去调用
    #       当通过实例调用时,会自动将当前调用对象作为self传入
    #       当通过类调用时,不会自动传递self,此时我们必须手动传递self
    def test(self):
        print('这是test方法~~~ ' , self)    

    # 类方法    
    # 在类内部使用 @classmethod 来修饰的方法属于类方法
    # 类方法的第一个参数是cls,也会被自动传递,cls就是当前的类对象
    #   类方法和实例方法的区别,实例方法的第一个参数是self,而类方法的第一个参数是cls
    #   类方法可以通过类去调用,也可以通过实例调用,没有区别
    @classmethod
    def test_2(cls):
        print('这是test_2方法,他是一个类方法~~~ ',cls)
        print(cls.count)

    # 静态方法
    # 在类中使用 @staticmethod 来修饰的方法属于静态方法  
    # 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例去调用  
    # 静态方法,基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数
    # 静态方法一般都是一些工具方法,和当前类无关
    @staticmethod
    def test_3():
        print('test_3执行了~~~')


a = A()
# 实例属性,通过实例对象添加的属性属于实例属性
# a.count = 10
# A.count = 100
# print('A ,',A.count) 
# print('a ,',a.count) 
# print('A ,',A.name) 
# print('a ,',a.name)   

# a.test() 等价于 A.test(a)
# A.test_2() 等价于 a.test_2()

A.test_3()
a.test_3()

4. 垃圾回收

了解即可,实际应用中很少使用。

  • 就像我们生活中会产生垃圾一样,程序在运行过程当中也会产生垃圾;程序运行过程中产生的垃圾会影响到程序的运行的运行性能,所以这些垃圾必须被及时清理。
  • 没用的东西就是垃圾。在程序中没有被引用的对象就是垃圾,这种垃圾对象过多以后会影响到程序的运行的性能,所以我们必须进行及时的垃圾回收,所谓的垃圾回收就是讲垃圾对象从内存中删除;
  • 在Python中有自动的垃圾回收机制,它会自动将这些没有被引用的对象删除,所以我们不用手动处理垃圾回收。
class A:
    def __init__(self):
        self.name = 'A类'

    # del是一个特殊方法,它会在对象被垃圾回收前调用
    def __del__(self):
        print('A()对象被删除了~~~',self)

a = A()
a = None # 将a设置为了None,此时没有任何的变量对A()对象进行引用,它就是变成了垃圾
# 返回:A()对象被删除了~~~ <__main__.A object at 0x000001BB04D03A48>

5. 特殊方法(魔术方法)

  • 特殊方法都是使用__开头和结尾的;官方文档
  • 特殊方法一般不需要我们手动调用,需要在一些特殊情况下自动执行

  1. __str__(self) 这个特殊方法会在尝试将对象转换为字符串的时候调用,它的作用可以用来指定对象转换为字符串的结果(print函数);
def __str__(self):
    return 'Person [name=%s , age=%d]'%(self.name,self.age)     

  1. __repr__(self)这个特殊方法会在对当前对象使用repr()函数时调用
    它的作用是指定对象在 ‘交互模式’中直接输出的效果
def __repr__(self):
    return 'Hello'       

  1. __bool__(self) 可以通过bool来指定对象转换为布尔值的情况
def __bool__(self):
    return self.age > 17

  1. __gt__(self)会在对象做大于比较的时候调用,该方法的返回值将会作为比较的结果;需要两个参数,一个self表示当前对象,other表示和当前对象比较的对象。
# self > other
def __gt__(self , other):
    return self.age > other.age

# 创建两个Person类的实例        
p1 = Person('孙悟空',18)
p2 = Person('猪八戒',28)

# 打印p1
# 当我们打印一个对象时,实际上打印的是对象的中特殊方法 __str__()的返回值
# print(p1) # <__main__.Person object at 0x04E95090>

  1. 其他方法
    object.__add__(self, other)
    object.__sub__(self, other)
    object.__mul__(self, other)
    object.__matmul__(self, other)
    object.__truediv__(self, other)
    object.__floordiv__(self, other)
    object.__mod__(self, other)
    object.__divmod__(self, other)
    object.__pow__(self, other[, modulo])
    object.__lshift__(self, other)
    object.__rshift__(self, other)
    object.__and__(self, other)
    object.__xor__(self, other)
    object.__or__(self, other)

    object.__lt__(self, other) 小于 <
    object.__le__(self, other) 小于等于 <=
    object.__eq__(self, other) 等于 ==
    object.__ne__(self, other) 不等于 !=
    object.__gt__(self, other) 大于 >
    object.__ge__(self, other) 大于等于 >= 

参考:https://edu.aliyun.com/course/1782

你可能感兴趣的:(Python)