是否可以在没有迭代器变量的情况下实现 Python for range 循环?

Posted

技术标签:

【中文标题】是否可以在没有迭代器变量的情况下实现 Python for range 循环?【英文标题】:Is it possible to implement a Python for range loop without an iterator variable? 【发布时间】:2010-10-23 13:27:11 【问题描述】:

如果没有i,是否可以进行关注?

for i in range(some_number):
    # do something

如果你只想做某件事 N 次并且不需要迭代器。

【问题讨论】:

这是个好问题! PyDev 甚至将“i”标记为“未使用变量”的警告。下面的解决方案消除了这个警告。 @Ashwin 您可以使用 \@UnusedVariable 删除该警告。请注意,我需要转义“at”符号才能通过此评论。 我问你同样的问题。 pylint 警告很烦人。当然,您可以通过像@Raffi Khatchadourian 提议的额外抑制来禁用警告。最好避免 pylint 警告抑制 cmets。 【参考方案1】:

在我的脑海中,没有。

我认为你能做的最好的事情是这样的:

def loop(f,n):
    for i in xrange(n): f()

loop(lambda: <insert expression here>, 5)

但我认为你可以忍受额外的 i 变量。

这里是使用_ 变量的选项,实际上它只是另一个变量。

for _ in range(n):
    do_something()

请注意,_ 被分配了在交互式 python 会话中返回的最后一个结果:

>>> 1+2
3
>>> _
3

因此,我不会以这种方式使用它。我不知道 Ryan 提到的任何成语。它可能会弄乱您的解释器。

>>> for _ in xrange(10): pass
...
>>> _
9
>>> 1+2
3
>>> _
9

并且根据Python grammar,这是一个可接受的变量名:

identifier ::= (letter|"_") (letter | digit | "_")*

【讨论】:

“但我认为你可以忍受额外的“i””是的,这只是一个学术点。 @nemo,如果您不想使用字母数字名称,您可以尝试 for _ in range(n):。 在这种情况下 _ 是一个变量吗?还是 Python 中的其他东西? @nemo 是的,它只是一个可接受的变量名。在解释器中,它会自动分配你最后一个表达式。 @kurczak 有一点。使用_ 可以清楚地表明它应该被忽略。说这样做没有意义就像说评论你的代码没有意义 - 因为它无论如何都会做同样的事情。【参考方案2】:

您可能正在寻找

for _ in itertools.repeat(None, times): ...

这是在 Python 中迭代 times 次的最快方法。

【讨论】:

我并不关心性能,我只是好奇是否有更简洁的方式来编写语句。虽然我已经零星使用 Python 大约 2 年了,但我仍然觉得我缺少很多东西。 Itertools 就是其中之一,感谢您提供的信息。 这很有趣,我没有意识到这一点。我只是看了一下 itertools 文档;但我想知道为什么这比使用 range 或 xrange 更快? @blackkettle:它更快,因为它不需要返回当前迭代索引,这是 xrange 成本的可衡量部分(以及 Python 3 的范围,它提供了一个迭代器,而不是一个列表)。 @nemo,范围已尽可能优化,但需要构建和返回列表不可避免地比迭代器更繁重(在 Py3 中,范围确实返回一个迭代器,如 Py2 的 xrange;向后兼容性不允许这样的更改在 Py2 中),尤其是不需要返回可变值的那个。 @Cristian,是的,显然每次都准备并返回一个 Python int,inc。 gc 工作,确实有可衡量的成本 -- 内部使用计数器 没关系。 我现在明白了。差异来自 GC 开销,而不是来自“算法”。顺便说一句,我运行了一个快速的 timeit 基准测试,加速比约为 1.42 倍。【参考方案3】:

分配给未使用值的一般习惯用法是将其命名为_

for _ in range(times):
    do_stuff()

【讨论】:

【参考方案4】:

每个人都建议您使用 _ 并不是说​​ _ 经常被用作gettext 功能之一的快捷方式,因此如果您希望您的软件以多种语言提供,那么您就是最好避免将其用于其他目的。

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')

【讨论】:

对我来说,_ 的使用似乎是个糟糕的主意,我不介意与之冲突。【参考方案5】:

这是一个利用(滥用?)data model (Py3 link) 的随机想法。

class Counter(object):
    def __init__(self, val):
        self.val = val

    def __nonzero__(self):
        self.val -= 1
        return self.val >= 0
    __bool__ = __nonzero__  # Alias to Py3 name to make code work unchanged on Py2 and Py3

x = Counter(5)
while x:
    # Do something
    pass

不知道标准库中是否有这样的东西?

【讨论】:

我认为有一个像 __nonzero__ 这样带有副作用的方法是一个可怕的想法。 我会改用__call__while x(): 并没有那么难写。 还有一个说法是避免使用Counter这个名字;当然,它不是保留的或在内置范围内,而是collections.Counter is a thing,并且创建一个同名的类可能会导致维护者混淆(并不是说这已经没有风险了)。【参考方案6】:

您可以使用 _11(或任何数字或其他无效标识符)来防止与 gettext 发生名称冲突。每当您使用下划线 + 无效标识符时,您都会得到一个可用于 for 循环的虚拟名称。

【讨论】:

不错! PyDev 同意你的观点:这消除了“未使用的变量”黄色警告。【参考方案7】:

可能的答案取决于您在使用迭代器时遇到的问题? 可以用

i = 100
while i:
    print i
    i-=1

def loop(N, doSomething):
    if not N:
        return
    print doSomething(N)
    loop(N-1, doSomething)

loop(100, lambda a:a)

但坦率地说,我认为使用这种方法没有意义

【讨论】:

注意:Python(至少绝对不是 CPython 参考解释器,可能不是大多数其他的)没有优化尾递归,所以 N 将被限制在 @ 的值附近987654321@(在 CPython 上默认为低四位范围内的某个位置);使用 sys.setrecursionlimit 会提高限制,但最终你会达到 C 堆栈限制,解释器会因堆栈溢出而死(不仅仅是提高一个不错的 RuntimeError/RecursionError)。【参考方案8】:

现在您有一个不需要的列表,而不是不需要的计数器。 最好的解决方案是使用以“_”开头的变量,它告诉语法检查器您知道您没有使用该变量。

x = range(5)
while x:
  x.pop()
  print "Work!"

【讨论】:

【参考方案9】:

我一般同意上面给出的解决方案。即用:

    for-loop 中使用下划线(2 行或更多行) 定义一个普通的while 计数器(3 行或更多行) 用__nonzero__ 实现声明一个自定义类(更多行)

如果要在 #3 中定义一个对象,我建议为with keyword 实现协议或应用contextlib。

我还提出了另一种解决方案。它是 3 班轮,不是非常优雅,但它使用 itertools 包,因此可能会引起人们的兴趣。

from itertools import (chain, repeat)

times = chain(repeat(True, 2), repeat(False))
while next(times):
    print 'do stuff!'

在这些示例中,2 是循环迭代的次数。 chain 包装了两个 repeat 迭代器,第一个是有限的,第二个是无限的。请记住,这些是真正的迭代器对象,因此它们不需要无限内存。显然这比解决方案 #1 慢得多。除非作为函数的一部分编写,否则可能需要清理 times 变量。

【讨论】:

chain 是不必要的,times = repeat(True, 2); while next(times, False): 做同样的事情。【参考方案10】:

我们在以下方面获得了一些乐趣,很有趣的分享如下:

class RepeatFunction:
    def __init__(self,n=1): self.n = n
    def __call__(self,Func):
        for i in xrange(self.n):
            Func()
        return Func


#----usage
k = 0

@RepeatFunction(7)                       #decorator for repeating function
def Job():
    global k
    print k
    k += 1

print '---------'
Job()

结果:

0
1
2
3
4
5
6
---------
7

【讨论】:

【参考方案11】:

如果do_something是一个简单的函数或者可以封装在一个函数中,一个简单的map()可以do_somethingrange(some_number)次:

# Py2 version - map is eager, so it can be used alone
map(do_something, xrange(some_number))

# Py3 version - map is lazy, so it must be consumed to do the work at all;
# wrapping in list() would be equivalent to Py2, but if you don't use the return
# value, it's wastefully creating a temporary, possibly huge, list of junk.
# collections.deque with maxlen 0 can efficiently run a generator to exhaustion without
# storing any of the results; the itertools consume recipe uses it for that purpose.
from collections import deque

deque(map(do_something, range(some_number)), 0)

如果您想将参数传递给do_something,您可能还会发现itertools repeatfunc recipe 读起来很好:

传递相同的参数:

from collections import deque
from itertools import repeat, starmap

args = (..., my args here, ...)

# Same as Py3 map above, you must consume starmap (it's a lazy generator, even on Py2)
deque(starmap(do_something, repeat(args, some_number)), 0)

传递不同的参数:

argses = [(1, 2), (3, 4), ...]

deque(starmap(do_something, argses), 0)

【讨论】:

【参考方案12】:
#Return first n items of the iterable as a list
list(itertools.islice(iterable, n))

取自http://docs.python.org/2/library/itertools.html

【讨论】:

【参考方案13】:

如果您真的想要避免使用名称(OP 中的迭代变量,或者不需要的列表或不需要的生成器返回所需的时间),您可以这样做,如果你真的想要:

for type('', (), ).x in range(somenumber):
    dosomething()

使用的技巧是创建一个匿名类type('', (), ),这会产生一个名称为空的类,但请注意它没有插入到本地或全局名称空间中(即使提供了非空名称)。然后,您将该类的成员用作无法访问的迭代变量,因为它所属的类是无法访问的。

【讨论】:

显然这是故意的病态,所以批评它是题外话,但我会在这里指出一个额外的陷阱。在 CPython 上,引用解释器,类定义自然是循环的(创建一个类不可避免地会创建一个引用循环,该循环会阻止基于引用计数的类的确定性清理)。这意味着您正在等待循环 GC 启动并清理课程。它通常会作为年轻一代的一部分被收集,默认情况下会频繁收集,但即便如此,每个循环也意味着大约 1.5 KB 的垃圾,具有不确定的生命周期。 基本上,为了避免在每个循环上(通常)确定性地清理命名变量(当它反弹时,清理旧值时),你正在制作一个巨大的未命名变量,即非确定性清洁,并且可以很容易地持续更长时间。【参考方案14】:

怎么样:

while range(some_number):
    #do something

【讨论】:

这是一个无限循环,因为条件 range(some_number) 始终为真! @deadly:好吧,如果some_number 小于或等于0,它不是无限的,它永远不会运行。 :-) 对于无限循环(尤其是在 Py2 上)来说,它的效率相当低,因为它为每个测试创建一个新的 list (Py2) 或 range 对象 (Py3)(从解释器的角度来看,它不是一个常数,它必须在每个循环中加载rangesome_number,调用range,然后测试结果。

以上是关于是否可以在没有迭代器变量的情况下实现 Python for range 循环?的主要内容,如果未能解决你的问题,请参考以下文章

Python3 魔法方法:迭代器

是否可以在不创建特殊迭代器的情况下每次迭代步进不同的量?

Apex 中的自定义迭代器

11.Python迭代器

NHibernate 可以在没有迭代器的情况下保存集合吗?

python之装饰器生成器迭代器