生成器是一类特殊 迭代器。 一个产生值的函数 yield 是一种产生一个迭代器却不需要构建迭代器的精密小巧的方法。
Pass语句
class PapayaWhip: ①
pass ②
应该在定义中有东西,这就是 pass 语句。 这是Python 保留字,意思是“继续,这里看不到任何东西”。 这是一个什么都不做的语句,是一个很好的占位符,如果你的函数和类什么都不想做(删空函数或类)。
☞Python中的pass 就像Java 或 C中的空大括号对 ({})
创建类
class PapayaWhip: ①
pass ②
init() 方法
尽管不是必须, Python 类 可以 具有类似构造函数的东西: init() 方法。
类实例创建后,init() 方法被立即调用。很容易将其——但技术上来说不正确——称为该类的“构造函数” 。 很容易,因为它看起来很像 C++ 的构造函数(按约定,init() 是类中第一个被定义的方法),行为一致(是类的新实例中第一片被执行的代码), 看起来完全一样。 错了, 因为init() 方法调用时,对象已经创建了,你已经有了一个合法类对象的引用。
self
每个方法的第一个参数,包括 init() 方法,永远指向当前的类对象。 习惯上,该参数叫 self。 该参数和C++或Java中 this 角色一样, 但 self 不是 Python的保留字, 仅仅是个命名习惯。
在 init() 方法中, self 指向新创建的对象; 在其他类对象中, 它指向方法所属的实例。尽管需在定义方法时显式指定self ,调用方法时并 不 必须明确指定。 Python 会自动添加。
实例化类
Python 中实例化类很直接。 实例化类时就像调用函数一样简单,将 init() 方法需要的参数传入。 返回值就是新创建的对象。
>>> import fibonacci2
>>> fib = fibonacci2.Fib(100) ①
>>> fib ②
<fibonacci2.Fib object at 0x00DB8810>
>>> fib.__class__ ③
<class 'fibonacci2.Fib'>
>>> fib.__doc__ ④
'生成菲波拉稀数列的迭代器'
- fib 是 Fib 的实例。
- 每个类实例具有一个内建属性, class, 它是该对象的类。 Python里面, 这类元数据由属性提供,但思想一致。你可访问对象的 docstring ,就像函数或模块中的一样。 类的所有实例共享一份 docstring。
- Python里面, 和调用函数一样简单的调用一个类来创建该类的新实例。 与C++ 或 Java不一样,没有显式的 new 操作符。
实例变量
继续下一行:
class Fib:
def __init__(self, max):
self.max = max ①
self.max是什么? 它就是实例变量。 与作为参数传入 init() 方法的 max完全是两回事。 self.max 是实例内 “全局” 的。 这意味着可以在其他方法中访问它。
class Fib:
def __init__(self, max):
self.max = max ①
.
.
.
def __next__(self):
fib = self.a
if fib > self.max: ②
- self.max 在 init() 方法中定义在 next() 方法中引用。
- 实例变量特定于某个类的实例。
迭代器
如何创建一个迭代器了。 迭代器就是一个定义了 iter() 方法的类。
这些类的所有三种方法, init, iter, 和 next, 起始和结束均为一对下划线(_) 字符。 当你使用类或实例的这些语法时,Python会自动调用他们。
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 ⑥
- 当有人调用iter(fib)的时候,iter()就会被调用。(正如你等下会看到的, for 循环会自动调用它, 你也可以自己手动调用。) 在完成迭代器初始化后,(在本例中, 重置我们两个计数器 self.a 和 self.b), iter() 方法能返回任何实现了 next() 方法的对象。 在本例(甚至大多数例子)中, iter() 仅简单返回 self, 因为该类实现了自己的 next() 方法。
- 当有人在迭代器的实例中调用next()方法时,next() 会自动调用。 随后会有更多理解。
- 当 next() 方法抛出 StopIteration 异常, 这是给调用者表示迭代用完了的信号。 和大多数异常不同, 这不是错误;它是正常情况,仅表示迭代器没有值可产生了。 如果调用者是 for 循环, 它会注意到该 StopIteration 异常并优雅的退出。 (换句话说,它会吞掉该异常。) 这点神奇之处就是使用 for 的关键。
- 为了分离出下一个值, 迭代器的 next() 方法简单 return该值。 不要使用 yield ; 该语法上的小甜头仅用于你使用生成器的时候。 这里你从无到有创建迭代器,使用 return 代替。
复数规则迭代器,实例
class LazyRules:
rules_filename = 'plural6-rules.txt'
def __init__(self):
self.pattern_file = open(self.rules_filename, encoding='utf-8')
self.cache = []
#打开模式文件之后,初始化缓存。 随后读取模式文件行的时候会用到它(在 __next__() 方法中) 。
def __iter__(self):
self.cache_index = 0
return self
#无论何时有人——如 for 循环——调用 iter(rules)的时候,__iter__() 方法都会被调用。
#每个__iter__() 方法都需要做的就是必须返回一个迭代器。 在本例中,返回 self,意味着该类定义了__next__() 方法,由它来关注整个迭代过程中的返回值。
def __next__(self):
self.cache_index += 1
if len(self.cache) >= self.cache_index:
return self.cache[self.cache_index - 1]
if self.pattern_file.closed:
raise StopIteration
line = self.pattern_file.readline()
if not line:
self.pattern_file.close()
raise StopIteration
pattern, search, replace = line.split(None, 3)
funcs = build_match_and_apply_functions(
pattern, search, replace)
self.cache.append(funcs)
return funcs
rules = LazyRules()
#让我们近观 rules_filename。它没在 __iter__() 方法中定义。事实上,它没在任何方法中定义。它定义于类级别。它是 类变量, 尽管访问时和实例变量一样 (self.rules_filename), LazyRules 类的所有实例共享该变量。
- 当模块引入时,创建了LazyRules 类的一个单一实例, 叫 rules, 它打开模式文件但并没有读取。
- 当要求第一个匹配和应用功能时,检查缓存并发现缓存为空。 于是,从模式文件读取一行, 从模式中创建匹配和应用功能,并缓存之。
- 假如,因为参数的缘故,正好是第一行匹配了。如果那样,不会有更多的匹配和应用会创建,也不会有更多的行会从模式文件中读取。
- 更进一步, 因为参数的缘故,假设调用者再次调用 plural() 函数来让一个不同的单词变复数。 plural() 函数中的for 循环会调用iter(rules),这会重置缓存索引但不会重置打开的文件对象。
- 第一次遍历, for循环会从rules中索要一个值,该值会调用其next()方法。然而这一次, 缓存已经被装入了一个匹配和应用功能对, 与模式文件中第一行模式一致。 由于对前一个单词做复数变换时已经被创建和缓存,它们被从缓存中返回。 缓存索引递增,打开的文件无需访问。
- 假如,因为参数的缘故,这一轮第一个规则 不 匹配。 所以 for 循环再次运转并从 rules请求一个值。 这会再次调用 next() 方法。 这一次, 缓存被用完了——它仅有一个条目,而我们被请求第二个——于是 next() 方法继续。 从打开的文件中读取下一行,从模式中创建匹配和应用功能,并缓存之。
- 该“读取创建并缓存”过程一直持续直到我们从模式文件中读取的规则与我们想变复数的单词不匹配。 如果我们确实在文件结束前找到了一个匹配规则,我们仅需使用它并停止,文件还一直打开。文件指针会留在我们停止读取,等待下一个 readline() 命令的地方。现在,缓存已经有更多条目了,并且再次从头开始来将一个新单词变复数,在读取模式文件下一行之前,缓存中的每一个条目都将被尝试。