Python类的继承
基本概念
面向对象三要素之一:继承inheritance。人类和猫类都是继承自动物类。
个体继承自父母,继承了父母的一部分特征,但也可以有自己的个性。在面向对象的世界中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码,多复用。子类可以定义自己的属性和方法。
先看一个不用继承的例子:
class Animal: def shout(self): print("Animal shouts") a = Animal() a.shout() class Cat: def shout(self): print("cat shouts") c = Cat() c.shout() 结果为: Animal shouts cat shouts
上面的两个类虽然有关系,但是定义时并没有建立这种关系,而是各自完成定义。
动物类和猫类都有吃,但是它们的吃有区别,所以分别定义。
class Animal: def __init__(self,name): self._name = name def shout(self):#一个通用的吃方法 print("{} shouts".format(self.__class__.__name__)) @property def name(self): return self._name a = Animal("monster") a.shout() class Cat(Animal): pass cat = Cat("garfield") cat.shout() print(cat.name) class Dog(Animal): pass dog = Dog("ahuang") dog.shout() print(dog.name) 结果为: Animal shouts Cat shouts garfield Dog shouts ahuang
上例可以看出,通过继承,猫类、狗类不用写代码,直接继承了父类的属性和方法。
继承
class Cat(Animal)这种形式就是从父类继承,括号中写上继承的类的列表。
父类
Animal就是Cat的父类,也称为基类、超类。
子类
Cat就是Animal的子类,也称为派生类。
定义
格式如下:
class 子类名(基类1,基类2,……): 语句块
如果定义时,没有基类列表,等同于继承自object。在Python3中,object类就是所有对象的根基类。
class A: pass #等价于 class A(object): pass
注意,上例在Python2中,两种写法是不同的。
Python支持多继承,继承也可以分为多级。
查看继承的特殊属性和方法有:
__base__:类的基类
__bases__:类的基类元组
__mro__:显示方法查找顺序,基类的元组。
mro()方法,同__mro__,比如int.mro()
__subclasses__()类的子类列表,int.__subclasses__()。
class Animal(): x = 123 def __init__(self,name): self._name = name @property def name(self): return self._name def shout(self): print("animal shout") class Cat(Animal): x = "cat" def shout(self):#override print("miao") class Garfield(Cat): pass class PersiaCat(Cat): pass class Dog(Animal): def run(self): print("dog run") tom = Cat("tom") print(tom.name) print(tom.shout()) dog = Dog("ahuang") print(dog.name) print(dog.shout()) gf = Garfield("gf") gf.shout() print("gf.x",gf.x) print("gf",gf.__dict__) print("gf.mro = {}".format(gf.__class__.__mro__))#祖先列表,继承链,应该注意,类才有这些方法,实例没有。 print("gf.bases = {}".format(gf.__class__.__bases__)) 结果为: tom miao None ahuang animal shout None miao gf.x cat gf {'_name': 'gf'} gf.mro = (<class '__main__.Garfield'>, <class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>) gf.bases = (<class '__main__.Cat'>,)
继承中的访问控制
class Animal: __COUNT = 100 HEIGHT = 0 def __init__(self,age,weight,height): self.__COUNT+=1 self.age = age self.__weight = weight self.HEIGHT = height def eat(self): print("{} eat ".format(self.__class__.__name__)) def __getweight(self): print(self.__weight) @classmethod def showcount1(cls): print(cls.__COUNT) @classmethod def __showcount2(cls): print(cls.__COUNT) def showcount3(self): print(self.__COUNT) class Cat(Animal): NAME = "CAT" __COUNT = 200 #c = Cat()__init__函数参数错误 c = Cat(3,5,15) c.eat() print(c.HEIGHT) #print(c.__COUNT)#私有的不可访问 #c.show__weight()#私有的不可访问 c.showcount1() #c.__showcount2()#私有的不可访问 c.showcount3() print(c.NAME) print("{}".format(Animal.__dict__)) print("{}".format(Cat.__dict__)) print(c.__dict__) print(c.__class__.mro()) 结果为: Cat eat 15 100 101 CAT {'__module__': '__main__', '_Animal__COUNT': 100, 'HEIGHT': 0, '__init__': <function Animal.__init__ at 0x0000000005A796A8>, 'eat': <function Animal.eat at 0x0000000005A79730>, '_Animal__getweight': <function Animal.__getweight at 0x0000000005A797B8>, 'showcount1': <classmethod object at 0x0000000005A74E10>, '_Animal__showcount2': <classmethod object at 0x0000000005A74748>, 'showcount3': <function Animal.showcount3 at 0x0000000005A79488>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None} {'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15} [<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]
从父类继承,自己没有的,就可以到父类中找。
私有的都是不可访问的,但是本质上依然是改了名称放在这个属性所在类的__dict__中了,知道这个新名称就可以直接找到这个隐藏的变量,但是这是个黑魔法,应该慎用。
class Animal(): x = 123 def __init__(self,name): self._name = name self.__age = 10 @property def name(self): return self._name def shout(self): print("animal shout") class Cat(Animal): x = "cat" def shout(self):#override print("miao") class Garfield(Cat): pass class PersiaCat(Cat): pass tom = Garfield("tom") print(tom.name) print(tom.shout()) print(tom.__dict__) print(Garfield.__dict__) print(Cat.__dict__) print(Animal.__dict__) 结果为: tom miao None {'_name': 'tom', '_Animal__age': 10} {'__module__': '__main__', '__doc__': None} {'__module__': '__main__', 'x': 'cat', 'shout': <function Cat.shout at 0x03FB7F60>, '__doc__': None} {'__module__': '__main__', 'x': 123, '__init__': <function Animal.__init__ at 0x03FB7228>, 'name': <property object at 0x03FD3210>, 'shout': <function Animal.shout at 0x03FB7E40>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
class Animal(): x = 123 def __init__(self,name): self._name = name self.__age = 10 @property def name(self): return self._name def shout(self): print("animal shout") class Cat(Animal): x = "cat" def __init__(self,name): Animal.__init__(self,name)#调整顺序,执行结果不同 self.catname = name#改成self._name会覆盖前面的_name def shout(self):#override print("miao") tom = Cat("tom") print(tom.name) print(tom.shout()) print(tom.__dict__) print(Cat.__dict__) print(Animal.__dict__) 结果为: tom miao None {'_name': 'tom', '_Animal__age': 10, 'catname': 'tom'} {'__module__': '__main__', 'x': 'cat', '__init__': <function Cat.__init__ at 0x03FDC108>, 'shout': <function Cat.shout at 0x03FDC150>, '__doc__': None} {'__module__': '__main__', 'x': 123, '__init__': <function Animal.__init__ at 0x03FDC030>, 'name': <property object at 0x03FB9840>, 'shout': <function Animal.shout at 0x03FDC0C0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
总结:
继承时,公有的,子类和实例都可以随意访问,私有成员被隐藏,子类和实例不可直接访问,当私有变量所在的类内的方法中可以访问这个私有变量。
Python通过自己一套实现和其他语言一样的面向对象的继承机制。
属性查找顺序
实例的__dict__》类__dict__如果有继承==》父类__dict__
如果搜索这些地方后没有找到就会抛异常,先找到就立即返回了。
方法的重写、覆盖override
class Animal(): def shout(self): print("animal shouts") class Cat(Animal): #覆盖了父类方法 def shout(self): print("miao") a = Animal() a.shout() c = Cat() c.shout() print(a.__dict__) print(c.__dict__) print(Animal.__dict__) print(Cat.__dict__) 结果为: animal shouts miao {} {} {'__module__': '__main__', 'shout': <function Animal.shout at 0x0000000005A79D90>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} {'__module__': '__main__', 'shout': <function Cat.shout at 0x0000000005A79C80>, '__doc__': None}
cat能否覆盖自己的方法?
class Animal(): def shout(self): print("animal shouts") class Cat(Animal): #覆盖了父类方法 def shout(self): print("miao") #覆盖了自身的方法,显式调用了父类的方法 def shout(self): print(super()) print(super(Cat,self)) super().shout() super(Cat,self).shout()#等价与super() self.__class__.__base__.shout(self)#不推荐 a = Animal() a.shout() c = Cat() c.shout() print(a.__dict__) print(c.__dict__) print(Animal.__dict__) print(Cat.__dict__) 结果为: animal shouts <super: <class 'Cat'>, <Cat object>> <super: <class 'Cat'>, <Cat object>> animal shouts animal shouts animal shouts {} {} {'__module__': '__main__', 'shout': <function Animal.shout at 0x0000000005A79510>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} {'__module__': '__main__', 'shout': <function Cat.shout at 0x0000000005A69BF8>, '__doc__': None}
super()可以访问到父类的属性,具体原理后面再说。
那对于类方法和静态方法呢?
class Animal(): @classmethod def class_method(cls): print("class_method_animals") @staticmethod def static_method(): print("static_method_animals") class Cat(Animal): @classmethod def class_method(cls): print("class_method_cat") @staticmethod def static_method(): print("static_method_cat") c = Cat() c.class_method() c.static_method() 结果为: class_method_cat static_method_cat
这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。
class Animal(): x = 123 def __init__(self,name): self._name = name self.__age = 10 @property def name(self): return self._name def shout(self): print("animal shout") @classmethod def clsmtd(cls): print(cls,cls.__name__) class Cat(Animal): x = "cat" def __init__(self,name): Animal.__init__(self,name)#调整顺序,执行结果不同 self.catname = name#改成self._name会覆盖前面的_name def shout(self):#override print("miao") @classmethod def clsmtd(cls): print(cls,cls.__name__) class Garfield(Cat): pass tom = Garfield("tom") print(tom.clsmtd())#注意结果 print(tom.__dict__) print(Cat.__dict__) print(Animal.__dict__) 结果为: <class '__main__.Garfield'> Garfield None {'_name': 'tom', '_Animal__age': 10, 'catname': 'tom'} {'__module__': '__main__', 'x': 'cat', '__init__': <function Cat.__init__ at 0x03FDC4F8>, 'shout': <function Cat.shout at 0x03FDC4B0>, 'clsmtd': <classmethod object at 0x03FCB990>, '__doc__': None} {'__module__': '__main__', 'x': 123, '__init__': <function Animal.__init__ at 0x03FB76F0>, 'name': <property object at 0x03FD7AE0>, 'shout': <function Animal.shout at 0x03FDC588>, 'clsmtd': <classmethod object at 0x03FCB9D0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
继承中的初始化
先看下面这一段代码,有没有问题:
class A: def __init__(self,a): self.a = a class B(A): def __init__(self,b,c): self.b = b self.c = c def printv(self): print(self.b) #print(self.a)#出错吗?会出错。 f = B(200,300) print(f.__dict__) print(f.__class__.__bases__) f.printv() 结果为: {'b': 200, 'c': 300} (<class '__main__.A'>,) 200
上例代码可知:如果类B定义时声明继承自类A,则在类B中__bases__中可以看到类A。但是这和是否调用类A的构造方法是两回事。
如果B中调用了A的构造方法,就可以拥有父类的属性了,如何理解这句话?观察B的实例f的__dict__中的属性。
class A: def __init__(self,a,d = 10): self.a = a self.__d = d class B(A): def __init__(self,b,c): A.__init__(self,b+c,b-c) self.b = b self.c = c def printv(self): print(self.b) #print(self.a)#出错吗? f = B(200,300) print(f.__dict__) print(f.__class__.__bases__) f.printv() 结果为: {'a': 500, '_A__d': -100, 'b': 200, 'c': 300} (<class '__main__.A'>,) 200
作为好的习惯,如果父类定义了__init__方法,你就该在子类的__init__中调用它。那子类什么时候自动调用父类的__init__方法呢?
示例1
class A: def __init__(self): self.a1 = "a1" self.__a2 = "a2" print("A init") class B(A): pass b = B() print(b.__dict__) 结果为: A init {'a1': 'a1', '_A__a2': 'a2'}
B实例的初始化会自动调用基类A的__init__方法。
实例2
class A: def __init__(self): self.a1 = "a1" self.__a2 = "a2" print("A init") class B(A): def __init__(self): self.b1 = "b1" print("B init") b = B() print(b.__dict__) 结果为: B init {'b1': 'b1'}
B实例的初始化__init__方法不会自动调用父类的初始化__init__方法,需要手动调用。
class A: def __init__(self): self.a1 = "a1" self.__a2 = "a2" print("A init") class B(A): def __init__(self): self.b1 = "b1" print("B init") A.__init__(self) b = B() print(b.__dict__) 结果为: B init A init {'b1': 'b1', 'a1': 'a1', '_A__a2': 'a2'}
如何正确初始化
class Animal: def __init__(self,age): print("Animal init") self.age = age def show(self): print(self.age) class Cat(Animal): def __init__(self,age,weight): print("cat init") self.age = age+1 self.weight = weight c = Cat(10,5) c.show() 结果为: cat init 11
上例我们前面都分析过,不会调用父类的__init__方法的,这就会导致没有实现继承效果。所以在子类的__init__方法中,应该显示调用父类的__init__方法。
class Animal: def __init__(self,age): print("Animal init") self.age = age def show(self): print(self.age) class Cat(Animal): def __init__(self,age,weight): #调用父类的__init__方法的顺序决定着show方法的结果 super().__init__(age) print("cat init") self.age = age+1 self.weight = weight #super().__init__(age) c = Cat(10,5) c.show() 结果为: Animal init cat init 11
注意,调用父类的__init__方法,出现在不同的位置,可能导致出现不同的结果。那么直接将上例中所有的实例属性改成私有变量呢?
class Animal: def __init__(self,age): print("Animal init") self.__age = age def show(self): print(self.__age) class Cat(Animal): def __init__(self,age,weight): #调用父类的__init__方法的顺序决定着show方法的结果 super().__init__(age) print("cat init") self.__age = age+1 self.__weight = weight #super().__init__(age) c = Cat(10,5) c.show() print(c.__dict__) 结果为: Animal init cat init 10 {'_Animal__age': 10, '_Cat__age': 11, '_Cat__weight': 5}
上例中打印10,原因看__dict__就知道了。因为父类Animal的show方法中__age会被解释为_Animal__age,因此显示的是10,而不是11。
这样的设计好不好,cat的实例c应该显示自己的属性值更好。
解决的办法:一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其他类的方法,即便是父类或者派生类的方法。