面向对象三要素之二、三:继承和多态
方法的重写override和重载overload
super()
继承中使用初始化
继承中的访问控制

类的继承


初识继承

class Animal:
    def __init__(self,name):
        self._name = name
    def shout(self):
        print('{} shout'.format(self.__class__.__name__))

class Dog(Animal):
    pass

class Cat(Animal):
    pass

d = Dog('bingo')
d.shout()
c = Cat('erpang')
c.shout()

print(Dog.__base__)
print(Dog.__bases__)
print(Dog.mro())
print(Dog.__mro__)
print(Animal.__subclasses__())
--------------------------------
Dog shout
Cat shout
<class '__main__.Animal'>
(<class '__main__.Animal'>,)
[<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>]
(<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)
[<class '__main__.Dog'>, <class '__main__.Cat'>]
  • 父类:基类或超类,如 Animal
  • 子类:派生类,如 Dog,Cat
  • 继承:class 子类名(父类1,父类2....):

继承中的几个特殊属性和方法

  • __base__:类的基类
  • __bases__:类的基类元组
  • mro():方法的查找顺序,返回列表
  • __mro__:方法的查找顺序,返回元组
  • __subclasses__():类的子类列表

方法的重写override

  • 子类覆盖父类方法
  • 实例调用时按照 mro 顺序查找,找到则不再向上
class Animal:
    def __init__(self, age=2):
        self._age = age
    
    @property
    def age(self):
        return self._age
    
    def shout(self):
        print('animal shout')
    

class Dog(Animal):
    # 子类覆盖父类的方法
    def shout(self):
        print('dog shout')
    

d = Dog()

print(d.age) #调用父类方法
d.shout()

print('~'*20)
print(d.__dict__)
print(Dog.mro())
print(Animal.__dict__)
print(Dog.__dict__)
-----------------------------------------------
2
dog shout
~~~~~~~~~~~~~~~~~~~~
{'_age': 2}
[<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>]
{'__module__': '__main__', '__init__': <function Animal.__init__ at 0x000001B6E526B598>, 'age': <property object at 0x000001B6E52EAE08>, 'shout': <function Animal.shout at 0x000001B6E526BAE8>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
{'__module__': '__main__', 'shout': <function Dog.shout at 0x000001B6E52F2048>, '__doc__': None}

类方法和静态方法同样可以覆盖,这里不举例,可自己尝试

super()

  • super()访问父类的类属性
  • 等价于super(self.__class__,self)
class Animal:   
    def shout(self):
        print('animal shout')
    

class Dog(Animal):
    # 覆盖父类的方法
    def shout(self):
        print(super()) # Dog类和Dog实例
        super().shout()
        # super(self.__class__,self).shout() # 等价super()
        print('dog shout')
        
d = Dog()
d.shout()

方法的重载overload

面向对象的高级语言中如 java,c++,都有 overload 的概念
注意重载overload和重写override的区别

  • 重载:同一个方法名,但是参数个数、类型不一样,就是同一个方法的重载
  • Python是一门动态类型语言,不支持重载(overload)的概念,因为在Python中函数的参数类型不需要在定义时指定,而是在运行时动态确定,因此无法根据函数的参数类型来区分不同的函数。在Python中,如果需要实现类似于重载的功能,可以通过参数默认值和可变参数来实现
// java的重载
public class Overloading {
    public int test(){
        System.out.println("test1");
        return 1;
    }
 
    public void test(int a){
        System.out.println("test2");
    }   
 
    //以下两个参数类型顺序不同
    public String test(int a,String s){
        System.out.println("test3");
        return "returntest3";
    }   
 
    public String test(String s,int a){
        System.out.println("test4");
        return "returntest4";
    }   
 
    public static void main(String[] args){
        Overloading o = new Overloading();
        System.out.println(o.test());
        o.test(1);
        System.out.println(o.test(1,"test3"));
        System.out.println(o.test("test4",1));
    }
}

# python的可变参数
class Overloading:
    def test(self, a:str, *args, **kwargs):
        print(a, args, kwargs, sep='\n')

o = Overloading()
o.test(1,2,3,k=3)
----------------------
1
(2, 3)
{'k': 3}

类的多态


  • 多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式
  • 多态首先是建立在继承的基础上的,先有继承才能有多态
  • python 的多态相对比较简单,可对比参考其他语言的多态

继承中的初始化问题

  1. 子类未定义__init__, 子类实例化会自动调用父类的__init__方法
  • 即子类找不到,到父类中找
# 示例1:子类未定义__init__,子类实例化会自动调用父类的__init__方法
class Animal:
    def __init__(self,name):
        self._name = name
        print('animal init')

class Dog(Animal):
    pass

d = Dog('bingo')
print(d.__dict__)
---------------------
animal init
{'_name': 'bingo'}
  1. 子类定义了__init__, 子类实例化就不会自动调用父类的__init__方法,需要手动调用
  • 按照 mro 的顺序,子类中有,不再向上查找
# 示例2:子类定义了`__init__`,子类实例化就不会自动调用父类的__init__方法
class Animal:
    def __init__(self,name):
        self.name = name
        print('animal init')

class Dog(Animal):
    def __init__(self,age):
        self.age = age
        print('dog init')

d = Dog(2)
print(d.__dict__)
-------------------------
dog init
{'age': 2}
  1. 手动调用父类的__init__方法
  • 同样使用 super()
  • super()调用顺序可能对结果有影响,一般建议放在子类__init__方法首行
# super()调用父类__init__
# 比较
# 示例1
class Animal:
    def __init__(self, age):
        print('animal init')
        self.age = age    
    
    def show(self):
        print(self.age)

class Dog(Animal):    
    def __init__(self, age, weight):
        # super调用顺序对结果有影响
        super().__init__(age) # 建议位置
        print('dog init')
        self.age = age + 1
        # super().__init__(age) # show结果为2

d = Dog(2,10)
d.show()
-------------------------------------
animal init
dog init
3

# 示例2
class Animal:
    def __init__(self, age):
        print('animal init')
        self.__age = age     # _Animal__age
    
    def show(self):
        print(self.__age)

class Dog(Animal):    
    def __init__(self, age, weight):
        super().__init__(age)
        print('dog init')
        self.__age = age + 1 # _Dog__age

d = Dog(2,10)
d.show()
print(d.__dict__)
-----------------------------
animal init
dog init
2
{'_Animal__age': 2, '_Dog__age': 3}

遵循原则,自己的私有属性,就该自己的方法读取和修改,不要借助其他类的方法,即使是父类或者派生类的方法

继承中的访问控制

分析如下例子

class Animal:    
    __COUNT = 100    # _Animal__COUNT
    HEIGHT = 0    
    def __init__(self, age, weight, height):
        self.__COUNT += 1  # self._Animal__COUNT
        self.age = age
        self.__weight = weight # self._Animal__weight
        self.HEIGHT = height   
        
    def eat(self):
        print('{} eat'.format(self.__class__.__name__))
        
    def __getweight(self):  # _Animal__getweight()
        print(self.__weight) # self._Animal__weight
        
    @classmethod    
    def showcount1(cls):
        print(cls)
        print(cls.__dict__)
        print(cls.__COUNT) #   cls._Animal__COUNT 
    @classmethod
    def __showcount2(cls):  # _Animal__showcount2()
        print(cls.__COUNT)  # cls._Animal__COUNT
        
    def showcount3(self):
        print(self.__COUNT) # self._Animal__COUNT
class Cat(Animal): 
    NAME = 'CAT'    
    __COUNT = 200 # _Cat__COUNT


c = Cat(3, 5, 15)
c.eat() # Cat eat
print(c.HEIGHT) # 15

print(c._Cat__COUNT) # 200
c._Animal__getweight() # 5
c.showcount1() # 100
c._Animal__showcount2() # 100
c.showcount3() # 101 
print(c._Animal__COUNT) # 101
print(c.NAME) # CAT
print("{}".format(Animal.__dict__))
print("{}".format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro())

如上例子请自行运行并解释缘由,该例主要用于全面理解访问控制,在实际项目中当然不建议这么做

总结:

  1. 继承时,子类没有的,就按 mro 到父类中找
  2. 私有的都是不可以访问的,但是本质上是改了名称放在这个属性所在类或实例的__dict__中, 不建议访问
  3. 继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量
  4. 实例属性查找顺序:实例的__dict__ ——> 类__dict__ ——>> (如果有继承) 再到父类 __dict__ ,如果搜索这些地方后没有找到就会抛异常,先找到就立即返回了

析构函数__del__


  • 在类中定义__del__方法,称为析构函数
  • 作用是在销毁类实例的时候调用,以释放占用的资源(如释放连接等)
  • 使用del语句删除实例,引用计数减 1,当引用计数为 0 时,自动调用__del__方法
class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age
    def __del__(self):
        print('delete {}'.format(self.name))
        
j = Person('jerry',18)
j1 = j

del j
print('~'*10,'start','~'*10)
del j1
--------------------------------
~~~~~~~~~~ start ~~~~~~~~~~
delete jerry

参考