基本概念
面向对象三要素之一,继承inherritance
人类和猫类都继承自动物类。
个体继承自父母,继承了父母的一部分特征,但也可以有自己的个性。
在面向对象的世界中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码、多复用。子类可以定义自己的属性和方法。
看一个不用继承的例子
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Animal: def shout(self): print('Animal shouts') a = Animal() a.shout()
class Cat: def shout(self): print('Cat shouts')
c = Cat() c.shout()
|
上面的2个类虽然有关系,但是定义时并没有建立这种关系,而是各自完成定义。
动物类和猫类都有吃,但是它们的吃有区别,所以分别定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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
的子类,也称为派生类。
定义
格式如下
1 2
| class 子类名(基类1[,基类2,...]): 语句块
|
如果类定义时,没有基类列表,等同于继承自object
。在Python3中,object
类是所有对象的根基类。
1 2 3 4 5
| class A: pass
class A(object): pass
|
注意,上例在Python2中,两种写法是不同的。
Python支持多继承,继承也可以多级。
查看继承的特殊属性和方法有
特殊属性和方法 |
含义 |
示例 |
__base__ |
类的基类 |
|
__bases__ |
类的基类元组 |
|
__mro__ |
显示方法查找顺序,基类的元组 |
|
mro() 方法 |
同上 |
int.mro() |
__subclasses__() |
类的子类列表 |
int.__subclasses__() |
继承中的访问控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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(3, 5, 15) c.eat() print(c.HEIGHT)
c.showcount1()
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 0x7fca758b75e0>, 'eat': <function Animal.eat at 0x7fca758b7670>, '_Animal__getweight': <function Animal.__getweight at 0x7fca758b7700>, 'showcount1': <classmethod object at 0x7fca7591aa90>, '_Animal__showcount2': <classmethod object at 0x7fca7593f5b0>, 'showcount3': <function Animal.showcount3 at 0x7fca758b78b0>, '__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__
中了。知道这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。
总结
继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,私有变量所在的类内的方法中可以访问这个私有变量。也就是只有类内的方法可以访问类内的私有变量,但类方法不可以调用实例的私有属性,因为实例可能不存在,或没有属性。私有变量或属性就是名称前面有两个下划线的,私有的实例属性就在实例中用,如果是私有的类属性,可以在类方法和实例方法中使用。公有的大家都可以用,私有的除了它自己谁都不能用。继承的,公开的,是祖先的、父类的就是我的,我实例化后是我自己,我在实例化后把这些实例属性都放到自己的字典中了,类的方法存一份就够了,所以不需要也拿过来,这些是放在类的字典中的
Python通过自己一套实现,实现和其它语言一样的面向对象的继承机制。
属性查找顺序
实例的__dict__
-> 类__dict__
如果有继承 -> 父类__dict__
如果搜索这些地方后没有找到就会抛异常,先找到就立即返回了。
方法的重写、覆盖override
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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 0x7f2d8609f5e0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} {'__module__': '__main__', 'shout': <function Cat.shout at 0x7f2d8609f670>, '__doc__': None}
|
Cat中能否覆盖自己的方法吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Animal: def shout(self): print('Animal shout') class Cat(Animal): def shout(self): print('miao') def shout(self): print(super()) print(super(Cat, self)) super().shout() super(Cat, self).shout() 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 shout <super: <class 'Cat'>, <Cat object>> <super: <class 'Cat'>, <Cat object>> Animal shout Animal shout Animal shout {} {} {'__module__': '__main__', 'shout': <function Animal.shout at 0x7f08af1425e0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} {'__module__': '__main__', 'shout': <function Cat.shout at 0x7f08af142700>, '__doc__': None}
|
super()
可以访问到父类的属性,其具体原理后面再说。
那对于类方法和静态方法呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Animal: @classmethod def class_method(cls): print('class_method_animal') @staticmethod def static_method(): print('static_method_animal') 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
|
这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。
继承中的初始化
先看下面一段代码,有没有问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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, 200) print(f.__dict__) print(f.__class__.__bases__) f.printv() 输出: {'b': 200, 'c': 200} (<class '__main__.A'>,) 200
|
上例代码可知:
如果类B定义时声明继承自类A,则在类B中__bases__
中是可以看到类A。
但是这和是否调用类A的构造方法是两回事。
如果B中调用了A的构造方法,就可以拥有父类的属性了。如何理解这一句话呢?
观察B的实例f的__dict__
中的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 500
|
作为好的习惯,如果父类定义了__init__
方法,你就该在子类的__init__
中调用它。
那子类什么时候自动调用父类的__init__
方法呢?
示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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__
方法,需要手动调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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'}
|
如何正确初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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__
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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): super().__init__(age) print('Cat init') self.age = age + 1 self.weight = weight c = Cat(10, 5) c.show() 输出: Animal init Cat init 11
|
注意,调用父类的__init__
方法,出现在不同的位置,可能导致出现不同的结果。
那么,直接将上例中所有的实例属性改成私有变量呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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): super().__init__(age) print('Cat init') self.__age = age + 1 self.__weight = weight c = Cat(10, 5) c.show()
print(c.__dict__) 输出: Animal init Cat init 10 {'_Animal__age': 10, 'age': 11, 'weight': 5}
|
上例中打印10,原因看__dict__
就知道了。因为父类Animal
的show
方法中__age
会被解释为_Animal__age
,因此显示的是10,而不是11。
这样的设计不好,Cat
的实例c
应该显示自己的属性值更好。
解决的办法:一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其他类的方法,即使是父类或者派生类的方法。
笔记
__mro__
非常重要,它会保存方法的查找顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| int.__subclasses__() int.__bases__ int.__base__ int.mro()
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):
super().__init__(name)
self._name = "Cat"+name
self.__age = 20 def shout(self): super().shout()
super(Cat, self).shout()
print('Cat shout')
@classmethod def clsmtd(cls): print(cls, cls.__name__) class Garfield(Cat): pass
class PersiaCat(Cat): pass
class Dog(Animal): def run(self): print('Dog run') tom = Garfield('tom') print(tom.name) print(tom.shout()) print(tom.__dict__) print(Garfield.__dict__) print(Cat.__dict__) print(Animal.__dict__)
print(tom.clsmtd())
输出: tom miao None {'_name': 'tom', '_Animal__age': 10} {'__module__': '__main__', '__doc__': None}
{'__module__': '__main__', 'x': 'cat', 'shout': <function Cat.shout at 0x7fb7f2b7e700>, '__doc__': None}
{'__module__': '__main__', 'x': 123, '__init__': <function Animal.__init__ at 0x7fb7f2b7e550>, 'name': <property object at 0x7fb7f2b83680>, 'shout': <function Animal.shout at 0x7fb7f2b7e670>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} <class 'test3.Garfield'> Garfield dog = Dog('ahuang') 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__))
|
加下划线和不加下划线的都是公有的,包括特殊的,在类上都继承下来,父类的就是你的,python为了实现做了一个查找的动作,对使用者来说可以不管这些。父类的就是我的,拿来就可以用。传的self就是自己,所以调父类的方法发现打印的self是自己,方法有一份就可以了,因为是不同的实例。
在子类中初始化时,要调用父类的初始化方法,不然就会覆盖父类的初始化方法,但有一些方法也是不需要继承的。另外,私有属性是自己的,不会继承。如果子类不做初始化或初始化没有重名,就会继承父类的初始化方法。所以最好显式地调用父类的初始化方法。私有变量如果与父类中的有冲突,并把调用这个变量的方法重新在子类中再写一次,因为私有变量是不会互相覆盖的。要覆盖就不要用私有变量。父类和祖先类的,除了私有的,都是我的。所以传进去的self,class也是自己的,显示的也是自己的,私有变量除外。
Minxi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| class Document: def __init__(self, content): self.content = content def print(self): print(self.content)
class Word(Document): pass
class PrintableWord(Word): def print(self): print('Word print {}'.format(self.content))
class Pdf(Document): pass
print(PrintableWord.mro()) word = PrintableWord('test\nabc') print(word.__dict) 输出: [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>] {'content': 'test\nabc'}
================================================================================== 装饰器 class Document: def __init__(self, content): self.content = content def print(self): print(self.content)
class Word(Document): pass
def printable(cls): cls.print = lambda self: print(self.content) return cls
@printable
class PrintableWord(Word): def __init__(self, content): self.content = content
class Pdf(Document): pass
print(PrintableWord.__dict__)
输出: =================================================================================
class PrintableMixin: def _print(self): print('~~~~~~~~~~~') print('{}'.format(self.content))
class Document: def __init__(self, content): self.content = content def print(self): print(self.content) def printable(cls): def _print(self): print('~~~~~~~~~~~') print('{}'.format(self.content)) cls.print = _print return cls
class Word(Document): pass
@printable class PrintableWord(Word): pass
class Pdf(Document): pass
class PrintablePdf(PrintableMixin, Pdf): pass
print(PrintablePdf.mro()) word = PrintablePdf('test\nabc') print(word.__dict__)
word.print()
|