python 魔术方法3 可迭代对象、迭代器、生成器
可迭代对象
根据我们的协议,在Python
中如果一个对象有 __iter__( )
方法或 __getitem__( )
方法,则称这个对象是可迭代的(Iterable);
-
__iter__( )
: 让对象可以用for ... in
循环遍历,在使用for ... in
循环的时候触发。 -
__getitem__( )
: 让对象可以通过“实例名[index]
”的方式访问实例中的元素。
两个条件只要满足一条,就可以说对象是可迭代的。显然列表List
、元组Tuple
、字典Dictionary
、字符串String
等数据类型都是可迭代的。当然因为Python的“鸭子类型”,我们自定义的类中只要实现了__iter__( )
方法或__getitem__( )
方法,也是可迭代对象。
如果你不知道上面在说啥先看看这篇文章。
如果看完了还是不知道在说啥,,,那就看[这篇文章]。(https://www.jianshu.com/p/1c92e2dd6440)
我们可以实现对句子里面的单词逐个处理的功能:(比如我们在这里加的每个单词大写的功能)
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __getitem__(self, item):
return self.words[item]
def __len__(self):
return len(self.words)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
s = Sentence('"The time has come," the Walrus said')
print(s)
for word in s:
print(word.title())
print(list(s))
#以下是输出结果
Sentence('"The time ha...e Walrus said')
The
Time
Has
Come
The
Walrus
Said
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
这里面有一个控制输出长度的很好的实现,reprlib.repr
.
解释器需要迭代对象x
时,会自动调用iter(x)
。内置的iter
函数有以下作用。
- 检查对象是否实现了
__iter__
方法,如果实现了就调用它,获取一个迭代器。 - 如果没有实现
__iter__
方法,但是实现了__getitem__
方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。 - 如果尝试失败,Python抛出
TypeError
异常。
迭代器
到目前为止,你可能已经注意到大多数容器对象都可以使用 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, end='')
这种访问风格清晰、简洁又方便。 迭代器本质上是一个产生值的工厂,每次向迭代器请求下一个值,迭代器都会进行计算出相应的值并返回。迭代器的使用非常普遍并使得 Python 成为一个统一的整体。
在幕后,for
语句会调用容器对象中的 iter()
。 该函数返回一个定义了 __next__()
方法的迭代器对象,该方法将逐一访问容器中的元素。 当元素用尽时,__next__()
将引发 StopIteration
异常来通知终止 for
循环。 你可以使用 next()
内置函数来调用 __next__()
方法。
迭代器准确点来说就是,实现了迭代器协议的对象。
迭代器协议需要实现:
-
__next__
:返回容器的下一个元素。一般是自动调用,也可以使用next()
来调用。 -
__iter__
:返回迭代器本身。iter()
函数触发。
迭代器可以利用内置的 iter
函数和一个序列来创建。下面这个例子说明了可迭代对象的运作机制:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
当遍历完序列时,会引发一个StopIteration
异常。这样迭代器就可以与循环兼容,因为可以捕获这个异常并停止循环。
看过迭代器协议的幕后机制,为我们自己的类添加迭代器行为就很容易了。 定义一个 __iter__()
方法来返回一个带有 __next__()
方法的对象。 如果类已定义了 __next__()
,则 __iter__()
可以简单地返回 self
:
class CountDown:
def __init__(self,step):
self.step = step
def __next__(self):
if self.step <= 0:
raise StopIteration
self.step -= 1
return self.step
def __iter__(self):
return self
for ele in CountDown(4):
print(ele)
3
2
1
0
迭代器实现遍历:
class SentenceIterator:
def __init__(self,words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
s1 = SentenceIterator('abcd')
print(next(s1))
print(next(s1))
print(next(s1))
print(next(s1))
生成器
Generator
是一个用于创建迭代器的简单而强大的工具。 它们的写法类似标准的函数,但当它们要返回数据时会使用 yield
语句,生成器可以暂停函数并返回一个中间结果,该函数会保存执行上下文。每次对生成器调用 next()
时,它会从上次离开位置恢复执行(它会记住上次执行语句时的所有数据值)。
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
可以用生成器来完成的操作同样可以用前一节所描述的基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建 __iter__()
和 __next__()
方法。
另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 这使得该函数相比使用 self.index
和 self.data
这种实例变量的方式更易编写且更为清晰。
除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发 StopIteration
。 这些特性结合在一起,使得创建迭代器能与编写常规函数一样容易。
在一个对象上实现迭代最简单的方式是使用一个生成器函数。比如我们重写上面的句子的例子:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for word in self.words:
#这里不需要return语句
yield word
s = Sentence('"The time has come," the Walrus said')
for word in s:
print(word)
生成器表达式
某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,将外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> from math import pi, sin
>>> sine_table = {x: sin(x*pi/180) for x in range(0, 91)}
>>> unique_words = set(word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']