继承

​ 在讲多态之前我们再复习下继承,下面是一个例子。

CircleRectangle 继承自 Shape,不同的图形,面积(area)计算方式不同。

# shape.py

class Shape:
def area(self):
return 0.0 class Circle(Shape):
def __init__(self, r=0.0):
self.r = r def area(self):
return math.pi * self.r * self.r class Rectangle(Shape):
def __init__(self, a, b):
self.a, self.b = a, b def area(self):
return self.a * self.b

用法比较直接:

>>> from shape import *
>>> circle = Circle(3.0)
>>> circle.area()
28.274333882308138
>>> rectangle = Rectangle(2.0, 3.0)
>>> rectangle.area()
6.0

如果 Circle 没有定义自己的 area

class Circle(Shape):
pass

那么它将继承父类 Shapearea

>>> Shape.area is Circle.area
True

一旦 Circle 定义了自己的 area,从 Shape 继承而来的那个 area 就被重写(overwrite)了:

>>> from shape import *
>>> Shape.area is Circle.area
False

通过类的字典更能明显地看清这一点:

>>> Shape.__dict__['area']
<function Shape.area at 0x0000000001FDB9D8>
>>> Circle.__dict__['area']
<function Circle.area at 0x0000000001FDBB70>

所以,子类重写父类的方法,其实只是把相同的属性名绑定到了不同的函数对象。可见 Python 是没有覆写(override)的概念的。

同理,即使 Shape 没有定义 area 也是可以的,Shape 作为“接口”,并不能得到语法的保证。

甚至可以动态的添加方法:

class Circle(Shape):
...
# def area(self):
# return math.pi * self.r * self.r # 为 Circle 添加 area 方法。
Circle.area = lambda self: math.pi * self.r * self.r

动态语言一般都是这么灵活,Python 也不例外。

Python 官方教程「9. Classes」第一句就是:

Compared with other programming languages, Python’s class mechanism adds classes with a minimum of new syntax and semantics.

Python 以最少的新的语法和语义实现了类机制,这一点确实让人惊叹,但是也让 C++ / Java 程序员感到颇为不适。

多态

如前所述,Python 没有重写(override)的概念。严格来讲,Python 并不支持「多态」,也不用支持多态,python是一种多态语言,崇尚鸭子类型**。

以下是维基百科中对鸭子类型得论述:

在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性。

-------------------------------------------

毫无疑问在python中对象也是一块内存,内存中除了包含属性、方法之外,还包含了对象得类型,我们通过引用来访问对象,比如a=A(),首先python创建一个对象A,然后声明一个变量a,再将变量a与对象A联系起来。变量a是没有类型得,它的类型取决于其关联的对象。a=A()时,a是一个A类型的引用,我们可以说a是A类型的,如果再将a赋值3,a=3,此时a就是一个整型的引用,但python并不是弱类型语言,在python中'2'+3会报错,而在php中'2'+3会得到5。可以这么理解,在python中变量类似与c中的指针,和c不同的是python中的变量可以指向任何类型,虽然这么说不太准确,但是理解起来容易点。

因此,在python运行过程中,参数被传递过来之前并不知道参数的类型,虽然python中的方法也是后期绑定,但是和java中多态的后期绑定却是不同的,java中的后期绑定至少知道对象的类型,而python中就不知道参数的类型。

为了解决继承结构中接口和实现的问题,或者说为了更好的用 Python 面向接口编程(设计模式所提倡的),我们需要人为的设一些规范。

请考虑 Shape.area() 除了简单的返回 0.0,有没有更好的实现?

以内建模块 asyncio 为例,AbstractEventLoop 原则上是一个接口,类似于 Java 中的接口或 C++ 中的纯虚类,但是 Python 并没有语法去保证这一点,为了尽量体现 AbstractEventLoop 是一个接口,首先在名字上标志它是抽象的(Abstract),然后让每个方法都抛出异常 NotImplementedError

class AbstractEventLoop:
def run_forever(self):
raise NotImplementedError
...

纵然如此,你是无法禁止用户实例化 AbstractEventLoop 的:

loop = asyncio.AbstractEventLoop()
try:
loop.run_forever()
except NotImplementedError:
pass

C++ 可以通过纯虚函数或设构造函数为 protected 来避免接口被实例化,Java 就更不用说了,接口就是接口,有完整的语法支持。

你也无法强制子类必须实现“接口”中定义的每一个方法,C++ 的纯虚函数可以强制这一点(Java 更不必说)。

就算子类「自以为」实现了“接口”中的方法,也不能保证方法的名字没有写错,C++ 的 override 关键字可以保证这一点(Java 更不必说)。

静态类型的缺失,让 Python 很难实现 C++ / Java 那样严格的多态检查机制。所以面向接口的编程,对 Python 来说,更多的要依靠程序员的素养。

回到 Shape 的例子,仿照 asyncio,我们把“接口”改成这样:

class AbstractShape:
def area(self):
raise NotImplementedError

这样,它才更像一个接口。

super

有时候,需要在子类中调用父类的方法。

比如图形都有颜色这个属性,所以不妨加一个参数 color__init__

class AbstractShape:
def __init__(self, color):
self.color = color

那么子类的 __init__() 势必也要跟着改动:

class Circle(AbstractShape):
def __init__(self, color, r=0.0):
super().__init__(color)
self.r = r

通过 supercolor 传给父类的 __init__()。其实不用 super 也行:

class Circle(AbstractShape):
def __init__(self, color, r=0.0):
AbstractShape.__init__(self, color)
self.r = r

但是 super 是推荐的做法,因为它避免了硬编码,也能处理多继承的情况。

python面向对象(五)之多态的更多相关文章

  1. Python面向对象三要素-多态

    Python面向对象3要素-多态 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.  一.多态概述 OCP原则:多用“继承”,少修改. 继承的用途:在子类上实现对基类的增强,实现多态. ...

  2. python面向对象(封装,继承,多态)

    python面向对象(封装,继承,多态) 学习完本篇,你将会深入掌握 如何封装一个优雅的借口 python是如何实现继承 python的多态 封装 含义: 1.把对象的属性和方法结合成一个独立的单位, ...

  3. Python面向对象-继承和多态特性

    继承 在面向对象的程序设计中,当我们定义一个class时候,可以从现有的class继承,新的class成为子类,被继承的class称为基类,父类或超类. 比如,编写一个名为Animal的class: ...

  4. python面向对象(封装、多态、反射)

    目录 面向对象之封装 @property 面向对象之多态 面向对象之反射 面向对象之封装 含义 将类中的某些名字按照特殊的书写方式"隐藏"起来,不让外界直接调用,目的是为了不然外界 ...

  5. Python面向对象 -- 继承和多态、获取对象信息、实例属性和类属性

    继承和多态 继承的好处: 1,子类可以使用父类的全部功能 2,多态:当子类和父类都存在相同的方法时,子类的方法会覆盖父类的方法,即调用时会调用子类的方法.这就是继承的另一个好处:多态. 多态: 调用方 ...

  6. python面向对象之继承/多态/封装

    老师说,按继承/多态/封装这个顺序来讲. 子类使用父类的方法: #!/usr/bin/env python # coding:utf-8 class Vehicle: def __init__(sel ...

  7. python面向对象-封装and多态

    python 接口类和抽象类 为什么讲封装之前要将这个东西? 我才不会说为什么 首先: python没有接口类这个概念!!!!!!!! 哈哈哈......神经病 python抽象类和接口类更接近于一种 ...

  8. Python()- 面向对象三大特性----多态

    多态: python 生来支持多态白话:一种事物的多种形态 (动物可以继承给狗,也可以继承给猫) class Animal: pass class Dog(Animal): def attack(se ...

  9. Python 面向对象(五) 描述器

    使用到了__get__,__set__,__delete__中的任何一种方法的类就是描述器 描述器的定义 一个类实现了__get__,__set__,__delete__中任意一个,这个类就是描述器. ...

  10. python 面向对象五 获取对象信息 type isinstance getattr setattr hasattr

    一.type()函数 判断基本数据类型可以直接写int,str等: >>> class Animal(object): ... pass ... >>> type( ...

随机推荐

  1. Mininet 系列实验(四)

    实验内容 本次实验拓扑图: 在该环境下,h0 向 h1 发送数据包,由于在 mininet 脚本中设置了连接损耗率,在传输过程中会丢失一些包,本次实验的目的是展示如何通过控制器计算路径损耗速率(h0- ...

  2. BZOJ2159 Crash 的文明世界 【第二类斯特林数 + 树形dp】

    题目链接 BZOJ2159 题解 显然不能直接做点分之类的,观察式子中存在式子\(n^k\) 可以考虑到 \[n^k = \sum\limits_{i = 0} \begin{Bmatrix} k \ ...

  3. 用Gradle命令行编译Android工程

    在Android sdk 目录下的samples/android-21/ 文件夹下,任找一个工程,如果在命令行直接编译 可能会报这种错误:gradle buile.gradle FAILURE: Bu ...

  4. 阿里云 邮件发送(Python)

    #coding:utf8 from smtplib import SMTP_SSL from email.header import Header from email.mime.text impor ...

  5. Could not update Activiti database schema: unknown version from database: '5.20.0.1'

    转: Could not update Activiti database schema: unknown version from database: '5.20.0.1' 2017年11月22日 ...

  6. mysqldump参数详细说明(转)

    Mysqldump参数大全(参数来源于mysql5.5.19源码)   参数 参数说明 --all-databases  , -A 导出全部数据库. mysqldump  -uroot -p --al ...

  7. python常用模块(3)

    ConfigParser模块学习 ConfigParser模块在python中是用来读取配置文件,配置文件的格式跟windows下的ini配置文件相似,可以包含一个或多个节(section),每个节可 ...

  8. Linux下查看系统版本和make版本

    一.查看Linux内核版本命令(两种方法): 1.cat /proc/versionz [root@localhost ~]# cat /proc/versionLinux version 2.6.1 ...

  9. NCPC2016-E- Exponial

    题目描述 Illustration of exponial(3) (not to scale), Picture by C.M. de Talleyrand-Périgord via Wikimedi ...

  10. openresty/1.11.2.1性能测试

    测试数据 ab -n -c -k http://127.0.0.1/get_cache_value nginx.conf lua_shared_dict cache_ngx 128m; server ...