内容简介:微信公众号:码农充电站pro个人主页:程序不是年轻的专利,但是,它属于年轻。
微信公众号:码农充电站pro
个人主页: https://codeshellme.github.io
程序不是年轻的专利,但是,它属于年轻。
目录
我们已经知道 封装
, 继承
和 多态
是面向对象的三大特征,面向对象语言都会提供这些机制。
1,封装
在这一节介绍类的 私有属性和方法
的时候,我们已经讲到过 封装
。
封装
就是在设计一个类的时候,只允许使用者访问他需要的方法,将复杂的,没有必要让使用者知道的方法隐藏起来。这样,使用者只需关注他需要的东西,为其屏蔽了复杂性。
私有性
就是实现 封装
的一种手段,这样,类的设计者就可以控制类中的哪些属性和方法可以被使用者访问到。一般,类中的属性,和一些复杂的方法都不会暴露给使用者。
由于前边的章节介绍过封装,这里就不再举例说明了。
2,继承
通过 继承
的机制,可使得 子类
轻松的拥有 父类
中的 属性和方法
。 继承
也是一种 代码复用
的方式。
Python 支持类的继承, 继承的类
叫做 子类
或者 派生类
, 被继承的类
叫做 父类
或 基类
。
继承的语法如下:
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
3,多继承
多继承
就是一个子类同时继承多个父类,这样,这个子类就同时拥有了多个父类的特性。
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()
方法。
建议:
由于 多继承
会使类的继承关系变得复杂,所以并不提倡过多的使用 多继承
。
4,多态
多态
从字面上理解就是一个事物可以呈现多种状态。 继承
是多态的基础。
在上面的例子中,类 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()
函数传递不同类型的参数,输出了不同的结果,这就是 多态
。
5,鸭子类型
在 静态类型
语言中,有严格的类型判断,上面的 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 能够表现出 鸭子特性
的原因。
(完。)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法导论(原书第3版)
Thomas H.Cormen、Charles E.Leiserson、Ronald L.Rivest、Clifford Stein / 殷建平、徐云、王刚、刘晓光、苏明、邹恒明、王宏志 / 机械工业出版社 / 2012-12 / 128.00元
在有关算法的书中,有一些叙述非常严谨,但不够全面;另一些涉及了大量的题材,但又缺乏严谨性。本书将严谨性和全面性融为一体,深入讨论各类算法,并着力使这些算法的设计和分析能为各个层次的读者接受。全书各章自成体系,可以作为独立的学习单元;算法以英语和伪代码的形式描述,具备初步程序设计经验的人就能看懂;说明和解释力求浅显易懂,不失深度和数学严谨性。 全书选材经典、内容丰富、结构合理、逻辑清晰,对本科......一起来看看 《算法导论(原书第3版)》 这本书的介绍吧!