在类的初印象中,我们已经简单的介绍了类,包括类的定义、类对象和实例对象。本文将进一步学习类的继承、迭代器、发生器等等。
单继承
派生类的定义如下:
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。
到目前为止,你可能注意到,大多数的容器对象都可以使用 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)是用来创建迭代器的工具,它的形式跟函数一样,唯一的不同是生成器使用 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 返回时,生成器函数的“状态”会被冻结,所有的数据值和执行位置会被记住,一旦 next() 再次被调用,生成器函数会从它上次离开的地方继续执行。
有些时候,类似于 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