尝试浏览大型列表时的Python IndexError

时间:2011-01-20 21:33:36

标签: python

我有大约200 000多个对象的列表,每一个代表一个文件(但并不实际持有该文件的内容,不仅仅是使用全路径名和日期)。

我正在编写的程序会复制这些文件的任何子集,具体取决于用户提供的日期范围。我首先创建所有在源目录中的文件的列表(与该glob模块),创建我的文件的表示的类的实例和该实例添加到列表中,像这样:

for f in glob.glob(srcdir + "/*.txt"):
    LOG_FILES.append(LogFile(f))

现在,为了快速复制文件并清除代码块,我删除了不适合日期范围的LogFile对象。

for i in xrange(0, len(LOG_FILES)):
    if LOG_FILES[i].DATE < from_date or LOG_FILES[i].DATE > to_date:
        del(LOG_FILES[i])

之后,我可以复制列表中剩下的文件:

for logfile in LOG_FILES:
    os.copy(logfile.PATH, destdir)

for i in xrange...示例出现该问题:我被抛出的IndexError时的值i到达63792

IndexError: list index out of range.

有什么想法吗?

编辑非常感谢您的快速回复!现在我想起来,这对我来说是一个愚蠢的疏忽。再次,谢谢大家。 :)

7 个答案:

答案 0 :(得分:7)

来自the docs

  

修改循环中迭代的序列是不安全的(这只能发生在可变序列类型中,例如列表)。如果您需要修改要迭代的列表(例如,复制所选项目),则必须迭代副本。

对于您的情况,我实际上建议使用生成器表达式和itertools.ifilter,以避免为您的大文件列表制作不必要的副本。

答案 1 :(得分:3)

您的方法存在的问题是del()正在删除该索引列表中的条目并重新排序列表

例如,如果列表中有五个项目并在第三个索引上调用del(),则列表的内容将向下移动,以便另一个元素获取第三个索引。

list = [1,2,3,4,5]
del(list[2])
print list     # outputs [1, 2, 4, 5]
print list[2]  # outputs 4

由于您从0循环到列表的原始大小,即使您从列表中只删除了一个项目,您最终也会到达列表中不再包含的索引。

更简单的方法是在向其添加项目时过滤列表。

for f in glob.glob(srcdir + "/*.txt"):
    lf = LogFile(f)
    if lf.DATE < from_date and lf.DATE > to_date:
        LOG_FILES.append(lf)

这可能会更加pythonic,但应该足够可读,以获得重点。

答案 2 :(得分:2)

[编辑] 哎呀,我忘了颠倒“&lt;”和“&gt;”并添加一个'等于'标志。

LOG_FILES = [LogFile(f) for f in glob.glob(srcdir + "/*.txt")
                        if from_date <= f.DATE <= to_date]

这可以取代LOG_FILES的整个初始化。这是一个列表理解(如果你希望你可以把它作为一个生成器(在枚举之前不会被评估),可以用[]替换()。这可能会更有效,这取决于你用它做什么。

您需要这样做,因为不允许在枚举时编辑集合。 (见上文,far more eloquent答案)。

您可以像这样阅读上面的表达式:

“创建一个LogFile结果的列表(或可枚举),当它在'glob.glob(...)'中为每个f传递'f'时,但仅当'if'语句为真。”< / p>

请参阅该链接的The List Comprehension部分。

答案 3 :(得分:1)

如果您在具有固定上限的数组上循环并同时删除元素,生成索引错误。您必须循环复制或使用动态索引。既然你说数组很大,我们使用后者:

limit, i = len(LOG_FILES), 0
while i < limit:
    if LOG_FILES[i].DATE < from_date and LOG_FILES[i].DATE > to_date:
        del(LOG_FILES[i])
        limit -= 1
    else:
        i += 1

答案 4 :(得分:1)

您也可以使用filter

LOG_FILES = filter(lambda log_file: log_file.DATE < from_date and \
                                    log_file.DATE > to_date, LOG_FILES)

答案 5 :(得分:1)

Cpfohl的回答有一个问题:

LOG_FILES = [LogFile(f) for f in glob.glob(srcdir + "/*.txt")
             if f.DATE >= from_date and f.DATE <= to_date]

由于

for f in glob.glob(srcdir + "/*.txt"):
    LOG_FILES.append(LogFile(f))

因此LOG_FILES [i]是LogFile(f) 然后LOG_FILES [i] .DATE是一个LogFile(f).DATE,而不是f.DATE

答案 6 :(得分:0)

1)在列表中从头到尾的迭代中删除元素解决问题

LOG_FILES = [ 1,2,30,2,5,8,30,3,2,37,22,30,27,30,4 ]

print LOG_FILES

L = len(LOG_FILES)-1
for i,x in enumerate(LOG_FILES[::-1]):
    print i,L-i,' ',LOG_FILES[L-i],x
    if x>15:
        del LOG_FILES[L-i]

print LOG_FILES

结果

[1, 2, 30, 2, 5, 8, 30, 3, 2, 37, 22, 30, 27, 30, 4]
0 14   4 4
1 13   30 30
2 12   27 27
3 11   30 30
4 10   22 22
5 9   37 37
6 8   2 2
7 7   3 3
8 6   30 30
9 5   8 8
10 4   5 5
11 3   2 2
12 2   30 30
13 1   2 2
14 0   1 1
[1, 2, 2, 5, 8, 3, 2, 4]

2)顺便说一下

if LOG_FILES[i].DATE < to_date and LOG_FILES[i].DATE > from_date :

可以写

if from_date  < LOG_FILES[i].DATE < to_date: