面向对象三要素之二、三:继承和多态
方法的重写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 的多态相对比较简单,可对比参考其他语言的多态
继承中的初始化问题
- 子类未定义
__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'}
- 子类定义了
__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}
- 手动调用父类的
__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())
如上例子请自行运行并解释缘由,该例主要用于全面理解访问控制,在实际项目中当然不建议这么做
总结:
- 继承时,子类没有的,就按 mro 到父类中找
- 私有的都是不可以访问的,但是本质上是改了名称放在这个属性所在类或实例的
__dict__
中, 不建议访问 - 继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量
- 实例属性查找顺序:实例的
__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