类的定义
Python类以保留字 class
开始, 后面跟类名。
class PapayaWhip: ①
pass ②
① | 类名是 PapayaWhip , 没有从其他类继承。 类名通常是大写字母分隔, 如EachWordLikeThis , 但这只是个习惯,并非必须。 |
② | 你可能猜到,类内部的内容都需缩进,就像函数中的代码一样, if 语句, for 循环, 或其他代码块。第一行非缩进代码表示到了类外。 |
类的初始化方法__init__()
class Fib:
'''生成菲波拉稀数列的迭代器''' ①
def __init__(self, max): ②
① | 类同样可以 (而且应该) 具有docstring , 与模块(module)和方法一样。 |
② | 类实例创建后,__init__() 方法被立即调用。很容易将其——但技术上来说不正确——称为该类的“构造函数” 。 因为 __init__() 方法调用时,对象已经创建了,你已经有了一个合法类对象的引用。 |
__init__()
方法中,
self
指向新创建的对象; 在其他类对象中, 它指向方法所属的实例。
尽管需在定义方法时显式指定self ,调用方法时并 不 必须明确指定。 Python 会自动添加。
实例化类
>>> import fibonacci2
>>> fib = fibonacci2.Fib(100) ①
>>> fib ②
<fibonacci2.Fib object at 0x00DB8810>
>>> fib.__class__ ③
<class 'fibonacci2.Fib'>
>>> fib.__doc__ ④
'生成菲波拉稀数列的迭代器'
① | 你正创建一个 Fib 类的实例(在fibonacci2 模块中定义) 将新创建的实例赋给变量fib。 你传入一个参数 100 , 这是Fib 的__init__() 方法作为max参数传入的结束值。 |
② | fib 是 Fib 的实例。 |
③ | 每个类实例具有一个内建属性, __class__ , 它是该对象的类。 |
④ | 你可以用__doc__属性访问对象的 docstring ,就像函数或模块中的一样。 类的所有实例共享一份 docstring 。 |
new
操作符。
实例变量
class Fib:
def __init__(self, max):
self.max = max ①
self.max 是实例内 “全局” 的。 这意味着可以在其他方法中访问它。实例变量直接在__init__方法内定义即可
实例变量特定于某个类的实例, 每个实例会记住自己的值。
斐波那契序列迭代器
class Fib: ①
def __init__(self, max): ②
self.max = max
def __iter__(self): ③
self.a = 0
self.b = 1
return self
def __next__(self): ④
fib = self.a
if fib > self.max:
raise StopIteration ⑤
self.a, self.b = self.b, self.a + self.b
return fib ⑥
① | 从无到有创建一个迭代器, fib 应是一个类,而不是一个函数。 |
② | “调用” Fib(max) 会创建该类一个真实的实例,并以max做为参数调用__init__() 方法。__init__() 方法以实例变量保存最大值,以便随后的其他方法可以引用。 |
③ | 当有人调用iter(fib) 的时候,__iter__() 就会被调用。(正如你等下会看到的, for 循环会自动调用它, 你也可以自己手动调用。) 在完成迭代器初始化后,(在本例中, 重置我们两个计数器 self.a 和 self.b ), __iter__() 方法能返回任何实现了 __next__() 方法的对象。 在本例(甚至大多数例子)中, __iter__() 仅简单返回 self, 因为该类实现了自己的 __next__() 方法。 |
④ | 当有人在迭代器的实例中调用next() 方法时,__next__() 会自动调用。 随后会有更多理解。 |
⑤ | 当 __next__() 方法抛出 StopIteration 异常, 这是给调用者表示迭代用完了的信号。 和大多数异常不同, 这不是错误;它是正常情况,仅表示迭代器没有值可产生了。 如果调用者是 for 循环, 它会注意到该 StopIteration 异常并优雅的退出。 (换句话说,它会吞掉该异常。) 这点神奇之处就是使用 for 的关键。 |
⑥ | 为了分离出下一个值, 迭代器的 __next__() 方法简单 return 该值。 不要使用 yield ; 该语法上的小甜头仅用于你使用生成器的时候。 这里你从无到有创建迭代器,使用 return 代替。 |
调用迭代器
>>> from fibonacci2 import Fib
>>> for n in Fib(1000):
... print(n, end=' ')
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
- 如你所见,
for
循环调用Fib(1000)
。 这返回Fib
类的实例。 叫它 fib_inst。 - 背地里,且十分聪明的,
for
循环调用iter(fib_inst)
, 它返回迭代器。 叫它 fib_iter。 本例中, fib_iter == fib_inst, 因为__iter__()
方法返回 self,但for
循环不知道(也不关心)那些。 - 为“循环通过”迭代器,
for
循环调用next(fib_iter)
, 它又调用fib_iter
对象的__next__()
方法,产生下一个菲波拉稀计算并返回值。for
拿到该值并赋给 n, 然后执行n值的for
循环体。 -
for
循环如何知道什么时候结束?很高兴你问到。 当next(fib_iter)
抛出StopIteration
异常时,for
循环将吞下该异常并优雅退出。 (其他异常将传过并如常抛出。) 在哪里你见过StopIteration
异常? 当然在__next__()
方法。
实现迭代功能需要实现__iter__()和__next__()方法>>> import plural6 >>> r1 = plural6.LazyRules() >>> r2 = plural6.LazyRules() >>> r1.rules_filename ① 'plural6-rules.txt' >>> r2.rules_filename 'plural6-rules.txt' >>> r2.rules_filename = 'r2-override.txt' ② >>> r2.rules_filename 'r2-override.txt' >>> r1.rules_filename 'plural6-rules.txt' >>> r2.__class__.rules_filename ③ 'plural6-rules.txt' >>> r2.__class__.rules_filename = 'papayawhip.txt' ④ >>> r1.rules_filename 'papayawhip.txt' >>> r2.rules_filename ⑤ 'r2-overridetxt'
① 类的每个实例继承了 rules_filename 属性及它在类中定义的值。 ② 修改一个实例属性的值不影响其他实例…… ③ ……也不会修改类的属性。可以使用特殊的 __class__
属性来访问类属性(于此相对的是单独实例的属性)。④ 如果修改类属性, 所有仍然继承该实例的值的实例 (如这里的r1 ) 会受影响。 ⑤ 已经覆盖(overridden)了该属性(如这里的 r2 )的所有实例 将不受影响。