Python 的类、继承和多态详解

类的定义

假如要定义一个类 Point,表示二维的坐标点:

# point.py
class Point:
  def __init__(self, x=0, y=0):
    self.x, self.y = x, y

最最基本的就是 __init__ 方法,相当于 C++ / Java 的构造函数。带双下划线 __ 的方法都是特殊方法,除了 __init__ 还有很多,后面会有介绍。

参数 self 相当于 C++ 的 this,表示当前实例,所有方法都有这个参数,但是调用时并不需要指定。

>>> from point import *
>>> p = Point(10, 10) # __init__ 被调用
>>> type(p)
<class 'point.Point'>
>>> p.x, p.y
(10, 10)

几乎所有的特殊方法(包括 __init__)都是隐式调用的(不直接调用)。

对一切皆对象的 Python 来说,类自己当然也是对象:

>>> type(Point)
<class 'type'>
>>> dir(Point)
['__class__', '__delattr__', '__dict__', ..., '__init__', ...]
>>> Point.__class__
<class 'type'>

Point 是 type 的一个实例,这和 p 是 Point 的一个实例是一回事。

现添加方法 set:

class Point:
  ...
  def set(self, x, y):
    self.x, self.y = x, y
>>> p = Point(10, 10)
>>> p.set(0, 0)
>>> p.x, p.y
(0, 0)

p.set(...) 其实只是一个语法糖,你也可以写成 Point.set(p, ...),这样就能明显看出 p 就是 self 参数了:

>>> Point.set(p, 0, 0)
>>> p.x, p.y
(0, 0)

值得注意的是,self 并不是关键字,甚至可以用其它名字替代,比如 this:

class Point:
  ...
  def set(this, x, y):
    this.x, this.y = x, y

与 C++ 不同的是,“成员变量”必须要加 self. 前缀,否则就变成类的属性(相当于 C++ 静态成员),而不是对象的属性了。

访问控制

Python 没有 public / protected / private 这样的访问控制,如果你非要表示“私有”,习惯是加双下划线前缀。

class Point:
  def __init__(self, x=0, y=0):
    self.__x, self.__y = x, y

  def set(self, x, y):
    self.__x, self.__y = x, y

  def __f(self):
    pass

__x、__y 和 __f 就相当于私有了:

>>> p = Point(10, 10)
>>> p.__x
...
AttributeError: 'Point' object has no attribute '__x'
>>> p.__f()
...
AttributeError: 'Point' object has no attribute '__f'

_repr_

尝试打印 Point 实例:

>>> p = Point(10, 10)
>>> p
<point.Point object at 0x000000000272AA20>

通常,这并不是我们想要的输出,我们想要的是:

>>> p
Point(10, 10)

添加特殊方法 __repr__ 即可实现:

class Point:
  def __repr__(self):
    return 'Point({}, {})'.format(self.__x, self.__y)

不难看出,交互模式在打印 p 时其实是调用了 repr(p):

>>> repr(p)
'Point(10, 10)'

_str_

如果没有提供 __str__,str() 缺省使用 repr() 的结果。
 这两者都是对象的字符串形式的表示,但还是有点差别的。简单来说,repr() 的结果面向的是解释器,通常都是合法的 Python 代码,比如 Point(10, 10);而 str() 的结果面向用户,更简洁,比如 (10, 10)。

按照这个原则,我们为 Point 提供 __str__ 的定义如下:

class Point:
  def __str__(self):
    return '({}, {})'.format(self.__x, self.__y)

_add_

两个坐标点相加是个很合理的需求。

>>> p1 = Point(10, 10)
>>> p2 = Point(10, 10)
>>> p3 = p1 + p2
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'

添加特殊方法 __add__ 即可做到:

class Point:
  def __add__(self, other):
    return Point(self.__x + other.__x, self.__y + other.__y)
>>> p3 = p1 + p2
>>> p3
Point(20, 20)

这就像 C++ 里的操作符重载一样。
Python 的内建类型,比如字符串、列表,都“重载”了 + 操作符。

特殊方法还有很多,这里就不逐一介绍了。

继承

举一个教科书中最常见的例子。Circle 和 Rectangle 继承自 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

那么它将继承父类 Shape 的 area:

>>> 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 面向接口编程(设计模式所提倡的),我们需要人为的设一些规范。

请考虑 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

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

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

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

时间: 2017-07-13

JPype实现在python中调用JAVA的实例

一.JPype简述 1.JPype是什么? JPype是一个能够让 python 代码方便地调用 Java 代码的工具,从而克服了 python 在某些领域(如服务器端编程)中的不足. 2.JPype与Jython(JPython后继者)的区别? 1)运行环境不同:jython运行在jvm上,而JPype的实际运行环境仍然是python runtime,只是在运行期间启动了一个嵌入的jvm: 2)使用者不同:jython是给java程序玩的,JPype是给python程序员玩的. 二.JPype

python实现rsa加密实例详解

python实现rsa加密实例详解 一 代码 import rsa key = rsa.newkeys(3000)#生成随机秘钥 privateKey = key[1]#私钥 publicKey = key[0]#公钥 message ='sanxi Now is better than never.' print('Before encrypted:',message) message = message.encode() cryptedMessage = rsa.encrypt(messag

python 换位密码算法的实例详解

 python 换位密码算法的实例详解 一前言: 换位密码基本原理:先把明文按照固定长度进行分组,然后对每一组的字符进行换位操作,从而实现加密.例如,字符串"Error should never pass silently",使用秘钥1432进行加密时,首先将字符串分成若干长度为4的分组,然后对每个分组的字符进行换位,第1个和第3个字符位置不变,把第2个字符和第4个字符交换位置,得到"Eorrrs shluoden v repssa liseltny" 二 代码:

Python实现excel转sqlite的方法

本文实例讲述了Python实现excel转sqlite的方法.分享给大家供大家参考,具体如下: Python环境的安装配置就不说了,个人喜欢pydev的开发环境. python解析excel需要使用第三方的库,这里选择使用xlrd 先看excel内容: 然后是生成的数据库: 下面是源代码: #!/usr/bin/python # encoding=utf-8 ''''' Created on 2013-4-2 @author: ting ''' from xlrd import open_wor

简单谈谈Python中的json与pickle

这是用于序列化的两个模块: • json: 用于字符串和python数据类型间进行转换 • pickle: 用于python特有的类型和python的数据类型间进行转换 Json 模块提供了四个功能:dumps.dump.loads.load pickle 模块提供了四个功能:dumps.dump.loads.load import pickle data = {'k1':123, 'k2':888} #dumps可以将数据类型转换成只有python才认识的字符串 p_str = pickle.

Python使用plotly绘制数据图表的方法

导语:使用 python-plotly 模块来进行压测数据的绘制,并且生成静态 html 页面结果展示. 不少小伙伴在开发过程中都有对模块进行压测的经历,压测结束后大家往往喜欢使用Excel处理压测数据并绘制数据可视化视图,但这样不能很方便的使用web页面进行数据展示.本文将介绍使用python-plotly模块来进行压测数据的绘制,并且生成静态html页面方便结果展示. Plotly简介 Plotly是一款使用JavaScript开发的制图工具,提供了与主流数据分析语言交互的API(如:Pyt

python对DICOM图像的读取方法详解

DICOM介绍 DICOM3.0图像,由医学影像设备产生标准医学影像图像,DICOM被广泛应用于放射医疗,心血管成像以及放射诊疗诊断设备(X射线,CT,核磁共振,超声等),并且在眼科和牙科等其它医学领域得到越来越深入广泛的应用.在数以万计的在用医学成像设备中,DICOM是部署最为广泛的医疗信息标准之一.当前大约有百亿级符合DICOM标准的医学图像用于临床使用. 看似神秘的图像文件,究竟是如何读取呢?网上随便 一搜,都有很多方法,但缺乏比较系统的使用方法,下文综合百度资料,结合python2.7,

Python基础教程之浅拷贝和深拷贝实例详解

Python基础教程之浅拷贝和深拷贝实例详解            网上关于Python的深拷贝和浅拷贝的文章很多,这里对三种拷贝进行比较并附实例,大家可以参考下 一般的复制 #encoding:utf-8 #定义一个嵌套集合 lista=[1,2,3,[4,5,6,[7,8,9]]] listb=lista #分别打印出 lista和listb的地址值 print id(lista) #4511103096 print id(listb) #4511103096 #修改lista中的内容,li

Python基础之函数原理与应用实例详解

本文实例讲述了Python基础之函数原理与应用.分享给大家供大家参考,具体如下: 目标 函数的快速体验 函数的基本使用 函数的参数 函数的返回值 函数的嵌套调用 在模块中定义函数 01. 函数的快速体验 1.1 快速体验 所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用 函数的使用包含两个步骤: 定义函数 -- 封装 独立的功能 调用函数 -- 享受 封装 的成果 函数的作用,在开发程序时,使用函数可以提高编写的效率以及代码的 重用 演练步骤 新建 04_函数 项目

Python基础之字典常见操作经典实例详解

本文实例讲述了Python基础之字典常见操作.分享给大家供大家参考,具体如下: Python字典 Python 中的字典是Python中一个键值映射的数据结构,下面介绍一下如何优雅的操作字典. 创建字典 Python有两种方法可以创建字典,第一种是使用花括号,另一种是使用内建 函数dict >>> info = {} >>> info = dict() 初始化字典 Python可以在创建字典的时候初始化字典 >>> info = {"name

Python基础之字符串常见操作经典实例详解

本文实例讲述了Python基础之字符串常见操作.分享给大家供大家参考,具体如下: 字符串基本操作 切片 # str[beg:end] # (下标从 0 开始)从下标为beg开始算起,切取到下标为 end-1 的元素,切取的区间为 [beg, end) str = ' python str ' print (str[3:6]) # tho # str[beg:end:step] # 取 [beg, end) 之间的元素,每隔 step 个取一个 print (str[2:7:2]) # yhn 原

Python基础之列表常见操作经典实例详解

本文实例讲述了Python基础之列表常见操作.分享给大家供大家参考,具体如下: Python中的列表操作 列表是Python中使用最频繁的数据类型[可以说没有之一] 一组有序项目的集合 可变的数据类型[可进行增删改查] 列表中可以包含任何数据类型,也可包含另一个列表[可任意组合嵌套] 列表是以方括号" []"包围的数据集合,不同成员以" ,"分隔 列表可通过序号访问其中成员 创建列表的方式 #创建一个含有元素1,2,4,8,16,32的列表 #方法1 L = [1,

基于python 二维数组及画图的实例详解

1.二维数组取值 注:不管是二维数组,还是一维数组,数组里的数据类型要一模一样,即若是数值型,全为数值型 #二维数组 import numpy as np list1=[[1.73,1.68,1.71,1.89,1.78], [54.4,59.2,63.6,88.4,68.7]] list3=[1.73,1.68,1.71,1.89,1.78] list4=[54.4,59.2,63.6,88.4,68.7] list5=np.array([1.73,1.68,1.71,1.89,1.78])

Python元组拆包和具名元组解析实例详解

前言 在Python中元组是一个相较于其他语言比较特别的一个内置序列类型.有些python入门教程把元组成为"不可变的列表",这种说法是不完备的,其并没有完整的概括元组的特点.除了用作不可变的列表,它还可以用于没有字段名的数据记录.下面的内容就围绕元组作为数据记录属性展开,并介绍带字段名的具名元组函数namedtuple,列表属性不再本文中叙述. 元组对于数据的记录 元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置,正是这个位置信息给数据赋予了意义. 下面的一段代码就演

python里使用正则表达式的组嵌套实例详解

python里使用正则表达式的组嵌套实例详解 由于组本身是一个完整的正则表达式,所以可以将组嵌套在其他组中,以构建更复杂的表达式.下面的例子,就是进行组嵌套的例子: #python 3.6 #蔡军生 #http://blog.csdn.net/caimouse/article/details/51749579 # import re def test_patterns(text, patterns): """Given source text and a list of pa

python 读取excel文件生成sql文件实例详解

python 读取excel文件生成sql文件实例详解 学了python这么久,总算是在工作中用到一次.这次是为了从excel文件中读取数据然后写入到数据库中.这个逻辑用java来写的话就太重了,所以这次考虑通过python脚本来实现. 在此之前需要给python添加一个xlrd模块,这个模块是专门用来操作excel文件的. 在mac中可以通过easy_install xlrd命令实现自动安装模块 import xdrlib ,sys import xlrd def open_excel(fil

python文件特定行插入和替换实例详解

python文件特定行插入和替换实例详解 python提供了read,write,但和很多语言类似似乎没有提供insert.当然真要提供的话,肯定是可以实现的,但可能引入insert会带来很多其他问题,比如在插入过程中crash掉可能会导致后面的内容没来得及写回. 不过用fileinput可以简单实现在特定行插入的需求: Python代码 import os import fileinput def file_insert(fname,linenos=[],strings=[]): ""