生成器和迭代器
生成器让Python能够以内存高效的方式处理大量数据。它们实现了惰性求值,只在需要时计算值,体现了Python对性能和内存使用的关注。
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
迭代器
迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器:
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
next(it) # 输出迭代器的下一个元素
next(it) # 再输出下一个元素
enumerate
列表好处是不需要对下标进行迭代,直接输出列表的值:
x = [2, 4, 6]
for i in x:
print(i)
但是有些情况下,我们既希望获得下标, 也希望获得对应的值,那么:
可以将迭代器传给 enumerate 函数, 这样每次迭代都会返回一组 (index, value) 组成的元组:
x = [2, 4, 6]
for i, n in enumerate(x):
print(i, 'is', n)
自定义迭代器
一个迭代器都有 __iter__()
与 __next__()
__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__()
方法并通过 StopIteration 异常标识迭代的完成。
__next__()
方法(Python 2 里是 next())会返回下一个迭代器对象。
自定义一个 list 的取反迭代器:
class ReverseListIterator(object):
def __init__(self, lst):
self.list = lst
self.index = len(lst)
def __iter__(self):
return self
def __next__(self):
self.index -= 1
if self.index >= 0:
return self.list[self.index]
else:
raise StopIteration
x = range(10)
for i in ReverseListIterator(x):
print(i)
只要我们定义了这三个方法(__init__, __iter__, __next__
),我们可以返回任意迭代值:
实现 Collatz 猜想
这里我们实现 Collatz 猜想:
- 奇数 n:返回 3n + 1
- 偶数 n:返回 n / 2
- 直到 n 为 1 为止:
class Collatz(object):
def __init__(self, start):
self.value = start
def __iter__(self):
return self
def __next__(self):
if self.value == 1:
raise StopIteration
elif self.value % 2 == 0:
self.value = self.value / 2
else:
self.value = 3 * self.value + 1
return self.value
for x in Collatz(5):
print(x)
不过迭代器对象存在状态,有问题:
i = Collatz(5)
# zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的迭代器。
for x, y in zip(i, i):
print(x, y)
# 下方代码等价于上方代码
i = Collatz(5)
# *zipped 可理解为解压,返回二维矩阵式
zipped = zip(i, i)
#<zip object at 0x00000200CFC1F400> #返回的是一个对象
x, y = zip(*zipped)
print(x, y)
解决方法是将迭代器和可迭代对象分开处理。
迭代器和可迭代对象分开处理
这里提供了一个二分树的中序遍历实现:
class BinaryTree(object):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def __iter__(self):
return InorderIterator(self)
class InorderIterator(object):
def __init__(self, node):
self.node = node
self.stack = []
def __next__(self):
if len(self.stack) > 0 or self.node is not None:
while self.node is not None:
self.stack.append(self.node)
self.node = self.node.left
node = self.stack.pop()
self.node = node.right
return node.value
else:
raise StopIteration()
测试:
tree = BinaryTree(
left=BinaryTree(
left=BinaryTree(1),
value=2,
right=BinaryTree(
left=BinaryTree(3),
value=4,
right=BinaryTree(5)
),
),
value=6,
right=BinaryTree(
value=7,
right=BinaryTree(8)
)
)
for value in tree:
print(value)
不会出现之前的问题:
for x, y in zip(tree, tree):
print(x, y)
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
- 迭代器则通过 next 的 return 将值返回;
- 与迭代器不同的是,生成器会自动记录当前的状态, 而迭代器则需要进行额外的操作来记录当前的状态。
之前的 collatz 猜想,简单循环的实现如下:
collatz:
- 奇数 n:返回 3n + 1
- 偶数 n:返回 n / 2
- 直到 n 为 1 为止:
def collatz(n):
sequence = []
while n != 1:
if n % 2 == 0:
n /= 2
else:
n = 3 * n + 1
sequence.append(n)
return sequence
for x in collatz(5):
print(x)
生成器的版本如下:
def collatz(n):
while n != 1:
if n % 2 == 0:
n /= 2
else:
n = 3 * n + 1
yield n
for x in collatz(5):
print(x)
迭代器的版本如下:
class Collatz(object):
def __init__(self, start):
self.value = start
def __iter__(self):
return self
def next(self):
if self.value == 1:
raise StopIteration
elif self.value % 2 == 0:
self.value = self.value / 2
else:
self.value = 3 * self.value + 1
return self.value
for x in collatz(5):
print(x)
事实上,生成器也是一种迭代器:
x = collatz(5)
x
它支持 next 方法,返回下一个 yield 的值:
next(x)
next(x)
__iter__
方法返回的是它本身:
x.__iter__()
return 和 yield 有什么区别?
yield 是暂停的意思(它有程序中起着类似红绿灯中等红灯的作用);yield 是创建迭代器,可以用 for 来遍历,有点事件触发的意思
return 在方法中直接返回值;是函数返回值,当执行到 return,后续的逻辑代码不在执行
相同点: 都是定义函数过程中返回值
不同点:yield 是暂停函数,return 是结束函数; 即 yield 返回值后继续执行函数体内代码,return 返回值后不再执行函数体内代码。
yield 返回的是一个迭代器(yield 本身是生成器-生成器是用来生成迭代器的);return 返回的是正常可迭代对象(list,set,dict 等具有实际内存地址的存储对象)
如果要返回的数据是通过 for 等循环生成的迭代器类型数据(如列表、元组),return 只能在循环外部一次性地返回,yeild 则可以在循环内部逐个元素返回。
yiled from 还可以使一个生成器可以委派子生成器,建立双向通道
def g1(x):
yield range(x, 0, -1)
yield range(x)
print(list(g1(5)))
#[range(5, 0, -1), range(0, 5)]
def g2(x):
yield from range(x, 0, -1)
yield from range(x)
print(list(g2(5)))
#[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
迭代器和生成器有什么区别?
在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象:迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法:iter() 和 next()
new和 init的区别?
执行顺序的不同:只有在new返回一个 cls 的实例时后面的init才能被调用
功能上的不同:当创建一个新实例时调用new,初始化一个实例时用init
返回值的不同:new方法会返回一个创建的实例,而init什么都不返回