Pythonic枚举while循环

9

Python有一种优雅的方法在for循环中自动生成计数器变量:使用 enumerate函数。这样可以省去初始化和递增计数器变量的需要。计数器变量也很难看,因为它们通常在循环结束后就没有用了,但它们的作用域不是循环的作用域,所以它们占用命名空间而没有必要(虽然我不确定enumerate是否真正解决了这个问题)。

我的问题是,是否有类似的 Python 的解决方案可以用于 while 循环。由于 enumerate 返回一个迭代器,因此 enumerate 无法用于 while 循环。理想情况下,解决方案应该是"pythonic",并且不需要函数定义。

例如:

x=0
c=0
while x<10:
  x=int(raw_input())
  print x,c
  c+=1

在这种情况下,我们希望避免初始化和增加c。 澄清: 可以使用无限的for循环并手动终止来实现,但我正在寻找一种使代码更清晰的解决方案,并且我认为在这种情况下该解决方案不会使代码更清晰。

5
可以举个例子吗?为什么必须使用 while 循环? - BrenBarn
4
你是否在寻找类似itertools.count的东西? - Fredrik Pihl
@BrenBarn 我添加了一个例子。无论如何,while 经常很有用 - 这就是为什么他们把它包含在语言中的原因。 - Bitwise
@morningstar 我不认为你可以用 for 做同样的事情,而不会让它变得更丑陋和难以理解。 - Bitwise
1
如果你想让代码清晰易懂,最好明确地说明计数器。 - BrenBarn
显示剩余6条评论
4个回答

9
改进(在易读性方面,我想说)Ignacio的答案:

对Ignacio的答案进行改进,使其更易读:

x = 0
for c in itertools.takewhile(lambda c: x < 10, itertools.count()):
    x = int(raw_input())
    print x, c

优点:

  • 只有while循环条件在循环头中,而不是副作用raw_input。
  • 循环条件可以依赖于任何普通while循环可能的条件。不需要将引用的变量“导入”takewhile中,因为它们已经在lambda范围内可见。此外,如果需要,它还可以依赖于计数,但在这种情况下不需要。
  • 简化:不再出现enumerate。

6

再一次介绍 itertools...

import itertools

for c, x in enumerate(
    itertools.takewhile(lambda v: v < 10,
      (int(raw_input()) for z in itertools.count())
    )
  ):
  print c, x

1
有趣,但我也不会说这使代码更清晰! :-) - BrenBarn
实际上,它的功能并不完全相同。原始代码将打印出10或更大的值,但这个代码不会。 - morningstar
是的,但我认为这是一个很好的例子,说明让算法更清晰、更简单可以让程序员分析并实际改进初始规范。 - Crowman
谢谢,我不知道itertools.takewhile() - Bitwise

2

如果您想在 while 循环之前进行 初始化,可以使用带有计数器的 单例模式

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(
                                cls, *args, **kwargs)
            cls.count=0
        else:
            cls.count+=1                            
        return cls._instance

那么只会有一个 Singleton 实例,每个额外的实例只会增加一个:

>>> Singleton().count    # initial instance
0
>>> Singleton().count
1
>>> Singleton().count
2
>>> Singleton().count
3

然后你的while循环变成了:
while Singleton():
    x=int(raw_input('x: '))
    if x>10: break

print 'While loop executed',Singleton().count,'times' 

输入 1,2,3,11 后,它会打印出:

x: 1
x: 2
x: 3
x: 11
While loop executed 4 times

如果您不介意在while循环之前进行单行初始化,您可以直接子类化迭代器:

import collections
class WhileEnum(collections.Iterator):
    def __init__(self,stop=None):
        self.stop=stop
        self.count=0

    def next(self):    # '__next__' on Py 3, 'next' on Py 2
        if self.stop is not None:
            self.remaining=self.stop-self.count
            if self.count>=self.stop: return False
        self.count+=1    
        return True

    def __call__(self):
        return self.next()

那么你的 while 循环变成了:

enu=WhileEnum()
while enu():
    i=int(raw_input('x: '))
    if i>10: break

print enu.count

我认为第二种方法更好。您可以拥有多个枚举器,并且还可以设置循环次数的限制:

limited_enum=WhileEnum(5)

1
我认为你想要的方式可能无法完全实现。如果我理解正确,你想要一个 while 循环,每次循环都会增加计数器,但不会在循环作用域之外显示计数器。我认为实现这个的方法是将你的while循环重写为非终止的for循环,并手动检查结束条件。对于你的示例代码:
import itertools

x = 0
for c in itertools.count():
    x = int(raw_input())
    print x, c
    if x >= 10:
        break

问题在于,你使用计数器进行迭代。如果你不想暴露该计数器,它需要来自循环结构。如果不定义新函数,你只能使用标准循环和显式检查。
另一方面,你也可以为此定义一个生成器。你仍然会进行迭代,但至少可以将检查包装在循环结构中。

谢谢。我通常避免使用那种for循环,因为我认为它不太符合Python的风格。只保留while和计数器变量可能会更清晰。让我们看看是否有人提出了不同的解决方案。 - Bitwise

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