在for循环中如何修改列表项?


177

现在,我知道在迭代循环中修改列表是不安全的。但是,假设我有一个字符串列表,并且我想剥离字符串本身。替换可变值是否算作修改?


20
字符串不是可变值。
user470379 2010年

4
@ user470379:列表中的元素是否可变与在循环遍历时是否安全修改它们所在的列表无关。
martineau

Answers:


143

它被认为是不良形式。如果需要保留对列表的现有引用,请使用列表理解来代替切片分配。

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)

10
切片分配很聪明,可以避免在循环期间修改原始文件,但是需要创建一个长度为原始文件的临时列表。
martineau,2010年

11
为什么我们指定b = a?
Vigrond

9
@Vigrond:因此,在print b执行该语句时,您可以知道是否a就地进行了修改,而不是替换了。另一种可能性是print b is a查看它们是否仍然都引用同一对象。
martineau

12
为什么a [:] =而不仅仅是a =?
kdubs

10
@kdubs:“ ...如果您需要保留列表的现有引用,则具有切片分配。”
伊格纳西奥·巴斯克斯·阿布拉姆斯

162

由于下面的循环仅修改已经看到的元素,因此可以接受:

a = ['a',' b', 'c ', ' d ']

for i, s in enumerate(a):
    a[i] = s.strip()

print(a) # -> ['a', 'b', 'c', 'd']

不同于:

a[:] = [s.strip() for s in a]

尽管它确实需要更多的索引操作,但它不需要创建临时列表和分配临时列表来替换原始列表。

注意:尽管您可以通过这种方式修改条目,但是您不能在不改变list遇到问题的风险的情况下更改其中的项数。

这是我的意思的示例-从该点开始删除条目会使索引混乱:

b = ['a', ' b', 'c ', ' d ']

for i, s in enumerate(b):
    if s.strip() != b[i]:  # leading or trailing whitespace?
        del b[i]

print(b)  # -> ['a', 'c ']  # WRONG!

(结果是错误的,因为它没有删除应有的所有项目。)

更新资料

由于这是一个相当普遍的答案,因此,这是有效地“就地”删除条目的方法(即使这不是确切的问题):

b = ['a',' b', 'c ', ' d ']

b[:] = [entry for entry in b if entry.strip() == entry]

print(b)  # -> ['a']  # CORRECT

3
但是,为什么Python只在语法中复制单个元素for i in a?这是非常违反直觉的,似乎与其他语言不同,并且导致我的代码中的错误,我不得不长时间进行调试。Python教程甚至没有提到它。虽然一定有一定理由吗?
xji

1
@JIXiang:它没有复制。它只是将循环变量名称分配给要迭代的事物的连续元素或值。
martineau

1
哎呀,为什么不必在同一行中对同一对象使用两个名称(a[i]s)?我宁愿做a[i] = a[i].strip()
纳文2017年

3
@Navin:因为a[i] = s.strip()仅执行一次索引操作。
martineau

1
@martineau enumerate(b)在每次迭代中都执行索引操作,而您正在使用进行另一个操作a[i] =。AFAIK不可能在Python中通过每个循环迭代仅执行1次索引操作来实现此循环:(
Navin

18

还有一个for循环变量,对我来说比使用enumerate()更干净:

for idx in range(len(list)):
    list[idx]=... # set a new value
    # some other code which doesn't let you use a list comprehension

19
许多人认为range(len(list))在Python中使用类似代码的味道。
martineau 2014年

2
@Reishin:因为enumerate是生成器,所以它不会创建一个元组列表,它会在遍历该列表时一次创建一个元组。判断哪个较慢的唯一方法是timeit
martineau,2015年

3
@martineau 代码可能不太漂亮,但据显示timeit enumerate比较慢
Reishin

2
@Reishin:您的基准测试代码不是完全有效的,因为它没有考虑到在给定索引处检索列表中的值的需要-该答案也未显示。
martineau

4
@Reishin:正是由于这个原因,您的比较是无效的。它隔离地测量循环开销。确切地说,必须测量执行整个循环所花费的时间,因为有可能通过某种方式在循环内为代码提供的好处可以减轻任何开销差异—否则,您就不会将苹果与苹果。
martineau

11

只要不更改将元素添加/删除到列表中,在迭代列表时修改每个元素就可以了。

您可以使用列表推导:

l = ['a', ' list', 'of ', ' string ']
l = [item.strip() for item in l]

或者只是做C-stylefor循环:

for index, item in enumerate(l):
    l[index] = item.strip()

4

不,如果您可以通过这种方式更改字符串,则不会更改列表的“内容”。但是在Python中它们不是可变的。任何字符串操作都将返回一个新字符串。

如果您有一个已知的可变对象列表,那么只要不更改列表的实际内容,就可以执行此操作。

因此,您将需要进行某种映射。如果使用生成器表达式,则[操作]将在迭代时完成,并节省内存。


4

您可以执行以下操作:

a = [1,2,3,4,5]
b = [i**2 for i in a]

这称为列表理解,可以使您更轻松地在列表内部循环。


3

Jemshit Iskenderov和Ignacio Vazquez-Abrams给出的答案确实很好。这个例子可以进一步说明:

a)给出了带有两个向量的列表;

b)您想遍历列表并反转每个数组的顺序

假设您有

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
    i = i[::-1]   # this command does not reverse the string

print([v,b])

你会得到

[array([1, 2, 3, 4]), array([3, 4, 6])]

另一方面,如果您这样做

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
   i[:] = i[::-1]   # this command reverses the string

print([v,b])

结果是

[array([4, 3, 2, 1]), array([6, 4, 3])]

1

从您的问题尚不清楚,确定删除哪些字符串的标准是什么,但是如果您有或可以列出要删除的字符串,则可以执行以下操作:

my_strings = ['a','b','c','d','e']
undesirable_strings = ['b','d']
for undesirable_string in undesirable_strings:
    for i in range(my_strings.count(undesirable_string)):
        my_strings.remove(undesirable_string)

将my_strings更改为['a','c','e']


0

简而言之,在迭代相同列表的同时对列表进行修改。

list[:] = ["Modify the list" for each_element in list "Condition Check"]

例:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.