Python中的循环列表迭代器

155

我需要遍历一个循环列表,可能需要多次遍历,每次都从上一次访问的项目开始。

这种情况用于连接池。客户端请求连接,迭代器检查指向连接是否可用并返回它,否则循环直到找到可用连接为止。

如何在Python中优雅地实现这一点?


如果你需要立即创建一个结果列表,以达到特定的长度,而不是按需进行迭代,请参见Repeat list to max number of elements 获取一般技术,以及How to replicate array to specific length array获取Numpy特定技术。

9个回答

256
使用itertools.cycle,这就是它的确切目的:
from itertools import cycle

lst = ['a', 'b', 'c']

pool = cycle(lst)

for item in pool:
    print(item)

输出:

a b c a b c ...

(无限循环,显然)
为了手动推进迭代器并逐个提取值,只需调用next(pool)
>>> next(pool)
'a'
>>> next(pool)
'b'

4
你正在循环打印物品。我想离开循环,稍后再回来(我想从离开的地方开始)。 - user443854
9
请使用pool.next()获取循环中的下一项。该函数将返回下一个项目,直到循环结束。请注意,此函数仅适用于Python 2.x版本,在Python 3.x版本中已被弃用。 - Jacob Krall
@JacobKrall说的没错;-) 你可以通过调用iterator.next()手动向前移动任何迭代器。这基本上就是for循环为您完成的操作。 - Lukas Graf
5
@user443854,这个回答比我的好多了。没必要再去重新实现库函数! - Jacob Krall
9
pool.next() 在我的代码中没有起作用,只有 next(pool) 能正常使用。可能是因为 Python 3 的原因? - fjsj
10
@fjsj 相关,对于 Python 3,你需要使用 next(iterator)(顺便说一下,在 Python 2.x 上也可以正常工作,因此应该使用的是规范形式)。有关更详细的解释,请参见 Is generator.next() visible in python 3.0?。我已相应更新了我的回答。 - Lukas Graf

77

正确的答案是使用itertools.cycle。但是,假设这个库函数不存在。您将如何实现它?

使用生成器

def circular():
    while True:
        for connection in ['a', 'b', 'c']:
            yield connection

然后,你可以使用 for 语句来无限循环迭代,或者你可以调用 next() 来获取生成器迭代器的单个下一个值:

connections = circular()
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
#....

1
不错!当列表耗尽时,它如何知道重新开始? - user443854
2
@user443854,“while True” 的意思是无限循环。 - Jacob Krall
5
没错,“itertools.cycle”是一个更好的答案。如果没有“itertools”,这就展示了你如何编写相同的功能 :) - Jacob Krall
简单的生成器是否也像itertools.cycle一样保存每个元素的副本?或者简单的生成器会设计得更节省内存吗?根据cycle文档注意,这个工具包中的成员可能需要大量的辅助存储(取决于可迭代对象的长度)。 - dthor
2
@dthor 这个生成器创建了一个包含三个元素的列表,并对其进行迭代,然后永久销毁该列表并创建一个新的列表。cycle 的文档说明输入可迭代对象在生成器开始之前被转换为 list,因为 iterable 只能“一次遍历值集合”。 - Jacob Krall
如果你在生成器之外定义了列表,那么在创建生成器时就不必重新创建整个列表,因此可以节省内存,特别是当循环长时间或者产生大量内存时,这对于itertools.cycle非常方便! - Maxim

16
你可以这样做:
conn = ['a', 'b', 'c', 'd', 'e', 'f']
conn_len = len(conn)
index = 0
while True:
    print(conn[index])
    index = (index + 1) % conn_len

这会一直打印 a b c d e f a b c...

6
你可以使用 append(pop()) 循环来完成这个任务:
l = ['a','b','c','d']
while True:
    print l[0]
    l.append(l.pop(0))

或者使用for i in range()循环:

l = ['a','b','c','d']
ll = len(l)
while True:
    for i in range(ll):
       print l[i]

或者简单地说:
l = ['a','b','c','d']

while True:
    for i in l:
       print i

以下所有内容均被打印:

>>>
a
b
c
d
a
b
c
d
...etc.

在这三种方法中,我更倾向于将append(pop())作为一个函数来使用。

servers = ['a','b','c','d']

def rotate_servers(servers):
    servers.append(servers.pop(0))
    return servers

while True:
    servers = rotate_servers(servers)
    print servers[0]

1
点赞这篇文章是因为它帮助我解决了一个完全不同的问题,我只想迭代一次列表,每次都将起始元素向前推进一步。我的用例是在扑克游戏中迭代玩家,每轮向前推进一个庄家筹码。 - Johan
1
从Python列表的前面删除一个项目很慢,因为列表的所有元素都必须被移动。官方文档警告不要这样做(请参见链接)。改用deque - Thomas Auzinger

4

如果你想要循环n次,可以使用ncycles itertools函数

from itertools import chain, repeat


def ncycles(iterable, n):
    "Returns the sequence elements n times"
    return chain.from_iterable(repeat(tuple(iterable), n))


list(ncycles(["a", "b", "c"], 3))
# ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']

3
你需要一个自定义迭代器 - 我将改编自这个答案的迭代器。
from itertools import cycle

class ConnectionPool():
    def __init__(self, ...):
        # whatever is appropriate here to initilize
        # your data
        self.pool = cycle([blah, blah, etc])
    def __iter__(self):
        return self
    def __next__(self):
        for connection in self.pool:
            if connection.is_available:  # or however you spell it
                return connection

3

为了避免无限循环,我使用了数组的长度来迭代,只有当列表大小加倍时才进行迭代。您可以实现自己的前置条件。思路是避免无限循环。

#Implement Circular Linked List
from itertools import cycle
list=[1,2,3,4,5]
lstlength=len(list)*2
print(lstlength)
pool=cycle(list)
i=0
#To avoid infinite loop break when you have iterated twice size of the list
for items in pool:
    print(items)
    if i >lstlength:
        break
    i += 1

0
class A(object):
    def __init__(self, l):
        self.strt = 0
        self.end = len(l)
        self.d = l

    def __iter__(self):
        return self

    def __next__(self):
        val = None
        if self.strt>=self.end:
            self.strt=0
        val = self.d[self.strt]
        self.strt += 1
        return val

a= A([8,9,7,66])
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))

0

对于那些可能感兴趣的人。 从给定的索引开始向前或向后循环:

def loop_fwd(arr, index):
  while True:
    arr_index = index % len(arr)
    yield arr_index, arr[arr_index]
    index += 1


def loop_bcw(arr, index):
  while True:
    arr_index = index % len(arr)
    yield arr_index, arr[arr_index]
    index -= 1


forward_it = loop_fwd([1,2,3,4,5], 3)
backward_it = loop_bcw([1,2,3,4,5], 3)

print('forward:')
for i in range(10):
  print(next(forward_it))


print('backward:')
for i in range(10):
  print(next(backward_it))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接