【问题标题】:How to modify list entries during for loop?如何在 for 循环期间修改列表条目?
【发布时间】:2011-05-04 03:10:15
【问题描述】:

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

【问题讨论】:

  • 字符串不是可变值。
  • @user470379:列表的元素是否可变与在循环时修改它们所在的列表是否安全无关。
  • 回答你的次要问题:不,值的替换——不管它们自身的可变性——不被视为修改。

标签: python


【解决方案1】:

由于下面的循环只修改了已经看到的元素,它被认为是可以接受的:

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

How to remove items from a list while iterating?

【讨论】:

  • 为什么 Python 只复制 for i in a 语法中的单个元素?这是非常违反直觉的,看起来与其他语言不同,并且导致我的代码出现错误,我不得不长时间调试。 Python教程甚至没有提到它。虽然一定有什么原因?
  • @JIXiang:它不会复制。它只是将循环变量名称分配给被迭代事物的连续元素或值。
  • @Navin:因为a[i] = s.strip()只做一次索引操作。
  • @Navin:使用enumerate() 不会添加索引操作。然而,不管有没有,通过a[i] = s.strip()的每次迭代执行的总数明显少于a[i] = a[i].strip()
  • @variable:不知道这个概念的具体名称。该问题与列表如何在内部存储和迭代有关(未记录在案并且可能在不同版本中有所不同)。在我看来,如果正在迭代的东西在迭代时发生了变化,那么操作可能会“搞砸”——即没有正确完成——这似乎是非常合乎逻辑的。它还取决于修改的内容以及列表中的元素类型。请参阅Modify a list while iterating 了解更多信息。
【解决方案2】:

它被认为是糟糕的形式。如果您需要保留对列表的现有引用,请改用列表推导式,并使用切片分配。

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

【讨论】:

  • 切片赋值很巧妙,避免在循环过程中修改原始,但需要创建一个与原始长度相同的临时列表。
  • @Vigrond:所以当print b 语句被执行时,你可以判断a 是否被就地修改而不是被替换。另一种可能性是 print b is a 以查看它们是否仍然引用同一个对象。
  • 可爱的解决方案和类似python的!
  • 为什么是 a[:] = 而不仅仅是 a =?
  • @kdubs: "...如果您需要保留对列表的现有引用,请使用切片分配。"
【解决方案3】:

还有一个 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

【讨论】:

  • 许多人认为在 Python 中使用类似 range(len(list)) 的东西会产生代码异味。
  • @Reishin:由于enumerate 是一个生成器,它不会创建一个list.of 元组,它会在遍历列表时一次创建一个。判断哪个更慢的唯一方法是timeit
  • @martineau code 可能不太漂亮,但根据timeit enumerate 比较慢
  • @Reishin:您的基准测试代码并不完全有效,因为它没有考虑到需要在给定索引处检索列表中的值 - 此答案中也没有显示.
  • @Reishin:正是由于这个原因,您的比较是无效的。它正在单独测量循环开销。为了得出结论,必须测量执行整个循环所花费的时间,因为任何开销差异都可能通过以某种方式循环的循环内的代码提供的好处来减轻 - 否则你不会将苹果与苹果进行比较苹果。
【解决方案4】:

在迭代列表时修改每个元素是可以的,只要您不将添加/删除元素更改为列表。

您可以使用列表推导:

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

或者只是执行C-style for 循环:

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

【讨论】:

    【解决方案5】:

    Ignacio Vazquez-Abrams 给出的answer 非常好。这个例子可以进一步说明。想象一下:

    1. 为您提供了一个包含两个向量的列表。
    2. 您想遍历列表并颠倒每个数组的顺序。

    假设你有:

    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])]
    

    【讨论】:

      【解决方案6】:

      不,你不会改变列表的“内容”,如果你可以这样改变字符串。但在 Python 中,它们不是可变的。任何字符串操作都会返回一个新字符串。

      如果你有一个你知道是可变的对象的列表,你可以这样做,只要你不改变列表的实际内容。

      因此,您需要制作某种地图。如果您使用生成器表达式,它 [操作] 将在您迭代时完成,您将节省内存。

      【讨论】:

        【解决方案7】:

        你可以这样做:

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

        它被称为列表推导,让您更容易在列表中循环。

        【讨论】:

          【解决方案8】:

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

          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']

          【讨论】:

            【解决方案9】:

            简而言之,就是在迭代同一个列表的同时对列表进行修改。

            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"]]
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-09-28
              • 2016-10-02
              • 2023-01-25
              • 1970-01-01
              • 2018-10-18
              • 1970-01-01
              相关资源
              最近更新 更多