类的初印象中,我们已经简单的介绍了类,包括类的定义、类对象和实例对象。本文将进一步学习类的继承、迭代器、发生器等等。

一、类的继承

单继承

派生类的定义如下:

class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>

基类名 BaseClassName 对于派生类来说必须是可见的。也可以继承在其他模块中定义的基类:

class DerivedClassName(module.BaseClassName):

对于派生类的属性引用:首先会在当前的派生类中搜索,如果没有找到,则会递归地去基类中寻找。

从C++术语上讲,Python 类中所有的方法都是vitual的,所以派生类可以覆写(override)基类的方法。在派生类中一个覆写的方法可能需要调用基类的方法,可以通过以下方式直接调用基类方法:

BaseClassName.method(self, arguments)

介绍两个函数:

  • isinstance(object, class_name):内置函数,用于判断实例对象 object 是不是类 class_name 或其派生类的实例,即object.__class__是 class_name 或其派生类时返回 True。
  • issubclass(class1, class2):内置函数,用于检查类 class1 是不是 class2 的派生类。例如issubclass(bool, int)会返回 True,因为 bool 是 int 的派生类。

多重继承

Python支持多重继承,一个多重继承的定义形如:

class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>

大多数的情况(未使用super)下,多重继承中属性搜索的方式是,深度优先,从左到右。在继承体系中,同样的类只会被搜寻一次。如果一个属性在当前类中没有被找到,它就会搜寻 Base1,然后递归地搜寻 Base1 的基类,然后如果还是没有找到,那么就会搜索 Base2,依次类推。

对于菱形继承,Python 3采用了 C3 线性化算法去搜索基类,保证每个基类只搜寻一次。所以对于使用者,无须担心这个问题,如果你想了解更多细节,可以看看Python类的方法解析顺序

二、自定义异常类

在《Python3的错误和异常》中,我们简单地介绍了Python中的异常处理、异常抛出以及清理动作。在学习了类的继承以后,我们就可以定义自己的异常类了。

自定义异常需要从 Exception 类派生,既可以是直接也可以是间接。例如:

class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value) try:
raise MyError(2*2)
except MyError as e:
print('My exception occurred, value:', e.value)
# 输出:My exception occurred, value: 4

在这个例子中, Exception 的默认方法 __init__() 被覆写了,现在新的异常类可以像其他的类一样做任何的事。当创建一个模块时,可能会有多种不同的异常,一种常用的做法就是,创建一个基类,然后派生出各种不同的异常:

class Error(Exception):
"""Base class for exceptions in this module."""
pass class InputError(Error):
def __init__(self, expression, message):
self.expression = expression
self.message = message class TransitionError(Error):
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message

需要特别注意的是,如果一个 except 后跟了一个异常类,则这个 except 语句不能捕获该异常类的基类,但能够捕获该异常类的子类。例如:

class B(Exception):
pass
class C(B):
pass
class D(C):
pass for e in [B, C, D]:
try:
raise e()
except D:
print('D')
except C:
print('C')
except B:
print('B')

上面的代码会按顺序输出B、C、D。如果将三个 except 语句逆序,则会打印B、B、B。

三、迭代器(Iterator)

到目前为止,你可能注意到,大多数的容器对象都可以使用 for 来迭代:

for element in [1, 2, 3]:
print(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line)

这种形式可以说是简洁明了。其实,for 语句在遍历容器的过程中隐式地调用了iter(),这个函数返回一个迭代器对象,迭代器对象定义了__next__() 方法,用以在每次访问时得到一个元素。当没有任何元素时,__next__() 将产生 StopIteration 异常来告诉 for 语句停止迭代。

内置函数 next()可以用来调用 __next__() 方法,示例:

>>> s = 'abc'
>>> it = iter(s) # 获取迭代器对象
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

在了解了迭代器的机制之后,就可以很简单的将迭代行为增加到你的类中。定义一个__iter__()方法返回一个具有 __next__() 的对象,如果这个类定义了 __next__() , 那么 __iter__() 仅需要返回 self:

class Reverse:
""" 逆序迭代一个序列 """
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index -= 1
return self.data[self.index]

测试:

# 测试
rev = Reverse('spam')
for c in rev:
print(c, end=' ') # 输出:m a p s # 单步测试
>>> rev = Reverse('spam')
>>> it = iter(rev) # 返回的 self 本身
>>> next(it) # 相当于 next(rev),因为iter(rev)返回本身
'm'
>>> next(it)
'a'
>>> next(it)
'p'
>>> next(it)
's'

四、生成器(Generator)

生成器(Generator)是用来创建迭代器的工具,它的形式跟函数一样,唯一的不同是生成器使用 yield 语句返回,而不是 return 语句。

有了生成器,我们不再需要自定义迭代器类(例如上面的 class Reverse),因为自定义迭代器类需要手动实现 __iter__()__next__() 方法,所以非常的麻烦。而生成器则会自动创建 __iter()__ 和 __next__(),可以更方便地生成一个迭代器,而且代码也会更短更简洁。例如,这里用生成器实现与 class Reverse 相同作用的迭代器:

def Reverse(data):
for idx in range(len(data)-1, -1, -1):
yield data[idx]

原来要十多行代码写一个迭代器类,现在使用生成器只需要3行代码!来测试一下:

# 测试
for c in Reverse('spam'):
print(c, end=' ') # 输出:m a p s # 单步测试
>>> rev = Reverse('spam')
>>> next(rev)
'm'
>>> next(rev)
'a'
>>> next(rev)
'p'
>>> next(rev)
's'

怎么样?现在感受到生成器的强大了吧。确实,生成器让我们可以方便的创建迭代器,而不必去自定义迭代器类那么麻烦。下面我们来了解一下生成器的工作过程:

def generator_func():
""" 这是一个简单的生成器 """
yield 1
yield 2
yield 3 # 测试
>>> g = generator_func()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

执行过程大致如下:

  1. 调用生成器函数将返回一个生成器。
  2. 第一次调用生成器的 next 方法时,生成器才开始执行生成器函数。直到遇到 yield 时暂停执行(挂起),并且将 yield 的参数作为此次的返回值。
  3. 之后每次调用 next 方法,生成器将从上次暂停的位置恢复并继续执行,直到再次遇到yield 时暂停,同样将 yield 的参数返回。
  4. 当调用 next 方法时生成器函数结束,则此次调用将抛出 StopIteration 异常(for循环终止条件)。

所以说,生成器的神奇之处在于每次使用 next() 执行生成器函数遇到 yield 返回时,生成器函数的“状态”会被冻结,所有的数据值和执行位置会被记住,一旦 next() 再次被调用,生成器函数会从它上次离开的地方继续执行。

五、类用作ADT

有些时候,类似于 Pascal 的“record”或 C 的“struct”这样的数据类型非常有用,绑定一些命名的数据。在 Python 中一个空的类定义就可以做到:

class Employee:
pass john = Employee() # Create an empty employee record # Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

一段 Python 代码中如果需要一个抽象数据类型,那么可以通过传递一个类给那个方法,就好像有了那个数据类型一样。

例如,如果你有一个函数用于格式化某些从文件对象中读取的数据,你可以定义一个有 read() 和 readline() 方法的类用于读取数据,然后将这个类作为一个参数传递给那个函数。

附:类变量与实例变量的区别

类变量(class variable)是类的属性和方法,它们会被类的所有实例共享。而实例变量(instance variable)是实例对象所特有的数据。如下:

class animal:
kind = 'dog' # class variable shared by all instances def __init__(self, color):
self.color = color # instance variable unique to each instance a1 = animal('black')
a2 = animal('white') print(a1.kind, a2.kind) # shared by all animals
print(a1.color, a2.color) # unique to each animal

当类变量(被所有实例共享)是一个可变的对象时,如 list 、dict ,那么在一个实例对象中改变该属性,其他实例的这个属性也会发生变化。这应该不难理解,例如:

class animal:
actions = [] # class variable shared by all instances def __init__(self, color):
self.color = color # instance variable unique to each instance def addActions(self, action):
self.actions.append(action) a1 = animal('black')
a2 = animal('white') a1.addActions('run') # 动物a1会跑
a2.addActions('fly') # 动物a2会飞 print(a1.actions, a2.actions) # 输出:['run', 'fly'] ['run', 'fly']

输出结果显示:动物 a1 和 a2 总是又相同的行为(actions),显然这不是我们想要的,因为不同的动物有不同的行为,比如狗会跑、鸟会飞、鱼会游……

对这个问题进行改进,我们只需要将 actions 这个属性变成实例变量,让它对每个实例对象都 unique ,而不是被所有实例共享:

class animal:

    def __init__(self, color):
self.color = color # instance variable
self.actions = [] # instance variable def addActions(self, action):
self.actions.append(action) a1 = animal('black')
a2 = animal('white') a1.addActions('run') # 动物a1会跑
a2.addActions('fly') # 动物a2会飞 print(a1.actions, a2.actions) # 输出:['run'] ['fly']

(全文完)

个人站点:http://songlee24.github.com

Python3基础(十一) 类的拓展的更多相关文章

  1. Python基础(十一) 类继承

    类继承: 继承的想法在于,充份利用已有类的功能,在其基础上来扩展来定义新的类. Parent Class(父类) 与 Child Class(子类): 被继承的类称为父类,继承的类称为子类,一个父类, ...

  2. Python3基础 A类作为B类的实例变量

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  3. python3在使用类基础时,遇到错误TypeError: module.**init**() takes at most 2 arguments (3 given)

    python3在使用类基础时,遇到错误TypeError: module.init() takes at most 2 arguments (3 given) 1.原因:直接导入的py文件,而没有导入 ...

  4. Python3基础(十二) 学习总结·附PDF

    Python是一门强大的解释型.面向对象的高级程序设计语言,它优雅.简单.可移植.易扩展,可用于桌面应用.系统编程.数据库编程.网络编程.web开发.图像处理.人工智能.数学应用.文本处理等等. 在学 ...

  5. Python基础-类的探讨(class)

    Python基础-类的探讨(class) 我们下面的探讨基于Python3,我实际测试使用的是Python3.2,Python3与Python2在类函数的类型上做了改变 1,类定义语法  Python ...

  6. Java基础(十一) Stream I/O and Files

    Java基础(十一) Stream I/O and Files 1. 流的概念 程序的主要任务是操纵数据.在Java中,把一组有序的数据序列称为流. 依据操作的方向,能够把流分为输入流和输出流两种.程 ...

  7. Java基础十一--多态

    Java基础十一--多态 一.多态定义 简单说:就是一个对象对应着不同类型. 多态在代码中的体现: 父类或者接口的引用指向其子类的对象. /* 对象的多态性. class 动物 {} class 猫 ...

  8. java基础-System类常用方法介绍

    java基础-System类常用方法介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.System类概念 在API中system类介绍的比较简单,我们给出定义,system中 ...

  9. python002 Python3 基础语法

    python002 Python3 基础语法 编码默认情况下,Python 3 源码文件以 UTF-8 编码,所有字符串都是 unicode 字符串. 当然你也可以为源码文件指定不同的编码: # -* ...

  10. Python3基础(八) 模块

    在程序中定义函数可以实现代码重用.但当你的代码逐渐变得庞大时,你可能想要把它分割成几个文件,以便能够更简单地维护.同时,你希望在一个文件中写的代码能够被其他文件所重用,这时我们应该使用模块(modul ...

随机推荐

  1. Javascript DOM 编程艺术(第二版)读书笔记——基本语法

    Javascript DOM 编程艺术(第二版),英Jeremy Keith.加Jeffrey Sambells著,杨涛.王建桥等译,人民邮电出版社. 学到这的时候,我发现一个问题:学习过程中,相当一 ...

  2. 三维重建:GitHub百度Apollo 2.0

    GitHub:https://github.com/ApolloAuto/apollo 1. 关于Apollo的数据:Apollo的数据会如何开放? 自动驾驶数据将包括具有高分辨率图像和像素级别标注的 ...

  3. java虚拟机(九)--常用jvm参数

    1.-Xms20M: 表示设置JVM启动内存的最小值为20M,必须以M为单位 2.-Xmx20M: 表示设置JVM启动内存的最大值为20M,必须以M为单位.将-Xmx和-Xms设置为一样可以避免JVM ...

  4. 【Redis】二、Redis高级特性

    (三) Redis高级特性   前面我们介绍了Redis的五种基本的数据类型,灵活运用这五种数据类型是使用Redis的基础,除此之外,Redis还有一些特性,掌握这些特性能对Redis有进一步的了解, ...

  5. 真机测试报错ERROR/AndroidRuntime: java.lang.RuntimeException: setParameters failed解决办法

    这个错误是和调用相机摄像头相关的. 产生这个错误的原因主要在于代码控制分辨率的显示和真机测试分辨率不一样. 一:解决办法 WindowManager wm = (WindowManager) getS ...

  6. TWaver GIS在电信中的使用

    GIS作为信息系统的重要组成部分,在电信行业中的应用由来已久.将GIS引入电信管理系统,GIS强大的功能就会得到充分的体现,GIS技术可以将各类电信信息系统以其特有的表现形有机整合在一起,并为真正做到 ...

  7. 洛谷——P2420 让我们异或吧

    P2420 让我们异或吧 题目描述 异或是一种神奇的运算,大部分人把它总结成不进位加法. 在生活中…xor运算也很常见.比如,对于一个问题的回答,是为1,否为0.那么: (A是否是男生 )xor( B ...

  8. centos 7桌面和命令行转行

    CentOS7图形界面与命令行界面(终端)切换(1)CentOS7 在图形界面进入dos界面 :ctrl+alt+F6 dos界面进入图形界面:ctrl+alt+F2 本机用的这个命令: (2)Cen ...

  9. centos7进入救援模式,修复错误配置

    因某些修改操作,导致系统重启后无法正常启动,此时可进入救援模式,修复错误配置即可. OS:centos 7 1.重启系统后,进入grup引导页面,选中第一项然后按“e” 进入编辑模式: 2.通过↓键找 ...

  10. Python的import module与form module import的区别

    import moduleName 如果要使用moduleName模块中的方法时,是moduleName.method(点方法), 比如moduleName中有个方法是set,则使用的是moduleN ...