Python3基础(十一) 类的拓展
在类的初印象中,我们已经简单的介绍了类,包括类的定义、类对象和实例对象。本文将进一步学习类的继承、迭代器、发生器等等。
一、类的继承
单继承
派生类的定义如下:
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
执行过程大致如下:
- 调用生成器函数将返回一个生成器。
- 第一次调用生成器的 next 方法时,生成器才开始执行生成器函数。直到遇到 yield 时暂停执行(挂起),并且将 yield 的参数作为此次的返回值。
- 之后每次调用 next 方法,生成器将从上次暂停的位置恢复并继续执行,直到再次遇到yield 时暂停,同样将 yield 的参数返回。
- 当调用 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基础(十一) 类的拓展的更多相关文章
- Python基础(十一) 类继承
类继承: 继承的想法在于,充份利用已有类的功能,在其基础上来扩展来定义新的类. Parent Class(父类) 与 Child Class(子类): 被继承的类称为父类,继承的类称为子类,一个父类, ...
- Python3基础 A类作为B类的实例变量
Python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 Conda ...
- python3在使用类基础时,遇到错误TypeError: module.**init**() takes at most 2 arguments (3 given)
python3在使用类基础时,遇到错误TypeError: module.init() takes at most 2 arguments (3 given) 1.原因:直接导入的py文件,而没有导入 ...
- Python3基础(十二) 学习总结·附PDF
Python是一门强大的解释型.面向对象的高级程序设计语言,它优雅.简单.可移植.易扩展,可用于桌面应用.系统编程.数据库编程.网络编程.web开发.图像处理.人工智能.数学应用.文本处理等等. 在学 ...
- Python基础-类的探讨(class)
Python基础-类的探讨(class) 我们下面的探讨基于Python3,我实际测试使用的是Python3.2,Python3与Python2在类函数的类型上做了改变 1,类定义语法 Python ...
- Java基础(十一) Stream I/O and Files
Java基础(十一) Stream I/O and Files 1. 流的概念 程序的主要任务是操纵数据.在Java中,把一组有序的数据序列称为流. 依据操作的方向,能够把流分为输入流和输出流两种.程 ...
- Java基础十一--多态
Java基础十一--多态 一.多态定义 简单说:就是一个对象对应着不同类型. 多态在代码中的体现: 父类或者接口的引用指向其子类的对象. /* 对象的多态性. class 动物 {} class 猫 ...
- java基础-System类常用方法介绍
java基础-System类常用方法介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.System类概念 在API中system类介绍的比较简单,我们给出定义,system中 ...
- python002 Python3 基础语法
python002 Python3 基础语法 编码默认情况下,Python 3 源码文件以 UTF-8 编码,所有字符串都是 unicode 字符串. 当然你也可以为源码文件指定不同的编码: # -* ...
- Python3基础(八) 模块
在程序中定义函数可以实现代码重用.但当你的代码逐渐变得庞大时,你可能想要把它分割成几个文件,以便能够更简单地维护.同时,你希望在一个文件中写的代码能够被其他文件所重用,这时我们应该使用模块(modul ...
随机推荐
- MVC之参数验证(一)
ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其数据的准确性.总地来说,我们可以采用Syste ...
- [ USACO 2017 FEB ] Why Did the Cow Cross the Road III (Gold)
\(\\\) \(Description\) 给定长度为\(2N\)的序列,\(1\text ~N\)各出现过\(2\)次,\(i\)第一次出现位置记为\(a_i\),第二次记为\(b_i\),求满足 ...
- 全志A33平台编译linux(分色排版)V1.1
全志A33平台编译linux 大文实验室/大文哥 壹捌陆捌零陆捌捌陆捌贰 21504965 AT qq.com 完成时间:2017/12/13 10:41 版本:V1.1 (一)解压缩lichee备用 ...
- 关于百度地图导航AndroidSDK的初始化问题
使用百度地图有一段时间了,导航是一个一直困扰我的问题.今天刚发现百度地图的导航SDK并不是对Android6.0版本不兼容.而是对某一部分手机不兼容,准确的说是对X64或X86的cpu不兼容.下载百度 ...
- MySql学习笔记(二) —— 正则表达式的使用
前面介绍利用一些关键字搭配相应的SQL语句进行数据库查找过滤,但随着过滤条件的复杂性的增加,where 子句本身的复杂性也会增加.这时我们就可以利用正则表达式来进行匹配查找. 1.基本字符匹配 ' o ...
- 安卓app测试之Monkeyscript
MonkeyScript是一组可以被Monkey识别的命令集合 优点:MonkeyScript可以完成重复固定的操作 使用:adb shell monkey -f <scriptfile> ...
- 微服务网关从零搭建——(三)Ocelot网关 + identity4
增加验证服务 1.创建名为AuthService 的core 空项目 2.修改startup文件 using System; using System.Collections.Generic; usi ...
- 手机通过Charles用线上域名访问PC本地项目
最近调试微信公众号,由于微信授权需要线上正式环境的域名,笔者想把手机公众号网页重定向到PC本地localhost调试. 该方法通过Charles代理转发,适用所有需要域名重定向的场景,例如手机通过在线 ...
- mac下安装好jdk和jmeter后设置环境变量
1. 执行vim ~/.bash_profile,打开文件: 2. 按i,进入输入状态,并输入如下信息,其中为jdk安装路径: export JAVA_HOME=/Library/Java/JavaV ...
- LINUX-文件系统分析
badblocks -v /dev/hda1 检查磁盘hda1上的坏磁块 fsck /dev/hda1 修复/检查hda1磁盘上linux文件系统的完整性 fsck.ext2 /dev/hda1 修 ...