草庐IT

Python for 循环和迭代器行为

coder 2023-05-22 原文

我想多了解一点 iterators ,所以如果我错了,请纠正我。

迭代器是一个对象,它具有指向下一个对象的指针,并作为缓冲区或流(即链表)读取。它们特别有效,因为它们所做的只是通过引用而不是使用索引告诉您下一步是什么。

但是我仍然不明白为什么会发生以下行为:

In [1]: iter = (i for i in range(5))

In [2]: for _ in iter:
   ....:     print _
   ....:     
0
1
2
3
4

In [3]: for _ in iter:
   ....:     print _
   ....:     

In [4]: 

在通过迭代器 ( In [2] ) 的第一次循环之后,就好像它被消耗并留空了,所以第二次循环 ( In [3] ) 什么也不打印。

但是,我从未为 iter 分配新值多变的。
for 背后到底发生了什么环形?

最佳答案

您的怀疑是正确的:迭代器已被消耗。

实际上,您的迭代器是 generator ,这是一个只能迭代一次的对象。

type((i for i in range(5))) # says it's type generator 

def another_generator():
    yield 1 # the yield expression makes it a generator, not a function

type(another_generator()) # also a generator

它们高效的原因与告诉您“通过引用”下一步是什么无关。它们是高效的,因为它们只根据请求生成下一个项目;所有项目都不是一次生成的。事实上,你可以有一个无限生成器:
def my_gen():
    while True:
        yield 1 # again: yield means it is a generator, not a function

for _ in my_gen(): print(_) # hit ctl+c to stop this infinite loop!

一些其他更正有助于提高您的理解:
  • 生成器不是指针,它的行为不像您在其他语言中可能熟悉的指针。
  • 与其他语言的区别之一:如上所述,生成器的每个结果都是即时生成的。直到被请求时才会产生下一个结果。
  • 关键词组合for in接受一个可迭代对象作为它的第二个参数。
  • 可迭代对象可以是生成器,如您的示例情况,但它也可以是任何其他可迭代对象,例如 list , 或 dict ,或 str对象(字符串),或提供所需功能的用户定义类型。
  • iter function应用于对象以获取迭代器(顺便说一句:不要使用 iter 作为 Python 中的变量名,就像您所做的那样 - 它是关键字之一)。实际上,更准确地说,对象的 __iter__ method被调用(在大多数情况下,iter 函数所做的一切都是如此;__iter__ 是 Python 所谓的“魔术方法”之一)。
  • 如果拨打__iter__成功,功能 next() 在循环中一遍又一遍地应用于可迭代对象,并将第一个变量提供给 for in分配给 next() 的结果功能。 (记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更准确地说:它调用迭代器对象的 __next__ 方法,这是另一种“神奇的方法”。
  • fornext() 时循环结束引发 StopIteration 异常(通常在调用 next() 时可迭代对象没有其他要生成的对象时发生)。

  • 您可以“手动”实现 for以这种方式在python中循环(可能不完美,但足够接近):
    try:
        temp = iterable.__iter__()
    except AttributeError():
        raise TypeError("'{}' object is not iterable".format(type(iterable).__name__))
    else:
        while True:
            try:
                _ = temp.__next__()
            except StopIteration:
                break
            except AttributeError:
                raise TypeError("iter() returned non-iterator of type '{}'".format(type(temp).__name__))
            # this is the "body" of the for loop
            continue
    

    上面的代码和你的示例代码几乎没有区别。

    实际上,for 中更有趣的部分循环不是 for ,但 in .使用 in本身产生的效果与 for 不同in ,但了解什么是非常有用的 in与它的参数一样,因为 for in实现非常相似的行为。
  • 单独使用时,in关键字首先调用对象的 __contains__ method ,这是另一种“魔术方法”(注意,使用 for in 时会跳过此步骤)。使用 in单独在容器上,您可以执行以下操作:
    1 in [1, 2, 3] # True
    'He' in 'Hello' # True
    3 in range(10) # True
    'eH' in 'Hello'[::-1] # True
    
  • 如果可迭代对象不是容器(即它没有 __contains__ 方法),则 in next 尝试调用对象的 __iter__方法。如前所述:__iter__方法返回 Python 中已知的 iterator .基本上,迭代器是一个可以使用内置泛型函数 next() 的对象。 1.生成器只是一种迭代器。
  • 如果拨打__iter__成功了,in关键字应用函数 next() 一遍又一遍地迭代对象。 (记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更准确地说:它调用迭代器对象的 __next__ 方法)。
  • 如果对象没有 __iter__返回迭代器的方法,in然后使用对象的 __getitem__ 回退到旧式迭代协议(protocol)。方法二。
  • 如果以上所有尝试均失败,您将收到 TypeError exception .

  • 如果你想创建你自己的对象类型来迭代(即,你可以使用 for in ,或者只是 in ),了解 yield 很有用关键字,用于 generators (正如刚才提到的)。
    class MyIterable():
        def __iter__(self):
            yield 1
    
    m = MyIterable()
    for _ in m: print(_) # 1
    1 in m # True    
    
    yield的存在将函数或方法变成生成器而不是常规函数/方法。您不需要 __next__方法,如果您使用生成器(它会自动带来 __next__)。

    如果您希望创建自己的容器对象类型(即,您可以单独使用 in,但不能使用 for in),您只需要 __contains__方法。
    class MyUselessContainer():
        def __contains__(self, obj):
            return True
    
    m = MyUselessContainer()
    1 in m # True
    'Foo' in m # True
    TypeError in m # True
    None in m # True
    

    1 请注意,要成为迭代器,对象必须实现 the iterator protocol .这仅意味着 __next____iter__方法必须正确实现(生成器“免费”提供此功能,因此您在使用它们时无需担心)。另请注意 ___next__方法 is actually next (no underscores) in Python 2 .

    2 见 this answer用于创建可迭代类的不同方法。

    关于Python for 循环和迭代器行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29403401/

    有关Python for 循环和迭代器行为的更多相关文章

    1. ruby - 树顶语法无限循环 - 2

      我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

    2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

      我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

    3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

      我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

    4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

      我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

    5. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

      我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

    6. ruby - Ruby gsub 替换中的行为不一致? - 2

      两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

    7. ruby-on-rails - Ruby 中意外的大小写行为 - 2

      我在一段非常简单的代码(如我所想)中得到了一个错误的值:org=4caseorgwhenorg=4val='H'endputsval=>nil请不要生气,我希望我错过了一些非常明显的东西,但我真的想不通。谢谢。 最佳答案 这是典型的Ruby错误。case有两种被调用的方法,一种是你传递一个东西作为分支的基础,另一种是你不传递的东西。如果您确实在case中指定了一个表达式语句然后评估所有其他条件并与===进行比较.在这种情况下org评估为false和org===false显然不是真的。所有其他情况也是如此,它们要么是真的,要么是假的。

    8. ruby - 使对象的行为类似于 ruby​​ 中并行分配的数组 - 2

      假设您在Ruby中执行此操作:ar=[1,2]x,y=ar然后,x==1和y==2。是否有一种方法可以在我自己的类中定义,从而产生相同的效果?例如rb=AllYourCode.newx,y=rb到目前为止,对于这样的赋值,我所能做的就是使x==rb和y=nil。Python有这样一个特性:>>>classFoo:...def__iter__(self):...returniter([1,2])...>>>x,y=Foo()>>>x1>>>y2 最佳答案 是的。定义#to_ary。这将使您的对象被视为要分配的数组。irb>o=Obje

    9. ruby - Ruby 中的闭包和 for 循环 - 2

      我是Ruby的新手,有些闭包逻辑让我感到困惑。考虑这段代码:array=[]foriin(1..5)array[5,5,5,5,5]这对我来说很有意义,因为i被绑定(bind)在循环之外,所以每次循环都会捕获相同的变量。使用每个block可以解决这个问题对我来说也很有意义:array=[](1..5).each{|i|array[1,2,3,4,5]...因为现在每次通过时都单独声明i。但现在我迷路了:为什么我不能通过引入一个中间变量来修复它?array=[]foriin1..5j=iarray[5,5,5,5,5]因为j每次循环都是新的,我认为每次循环都会捕获不同的变量。例如,这绝对

    10. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

      我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

    随机推荐