微信公众号:码农充电站pro
我的主页:https://codeshellme.github.iohtml
程序不是年轻的专利,可是,它属于年轻。python
目录git
咱们已经知道封装
,继承
和多态
是面向对象的三大特征,面向对象语言都会提供这些机制。github
在这一节介绍类的私有属性和方法
的时候,咱们已经讲到过封装
。shell
封装
就是在设计一个类的时候,只容许使用者访问他须要的方法,将复杂的,没有必要让使用者知道的方法隐藏起来。这样,使用者只需关注他须要的东西,为其屏蔽了复杂性。微信
私有性
就是实现封装
的一种手段,这样,类的设计者就能够控制类中的哪些属性和方法能够被使用者访问到。通常,类中的属性,和一些复杂的方法都不会暴露给使用者。函数
因为前边的章节介绍过封装,这里就再也不举例说明了。ui
经过继承
的机制,可以使得子类
轻松的拥有父类
中的属性和方法
。继承
也是一种代码复用
的方式。设计
Python 支持类的继承,继承的类
叫作子类
或者派生类
,被继承的类
叫作父类
或基类
。code
继承的语法以下:
class 子类名(父类名): pass
在子类名
后边的括号中,写入要继承的父类。
object
类
在Python 的继承体系中,object
是最顶层类,它是全部类的父类。在定义一个类时,若是没有继承任何类,会默认继承object
类。以下两种定义方式是等价的:
# 没有显示继承任何类,默认继承 object class A1: pass # 显示继承 object class A2(object): pass
每一个类中都有一个mro
方法,该方法能够打印类的继承关系(顺序)。咱们来查看A1
和 A2
的继承关系:
>>> A1.mro() [<class '__main__.A1'>, <class 'object'>] >>> >>> A2.mro() [<class '__main__.A2'>, <class 'object'>]
可见这两个类都继承了 object
类。
继承中的__init__
方法
当一个子类继承一个父类时,若是子类中没有定义__init__
,在建立子类的对象时,会调用父类的__init__
方法,以下:
#! /usr/bin/env python3 class A(object): def __init__(self): print('A.__init__') class B(A): pass
以上代码中,B
继承了A
,A
中有__init__
方法,B
中没有__init__
方法,建立类B
的对象b
:
>>> b = B() A.__init__
可见A
中的__init__
被执行了。
方法覆盖
若是类B
中也定义了__init__
方法,那么,就只会执行B
中的__init__
方法,而不会执行A
中的__init__
方法:
#! /usr/bin/env python3 class A(object): def __init__(self): print('A.__init__') class B(A): def __init__(self): print('B.__init__')
此时建立B
的对象b
:
>>> b = B() B.__init__
可见,此时只执行了B
中的__init__
方法。这实际上是方法覆盖
的缘由,由于子类
中的__init__
与父类
中的__init__
的参数列表同样,此时,子类中的方法覆盖了父类中的方法,因此建立对象b
时,只会执行B
中的__init__
方法。
当发生继承关系(即一个子类继承一个父类)时,若是子类中的一个方法与父类中的一个方法
如出一辙
(即方法名相同,参数列表也相同),这种状况就是方法覆盖
(子类中的方法会覆盖父类中的方法)。
方法重载
当方法名
与参数列表
都同样时会发生方法覆盖
;当方法名
同样,参数列表
不同时,会发生方法重载
。
在单个类中,代码以下:
#! /usr/bin/env python3 class A(object): def __init__(self): print('A.__init__') def test(self): print('test...') def test(self, i): print('test... i:%s' % i)
类A
中的两个test
方法,方法名
相同,参数列表
不一样。
其实这种状况在Java
和 C++
是容许的,就是方法重载
。而在Python 中,虽然在类中这样写不会报错,但实际上,下面的test(self, i)
已经把上面的test(self)
给覆盖掉了。建立出来的对象只能调用test(self, i)
,而test(self)
是不存在的。
示例:
>>> a = A() # 建立 A 的对象 a A.__init__ >>> >>> a.test(123) # 能够调用 test(self, i) 方法 test... i:123 >>> >>> a.test() # 调用 test(self) 发生异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: test() missing 1 required positional argument: 'i'
在继承关系中,代码以下:
#! /usr/bin/env python3 class A(object): def __init__(self): print('A.__init__') def test(self): print('test...') class B(A): def __init__(self): print('B.__init__') def test(self, i): print('test... i:%s' % i)
上面代码中B
继承了A
,B
和 A
中都有一个名为test
的方法,可是参数列表
不一样。
这种状况跟在单个类中的状况是同样的,在类B
中,test(self, i)
会覆盖A 中的test(self)
,类B
的对象只能调用test(self, i)
,而不能调用test(self)
。
示例:
>>> b = B() # 建立 B 的对象 B.__init__ >>> >>> b.test(123) # 能够调用 test(self, i) 方法 test... i:123 >>> >>> b.test() # 调用 test(self) 方法,出现异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: test() missing 1 required positional argument: 'i'
super()
方法
super()
方法用于调用父类中的方法。
示例代码:
#! /usr/bin/env python3 class A(object): def __init__(self): print('A.__init__') def test(self): print('class_A test...') class B(A): def __init__(self): print('B.__init__') super().__init__() # 调用父类中的构造方法 def test(self, i): print('class_B test... i:%s' % i) super().test() # 调用父类中的 test 方法
演示:
>>> b = B() # 建立 B 的对象 B.__init__ # 调用 B 的构造方法 A.__init__ # 调用 A 的构造方法 >>> >>> b.test(123) # 调用 B 中的 test 方法 class_B test... i:123 class_A test... # 执行 A 中的 test 方法
is-a
关系
一个子类的对象,同时也是一个父类的对象,这叫作is-a
关系。可是一个父类的对象,不必定是一个子类的对象。
这很好理解,就像,猫必定是动物,但动物不必定是猫。
咱们可使用isinstance()
函数来判断一个对象是不是一个类的实例。
好比咱们有以下两个类,Cat
继承了 Animal
:
#! /usr/bin/env python3 class Animal(object): pass class Cat(Animal): pass
来看下对象和类之间的从属关系:
>>> a = Animal() # 建立 Animal 的对象 >>> c = Cat() # 建立 Cat 的对象 >>> >>> isinstance(a, Animal) # a 必定是 Animal 的实例 True >>> isinstance(c, Cat) # c 必定是 Cat 的实例 True >>> >>> isinstance(c, Animal) # Cat 继承了 Animal,因此 c 也是 Animal 的实例 True >>> isinstance(a, Cat) # 但 a 不是 Cat 的实例 False
多继承
就是一个子类同时继承多个父类,这样,这个子类就同时拥有了多个父类的特性。
C++ 语言中容许多继承,但因为多继承会使得类的继承关系变得复杂。所以,到了Java 中,就禁止了多继承的方式,取而代之的是,在Java 中容许同时继承多个接口
。
Python 中也容许多继承,语法以下:
# 括号中能够写多个父类 class 子类名(父类1, 父类2, ...): pass
咱们构造一个以下的继承关系:
代码以下:
#! /usr/bin/env python3 class A(object): def test(self): print('class_A test...') class B(A): def test(self): print('class_B test...') class C(A): def test(self): print('class_C test...') class D(B, C): pass
类A
,B
,C
中都有test()
方法,D
中没有test()
方法。
使用D
类中的mro()
方法查看继承关系:
>>> D.mro() [<class 'Test.D'>, <class 'Test.B'>, <class 'Test.C'>, <class 'Test.A'>, <class 'object'>]
建立D
的对象:
>>> d = D()
若是类D
中有test()
方法,那么d.test()
确定会调用D
中的test()
方法,这种状况很简单,不用多说。
当类D
中没有test()
方法时,而它继承的父类 B
和 C
中都有 test()
方法,此时会调用哪一个test()
呢?
>>> d.test() class_B test...
能够看到d.test()
调用了类B
中的 test()
方法。
实际上这种状况下,Python 解释器会根据D.mro()
的输出结果来依次查找test()
方法,即查找顺序是D->B->C->A->object
。
因此d.test()
调用了类B
中的 test()
方法。
建议:
因为
多继承
会使类的继承关系变得复杂,因此并不提倡过多的使用多继承
。
多态
从字面上理解就是一个事物能够呈现多种状态。继承
是多态的基础。
在上面的例子中,类D
的对象d
调用test()
方法时,沿着继承链
(D.mro()
)查找合适的test()
方法的过程,就是多态的表现过程。
好比,咱们有如下几个类:
Animal
:有一个speak()
方法Cat
:继承Animal
类,有本身的speak()
方法Dog
:继承Animal
类,有本身的speak()
方法Duck
:继承Animal
类,有本身的speak()
方法Cat
,Dog
,Duck
都属于动物,所以都继承Animal
,代码以下:
#! /usr/bin/env python3 class Animal(object): def speak(self): print('动物会说话...') class Cat(Animal): def speak(self): print('喵喵...') class Dog(Animal): def speak(self): print('汪汪...') class Duck(Animal): def speak(self): print('嘎嘎...') def animal_speak(animal): animal.speak()
咱们还定义了一个animal_speak
函数,它接受一个参数animal
,在函数内,调用了speak()
方法。
实际上,这种状况下,咱们调用animal_speak
函数时,能够为它传递Animal
类型的对象,以及任何的Animal
子类的对象。
传递Animal
的对象时,调用了Animal
类中的 speak()
:
>>> animal_speak(Animal()) 动物会说话...
传递Cat
的对象时,调用了Cat
类中的 speak()
:
>>> animal_speak(Cat()) 喵喵...
传递Dog
的对象时,调用了Dog
类中的 speak()
:
>>> animal_speak(Dog()) 汪汪...
传递Duck
的对象时,调用了Duck
类中的 speak()
:
>>> animal_speak(Duck()) 嘎嘎...
能够看到,咱们能够给animal_speak()
函数传递多种不一样类型
的对象,为animal_speak()
函数传递不一样类型的参数,输出了不一样的结果,这就是多态
。
在静态类型
语言中,有严格的类型判断,上面的animal_speak()
函数的参数只能传递Animal
及其子类
的对象。
而Python 属于动态类型
语言,不会进行严格的类型判断。
所以,咱们不只能够为animal_speak()
函数传递Animal
及其子类
的对象,还能够传递其它与Animal
类绝不相关的类的对象,只要该类中有speak()
方法就行。
这种特性,在Python 中被叫作鸭子类型
,意思就是,只要一个事物走起来像鸭子,叫起来像鸭子,那么它就是鸭子,即便它不是真正的鸭子
。
从代码上来讲,只要一个类中有speak()
方法,那么就能够将该类的对象传递给animal_speak()
函数。
好比,有一个鼓类Drum
,其中有一个函数speak()
:
class Drum(object): def speak(self): print('咚咚...')
那么,类Drum
的对象也能够传递给animal_speak()
函数,即便Drum
与Animal
类绝不相关:
>>> animal_speak(Drum()) 咚咚...
从另外一个角度来考虑,实际上Python 函数中的参数,并无标明参数的类型。在animal_speak()
函数中,咱们只是将参数叫作了animal
而已,所以咱们就认为animal_speak()
函数应该接受Animal 类及其子类的对象,其实这仅仅只是咱们认为的而已。
计算机并不知道animal
的含义,若是咱们将原来的animal_speak()
函数:
def animal_speak(animal): animal.speak()
改写成:
def animal_speak(a): a.speak()
实际上,咱们知道,这两个函数并无任何区别。所以,参数a
能够是任意的类型,只要a
中有speak()
方法就行。这就是Python 可以表现出鸭子特性
的缘由。
(完。)
推荐阅读:
Python 简明教程 --- 16,Python 高阶函数
Python 简明教程 --- 17,Python 模块与包
Python 简明教程 --- 18,Python 面向对象
Python 简明教程 --- 19,Python 类与对象
Python 简明教程 --- 20,Python 类中的属性与方法
欢迎关注做者公众号,获取更多技术干货。