2012-01-05 90 views
1

在嘗試刪除未開始從列表中的特定字符串的所有文件名,我碰到下面的意外行爲:list.remove()方法不能提供預期的結果

>>> allfiles = os.listdir(mydir) 
>>> allfiles 
['dwcpybyext.sh', 'dwlaunch', 'libupdate.sh', 'ntpsync.sh'] 
>>> for f in allfiles: 
... if f.startswith('n') == False: 
...  allfiles.remove(f) 
... 
>>> allfiles 
['dwlaunch', 'ntpsync.sh'] 

這在理論上應該有的文件名稱不是從列表中的'n'開始的。相反,它在列表中以'd'開頭。如果我改變循環使用if f.startswith('d') == False:我得到['dwcpybyext.sh', 'dwlaunch', 'ntpsync.sh'] - 最後一個項目甚至不包含'd'字符。

爲什麼我看到這種行爲?似乎不太可能是Python的list.remove()方法中的一個錯誤 - 如果我替換del allfiles[allfiles.index(f)],我會得到相同的行爲,而.remove()基本上就是這個別名。

+1

閱讀說明在7.3節:http://docs.python.org/reference/compound_stmts.html #for – 2012-01-05 15:41:47

回答

7

在迭代它的同時修改列表是一個非常糟糕的主意。嘗試下一個:

allfiles = filter(lambda x: x.startswith('n'), allfiles) 
+7

我會使用list comprehension,它更清晰,並且在Python 3中返回一個列表。'[x for allfiles if x.startswith('n')]' – kennytm 2012-01-05 15:40:16

+0

@KennyTM這個選擇取決於OP – 2012-01-05 15:43:21

+0

對於我的需求,列表理解是更好的方法。感謝您的幫助! – Kudzu 2012-01-05 15:52:01

0

您正在修改您正在迭代的同一個列表。

您必須複製列表或迭代它以相反的順序和使用索引來訪問列表,使一個索引始終存在

example = ['a','b','c'] 
for i in reversed(range(len(example))): 
    if example[i] == 'b': 
     del(example[i]) 
2

你不應該改變你遍歷列表。使用

allfiles = [f for f in allfiles if f.startswith('n')] 

改爲。

更新:與filter替代由@RomanBodnarchuk小性能對比(這是完全正常的,當然):

$ python -mtimeit -s'L=range(10000)' '[x for x in L if x < 100]' 
1000 loops, best of 3: 662 usec per loop 
$ python -mtimeit -s'L=range(10000)' 'filter(lambda x: x < 100, L)' 
100 loops, best of 3: 2.06 msec per loop 

看來列表內涵比filterlambda快了3倍

+0

感謝您的額外信息。在這種情況下,速度*是重要的,所以這是使用列表理解的另一個原因。 – Kudzu 2012-01-06 02:32:19

1

不知道確切的,但可能與您正在修改您正在迭代的列表的事實有關!不要這樣做。相反,使用這樣的代碼:

allfiles = [f for f in os.listdir(mydir) if f.startswith('n') 

或者,如果你喜歡一個循環:

allfiles = [] 
for f in os.listdir(mydir): 
    if f.startswith('n'): 
     allfiles.append(f)