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

一、类的继承

单继承

派生类的定义如下:

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. Objective-C设计模式——适配器Adapter(接口适配)

    适配器模式 适配器模式通俗来讲,其实就是对客户端添加新的类但却不修改客户端和新的类的接口.此时我们需要自己来实现适配,在适配器模式中有Target对象,即客户端所需要的接口对象,Adaptee对象,即 ...

  2. es6之iterator,for...of

    遍历器(Iterator)是一种统一的接口机制,来处理所有不同的数据结构. JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和 ...

  3. git删除本地分支失败,报错error: branch 'test219' not found.

    错误: 删除本地分支报错,操作如下: git branch -d test219 操作失败,错误信息:error: branch 'test219' not found git branch -D t ...

  4. 获取WebBrowser全cookie 和 httpWebRequest 异步获取页面数据

    获取WebBrowser全cookie [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true) ...

  5. HDU_6016_(Bestcoder round #92 1002)_(dfs)(暴力)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6016 题意:给定男羊和女羊的朋友关系,即给定一个图,问从任意一只羊开始连续数四只不相同的羊的方法数. ...

  6. Vue渲染原理

    现在基本所有的框架都已经认同这个看法——DOM应尽可能是一个函数式到状态的映射.状态即是唯一的真相,而DOM状态只是数据状态的一个映射.如下图所示,所有的逻辑尽可能在状态的层面去进行,当状态改变的时候 ...

  7. 梦想CAD控件COM接口搜索图面上的文字

    点击此处下载演示实例 主要用到函数说明: _DMxDrawX::NewSelectionSet 实例化一个构造选择集进行过滤,该类封装了选择集及其处理函数. _DMxDrawX::NewResbuf ...

  8. pringboot开启找回Run Dashboard

    代码中加入 <option name="configurationTypes"> <set> <option value="SpringBo ...

  9. 【webpack插件使用】在开发中快速掌握并使用Webpack构建web应用程序

    1.webpack-dev-server插件的基本使用 入门程序 const path = require('path'); // 导出一个Webpack的配置对象(通过node中的模块操作,向外暴露 ...

  10. eclipse 中导入 MyBatis 的源码

    (1)选中 Mybatis-3.2.2.jar ,右击,在弹出的快捷菜单中选择 “Properties” 选项,进入属性界面. (2)进入属性界面后,选中 “Java  Source Attachme ...