【问题标题】:How does a for loop evaluate its argumentfor 循环如何评估其参数
【发布时间】:2016-05-28 03:30:56
【问题描述】:

我的问题很简单。

for 循环是否每次都会评估它使用的参数?

如:

for i in range(300):

python 是否为这个循环的每次迭代创建一个包含 300 个项目的列表?

如果是,这是避免它的方法吗?

lst = range(300)
for i in lst:
    #loop body

类似这样的代码示例也是如此。

for i in reversed(lst):

for k in range(len(lst)):

是每次都应用反向过程,还是每次迭代都计算长度? (我对 python2 和 python3 都要求这个)

如果不是,Python 在迭代时如何评估可迭代对象的更改?

【问题讨论】:

  • 如果您对优化感兴趣,我会查看“将代码转换为漂亮的惯用 Python” - youtube.com/watch?v=OSGv2VnC0go - 很多很棒的 Python 代码。
  • 你已经得到了答案,但值得一提的是,在 Python 2 中 range() 只返回一个列表。在 Python 3 中,range(300) 创建了一个范围对象,它是一个具有内部状态的生成器。这意味着您可以在不耗尽内存的情况下执行range(1000000000000000000) 之类的操作。在 Python 2 中,您可以使用xrange 来获得相同的效果。

标签: python python-2.7 python-3.x for-loop


【解决方案1】:

创建什么对象取决于您循环返回的 Iterable 的 __iter__ 方法。

当迭代一个本身不是迭代器的迭代器时,Python 通常会创建一个迭代器。在 Python2 中,range 返回一个列表,它是一个 Iterable,并且有一个 __iter__ 方法返回一个 Iterator。

>>> from collections import Iterable, Iterator
>>> isinstance(range(300), Iterable)
True
>>> isinstance(range(300), Iterator)
False
>>> isinstance(iter(range(300)), Iterator)
True

for in sequence: do something 语法基本上是执行此操作的简写:

it = iter(some_iterable) # get Iterator from Iterable, if some_iterable is already an Iterator, __iter__ returns self by convention
while True:
    try:
        next_item = next(it)
        # do something with the item
    except StopIteration:
        break

这是一个演示,其中包含一些打印语句,以阐明使用 for 循环时发生的情况:

class CapitalIterable(object):
    'when iterated over, yields capitalized words of string initialized with'
    def __init__(self, stri):
        self.stri = stri

    def __iter__(self):
        print('__iter__ has been called')
        return CapitalIterator(self.stri)

        # instead of returning a custom CapitalIterator, we could also
        # return iter(self.stri.title().split())
        # because the built in list has an __iter__ method

class CapitalIterator(object):
    def __init__(self, stri):
        self.items = stri.title().split()
        self.index = 0

    def next(self): # python3: __next__
        print('__next__ has been called')
        try:
            item = self.items[self.index]
            self.index += 1
            return item
        except IndexError:
            raise StopIteration

    def __iter__(self):
        return self

c = CapitalIterable('The quick brown fox jumps over the lazy dog.')
for x in c:
    print(x)

输出:

__iter__ has been called
__next__ has been called
The
__next__ has been called
Quick
__next__ has been called
Brown
__next__ has been called
Fox
__next__ has been called
Jumps
__next__ has been called
Over
__next__ has been called
The
__next__ has been called
Lazy
__next__ has been called
Dog.
__next__ has been called

如您所见,__iter__ 只被调用一次,因此只创建了一个 Iterator 对象。

【讨论】:

    【解决方案2】:

    Range 在这种情况下会创建一个包含 300 个整数的数组。它不会创建 300 个整数的数组 300 次。它不是每一个有效的。如果您使用 xrange ,它将创建一个不会占用几乎一样多内存的可迭代对象。 https://docs.python.org/2/library/functions.html#xrange

    example.py

    ​​>
    for i in xrange(300): #low memory foot print, similar to a normal loop
        print(i)
    

    【讨论】:

      【解决方案3】:

      不用担心,迭代器只会被评估一次。它最终大致相当于这样的代码:

      it = iter(range(300))
      while True:
          try:
              i = next(it)
          except StopIteration:
              break
          ... body of loop ...
      

      请注意,它不是相当等效的,因为break 的工作方式会有所不同。请记住,您可以将 else 添加到 for 循环中,但这在上面的代码中不起作用。

      【讨论】:

      • 或者在 Python 2 或 Python 3 中,i = next(it)
      • 另外,虽然在整个循环中使用的是同一个 iterator,但它正在迭代的东西可能会改变,这就是为什么不建议这样做的原因,例如,修改迭代其元素时的列表。
      • 谢谢,我刚刚找到了一种方法来检查我问的内容。我可以只定义一个返回可迭代(不是生成器)的函数,在其中添加一个打印语句,而不是检查它会打印多少次。
      猜你喜欢
      • 1970-01-01
      • 2011-12-17
      • 1970-01-01
      • 2019-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      相关资源
      最近更新 更多