A pit that is often stepped on in Python lists and dictionaries

Get into the habit of writing together! This is the 14th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the details of the event .

[Introduction]: In Python, if we want to modify a set of data in the process of traversing a set of data, there are usually many problems. For example, when performing the above operation on a list, some data will be ignored; when traversing a dictionary , the data cannot be modified. This paper proposes a variety of solutions to these problems.

Introduction

1. About the list

1. Problem description

In Python, if you try to modify a set of data while iterating over it, this is usually fine. E.g:


l = [3, 4, 56, 7, 10, 9, 6, 5]

for i in l:
    if not i % 2 == 0:
        continue
    l.remove(i)

print(l)
复制代码

The above code traverses a list of numbers, and directly modifies the list l in order to remove all even numbers. However, the output after running is:

[3, 56, 7, 9, 5]
复制代码

Wait a moment! The output doesn't seem right. The final result still contains an even number 56. Why is this number not successfully removed? We can try to print out all the elements traversed by the for loop, running the following code:

l = [3, 4, 56, 7, 10, 9, 6, 5]

for i in l:
    print(i)
    if not i % 2 == 0:
        continue
    l.remove(i)

print(l)
复制代码

The output of this code is:

3
4
7
10
6
[3, 56, 7, 9, 5]
复制代码

As you can see from the output, the for loop doesn't seem to be accessing all the elements in the list. To understand what the for loop does internally, we can simulate it using iter and next. Take a look at the example below, where I use the ipython shell to run the code:


In [1]: l = [3, 4, 56, 7, 10, 9, 6, 5]

In [2]: # 把列表变成一个迭代器

In [3]: it = iter(l)

In [4]: # 使用 next() 方法来模拟 for循环

In [5]: next(it)
Out[5]: 3

In [6]: next(it)
Out[6]: 4

In [7]: # 移除一个迭代器已经访问过的元素

In [8]: l.remove(3)

In [9]: next(it)
Out[9]: 7

In [10]: # 注意此处跳过了56,我们可以再移除一个元素

In [11]: l.remove(4)

In [12]: next(it)
Out[12]: 9
复制代码

The above experiment reveals that when you remove an element that an iterator has already visited, on the next iteration, you skip an element on the right and go directly to the next one.

The opposite is still true, that is, after starting the iteration, if you add an element to the beginning of the list, the next iteration may access the element that has already been iterated. This happens in the following code:

In[1]: l = [3, 4, 56, 7, 10, 9, 6, 5]

In[2]: it = iter(l)

In[3]: next(it)
Out[3]: 3

In[4]: next(it)
Out[4]: 4

In[5]: l.insert(0, 44)

In[6]: next(it)
Out[6]: 4
复制代码

Note that when 44 is added to the head of the list, 4 is accessed twice.

2. Solutions

In order to solve the above problem, we must ensure that the elements visited by the iterator cannot be removed.

Option One

We can first flip the original list to get a new list, then iterate over the new list and remove the elements that do not meet the conditions in the original list l. The program code is as follows:

l = [3, 4, 56, 7, 10, 9, 6, 5]

# 迭代翻转后的列表
for i in reversed(l):
    print(i)
    if not i % 2 == 0:
        continue
    l.remove(i)

print(l)
复制代码

The result is as follows:

5
6
9
10
7
56
4
3
[3, 7, 9, 5]
复制代码

Note that the iterator now successfully accesses all the elements in the list, and finally outputs a list containing only odd numbers.

Option II

We can also copy the list l before starting the iteration. But when there is too much data in the list l, doing so is obviously more performance-intensive. The program code is as follows:

l = [3, 4, 56, 7, 10, 9, 6, 5]

# 在这里使用 'l.copy()' 来对列表 l 进行浅拷贝
for i in l.copy():  
    print(i)   
    if not i % 2 == 0:     
        continue  
    l.remove(i)
    
print(l)
复制代码

The output is as follows:

3
4
56
7
10
9
6
5
[3, 7, 9, 5]
复制代码

This scheme ensures that the order of iteration is the same as the order in which elements are removed. But since the two operations iterate and remove are on two different lists, it doesn't matter that the order is the same.

2. About the dictionary

1. Problem description

The dictionary cannot be modified while iterating over the dictionary. as follows:

# {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
d = {k: k for k in range(10)}

for k, v in d.items():  
    if not v % 2 == 0:    
        continue  
    d.pop(k)
复制代码

This code produces a RuntimeError:

Traceback (most recent call last):  
  File "F:/Documents/pythonprojects/01practice/app.py", line 7, in <module>  
    for k, v in d.items():
RuntimeError: dictionary changed size during iteration
复制代码

2. Solutions

We can copy all the keys of the dictionary first, and then in the process of iterating the keys, remove the elements that do not meet the conditions. The process is as follows:

# {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
d = {k: k for k in range(10)}
# 这里复制了字典中的所有key值
# 没有复制整个字典
# 同时使用tuple()速度更快
for k in tuple(d.keys()):   
    if not d[k] % 2 == 0:    
        continue  
    d.pop(k)
    
print(d)
复制代码

The output after running the code is as follows:


{1: 1, 3: 3, 5: 5, 7: 7, 9: 9}
复制代码

We successfully removed all even key-value pairs from the dictionary!

in conclusion

In this paper, we propose different solutions for the problem that cannot be modified when iterating over a set of data: if we want to modify the list when traversing the list, we can first flip or copy the original list to get A new list, and then in the process of traversing the new list, modify the data in the original list; if we want to modify the dictionary while traversing the dictionary, we can copy all the key values ​​of the dictionary first, and then iterate the key values. , modify the data in the dictionary.

Thanks

Due to the lack of talent and knowledge of the author, and the limitation of time and energy, errors in the code are inevitable, and readers are welcome to criticize and correct them.

Disclaimer: This article is published for the purpose of passing on more knowledge for exchange and learning. If the source is incorrectly marked or violates your legal rights    , please contact us with the ownership certificate, and we will correct or delete it in time, thank you.

Guess you like

Origin juejin.im/post/7086266876438790151