微信公众号: 码农充电站 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 能够表现出鸭子特性的原因.
(完.)
来源: https://www.cnblogs.com/codeshell/p/13233821.html