在Python 3.x中寻找熟悉的C语言for循环实现有困难

4

我对Python不熟悉,从Python 3.4开始学习。

我阅读了Python 3.x文档中关于循环惯用法的部分,并没有找到构建熟悉的C家族for循环的方法,即:

   for (i = 0; i < n; i++) {
       A[i] = value;
   }

在Python中编写像这样的for-loop似乎是不可能的,这是有意设计的。有人知道为什么Python迭代一个序列遵循这样的模式吗?
for x in iterable: # e.g. range, itertools.count, generator functions
    pass;

这样做是否更高效、更方便,或者减少了索引越界异常的发生?


3
原因?Guido 认为 for i in range(n): 没有问题。请参见 PEP284 - NightShadeQueen
@JGreenwell:那个标签几乎没有什么用! - Lightness Races in Orbit
@LightnessRacesinOrbit 这个问题是关于“为什么一种类型的for循环比另一种更受欢迎”的 - 标签应该反映问题的内容(这里是关于for循环,而不仅仅包含它们),并且对于未来的搜索有用:添加此标签可以实现两者。实际上,我认为拒绝理由“编辑太小”更可接受(认为它太小,但引用我上面的原因)。 - LinkBerest
1
C语言的语法相对较早就确定下来了,其决策反映了当时常见的实际情况。之后出现的语言则可以从中受益。避免“差一错误”是尝试不同方法的一个重要激励。 - John La Rooy
8个回答

7
for lower <= var < upper:

这是一个C风格循环的拟议语法。我说“拟议语法”,因为PEP 284被拒绝了,原因是:

具体来说,Guido认为range()格式不需要修复,“(15年前)range()的整个目的就是为了*避免*需要语法来指定数字循环。我认为它已经很好地发挥了作用,没有什么需要修复的(除了range()需要成为迭代器,在Python 3.0中将成为迭代器)。"

所以我们不能使用for lower <= var < upper:

那么,如何获得C风格的循环呢?嗯,你可以使用range([start,]end[,step])

for i in range(0,len(blah),3):
    blah[i] += merp #alters every third element of blah
                    #step defaults to 1 if left off

如果您需要索引和值,可以使用enumerate

for i,j in enumerate(blah):
    merp[j].append(i)

如果您想同时查看两个(或更多!)迭代器,可以使用zip命令(还有:itertools.izipitertools.izip_longest)。
for i,j in zip(foo,bar):
    if i == j: print("Scooby-Doo!") 

最后,总有 while 循环

i = 0
while i < upper:
    A[i] = b
    i++

附录:还有PEP 276,建议使int可迭代,但也被拒绝了。仍然会是半开区间。

那个提案很容易理解,但Guido的拒绝看起来是有道理的(尽管如果startstop参数是包含边界的话,range会更清晰,因为至少根据我看到的示例中,count似乎更适合用于stop的名称...)。在某些问题中,使用修改后的范围range(L, R if L == 0 else R + 1)会更容易,假设区间[L,R]。 - codeReview
答案令人困惑。为什么要在顶部放置无关的非Python语法?(有许多被拒绝的想法)我没有看到for (i = 0; i < n; i++) A[i] = value;的习惯用法Python等价物。 - jfs
对于这种情况,使用for i in range(n): A[i] = value和while循环都可以。 - NightShadeQueen

5

range(n) 生成一个适合迭代的对象 :)

for i in range(n):
    A[i] = value

对于更一般的情况(不仅仅是计数),您应该转换为 while 循环。例如:

i = 0
while i < n:
    A[i] = value
    i += 1

1
我知道这对一些人来说可能很自然,但值得注意的是,在Python 3中,range(n)返回一个类似生成器的对象,其“元素”在范围[0,n)内。而Python 2的range则返回一个列表对象。 - erip
我在文档中没有找到很多 while 使用示例,而且使用两个整数来控制迭代的可变序列迭代和变异似乎更快。尽管如此,我学习Python是为了快速解决代码问题,而不是为了性能。但我确实想知道调用for i in可迭代对象的成本或调用生成器函数(如rangeenumerateitertools.count)是否比你答案中的while循环模式运行得稍慢一些。 - codeReview
1
@codeReview,这取决于您使用的实现。在pypy中,while循环速度更快,但在cPython下速度较慢。无论哪种情况,这都是微观优化,不会是我寻求更好性能的第一选择。对于循环而言,pypy比cPython快约30倍,因此如果您编写了大量循环的代码,则需要考虑这一点。 - John La Rooy
我一直在使用CPython 3.4.0 64位版本,因为python.org使其对初学者非常容易获取。我忍不住尝试了NumPy,但现在我只是在努力熟练掌握CPython的内置模块,然后再转向其他版本。我应该遵循优化建议 - codeReview

5

现在大多数语言都采用了foreach循环,因为你通常只需要访问集合中的对象而不需要索引,而且像set这样需要迭代器才能访问的元素可以使用与随机可访问集合完全相同的语法进行迭代。

在Python中,在迭代时正确的访问索引的方式应该是:

for i, x in enumerate(iterable):

此时,i是您的索引,xiterable[i]上的项。


2
首先,Python 有几种执行 C 风格的 for 循环的方法,其中最常见的两种是(你在帖子中提到的第一种,即使用 range 返回的生成器对象):
for i in range(some_end_value):
    print(i)
# or the many times preferred
for i, elem in enumerate(some_list):
    print("i is at {0} and the value is {1}".format(i, elem))

关于为什么Python是这样设置的,我认为这只是一种更方便和更受欢迎的设置foreach-style循环的方式 - 特别是随着语言不再需要定义其最大索引的数组/列表。例如,在中,也可以这样做:
for (int i: someArray) {
    system.out.println(i) // which would print the current item of an integer array
}

"

(foreach (int i in someArray))和 (for (auto &i: int))也有它们自己的foreach循环。而在中,大多数人倾向于编写宏来获得foreach循环的功能。

这只是一种方便的方式来访问动态数组、列表、字典和其他构造。而循环可以用于必须修改迭代器本身的活动 - 或者只需创建第二个变量并使用迭代器进行数学修改。

"
(Note: I have kept the original English terms for programming constructs such as "foreach loop" and "dynamic arrays" as they are commonly used in Chinese programming communities.)

2

Python是比C语言更高级的语言,使用高级抽象(如“序列”)进行迭代更自然、更安全,因此需要另一个高级抽象——“迭代器”。 C语言实际上没有这样的抽象,因此用低级别的“手动”索引或指针增量表达大多数遍历也就不足为奇了。不过,这是C语言低级本质的产物——对于大多数循环结构来说,使用它作为主要构建块是愚蠢的,而且不仅仅是Python。


2
您需要查看使用range()函数。
for i in range(n):
    A[i] = value

该函数可以作为range(n)使用,返回一个整数列表从0到n,也可以作为range(start, end)使用,返回从起始值到结束值的整数。例如:

range(1, 5)

将会给你数字1、2、3、4和5。


2

在Python中实现C风格的for循环的最佳方法是使用range函数。很少有人知道range(stop)这个重载函数,它接受start、stop和step三个参数,其中step是可选的。有了这个函数,你几乎可以做到任何你能用C-style for loop做的事情:

range(start, stop[, step])

for (i = 0; i < 10; i++)
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for (i = 1; i < 11; i++)    
>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for (i = 0; i < 30; i=i+5)
>>> range(0, 30, 5)
[0, 5, 10, 15, 20, 25]
for (i = 0; i < 10; i=i+3)
>>> range(0, 10, 3)
[0, 3, 6, 9]
for (i = 0; i > -10; i--)
>>> range(0, -10, -1)
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

请查看 https://docs.python.org/2/library/functions.html#range


1
C循环的等价形式:
for (i = 0; i < n; i++) A[i] = value; 

例如,如果A是一个numpy数组,则将数组中的所有项设置为相同的值:
A[:] = value

如果 len(A) > n,那么
A[:n] = value

如果您想创建一个具有n个值的Python列表:
A = [value] * n #NOTE: all items refer to the *same* object

你可以在现有列表中替换值:
A[:n] = [value]*n #NOTE: it may grow if necessary

或者不创建临时列表:

for i in range(n): A[i] = value

Pythonic的枚举方式是在使用值的同时枚举所有值及其对应的索引:
for index, item in enumerate(A):
    A[index] = item * item

代码也可以使用列表推导式来编写:
A = [item * item for item in A] #NOTE: the original list object may survive

不要试图用Python编写C。


我会尝试使用numpy库,但如果我在hackerrank.com上工作的话,numpy是不允许的,而且我正在努力学习基础知识。无论如何+1。自从我猜测它比enumerate更便宜以来,我基本上一直在使用for i in range`循环模式。 - codeReview
@codeReview:不要过早地进行优化。除非您的分析器指出问题,否则请使用惯用代码。 - jfs
感谢您的建议,现在编写解决方案的时间更短了,而且有时候优化也是不必要的。 - codeReview

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