Làm cách nào để tránh được Runtime RuntimeError: từ điển bị thay đổi kích thước trong khi lặp lại lỗi lỗi?


258

Tôi đã kiểm tra tất cả các câu hỏi khác có cùng lỗi nhưng không tìm thấy giải pháp hữu ích nào = /

Tôi có một từ điển danh sách:

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

trong đó một số giá trị trống Khi kết thúc việc tạo các danh sách này, tôi muốn xóa các danh sách trống này trước khi trả lại từ điển của mình. Hiện tại tôi đang cố gắng làm điều này như sau:

for i in d:
    if not d[i]:
        d.pop(i)

tuy nhiên, điều này mang lại cho tôi lỗi thời gian chạy. Tôi biết rằng bạn không thể thêm / xóa các thành phần trong từ điển trong khi lặp qua nó ... điều gì sẽ xảy ra sau đó?

Câu trả lời:


455

Trong cuộc gọi Python 2.x keystạo một bản sao của khóa mà bạn có thể lặp lại trong khi sửa đổi dict:

for i in d.keys():

Lưu ý rằng điều này không hoạt động trong Python 3.x vì keystrả về một trình vòng lặp thay vì danh sách.

Một cách khác là sử dụng listđể buộc một bản sao của các phím được thực hiện. Cái này cũng hoạt động trong Python 3.x:

for i in list(d):

1
Tôi tin rằng bạn có nghĩa là 'cuộc gọi keystạo ra một bản sao của các phím mà bạn có thể lặp lại' hay còn gọi là các pluralphím phải không? Mặt khác, làm thế nào người ta có thể lặp lại qua một phím đơn? Nhân tiện, tôi không chọn nit, tôi thực sự quan tâm muốn biết đó thực sự là chìa khóa hay chìa khóa
HighOnMeat

6
Hoặc tuple thay vì danh sách vì nó nhanh hơn.
Brambor

8
Để làm rõ hành vi của python 3.x, d.keys () trả về một iterable (không phải là iterator), nghĩa là nó trực tiếp xem các khóa của từ điển. Việc sử dụng for i in d.keys()thực sự hoạt động trong python 3.x nói chung , nhưng vì nó lặp đi lặp lại qua chế độ xem lặp lại các khóa của từ điển, nên việc gọi d.pop()trong vòng lặp dẫn đến cùng một lỗi như bạn đã tìm thấy. for i in list(d)mô phỏng hành vi python 2 hơi kém hiệu quả khi sao chép các khóa vào danh sách trước khi lặp, cho các trường hợp đặc biệt như của bạn.
Michael Krebs

giải pháp python3 của bạn không hoạt động khi bạn muốn xóa một đối tượng trong dict bên trong. ví dụ: bạn có một dic A và dict B trong dict A. nếu bạn muốn xóa đối tượng trong dict B thì nó xảy ra lỗi
Ali-T

1
Trong python3.x, list(d.keys())tạo cùng một đầu ra như list(d), gọi listmột dictphím trả về. Cuộc keysgọi (mặc dù không đắt lắm) là không cần thiết.
Sean Breckenridge

46

Chỉ cần sử dụng hiểu từ điển để sao chép các mục có liên quan vào một lệnh mới

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.iteritems() if v}
>>> d
{'a': [1], 'b': [1, 2]}

Đối với điều này trong Python 3

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.items() if v}
>>> d
{'a': [1], 'b': [1, 2]}

11
d.iteritems()đã cho tôi một lỗi. Tôi đã sử dụng d.items()thay thế - sử dụng python3
wcyn

4
Điều này hoạt động cho vấn đề đặt ra trong câu hỏi của OP. Tuy nhiên, bất cứ ai đến đây sau khi nhấn RuntimeError này bằng mã đa luồng, hãy lưu ý rằng GIL của CPython cũng có thể được phát hành ở giữa mức độ hiểu danh sách và bạn phải sửa nó theo cách khác.
Yirkha

40

Bạn chỉ cần sử dụng "bản sao":

Theo cách đó, bạn lặp đi lặp lại trên các trường từ điển gốc và đang di chuyển có thể thay đổi dict (dict) mong muốn. Nó hoạt động trên từng phiên bản python, vì vậy nó rõ ràng hơn.

In [1]: d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

In [2]: for i in d.copy():
   ...:     if not d[i]:
   ...:         d.pop(i)
   ...:         

In [3]: d
Out[3]: {'a': [1], 'b': [1, 2]}

Đã làm việc! Cảm ơn bạn.
Guilherme Carvalho Lithg

13

Tôi sẽ cố gắng tránh chèn danh sách trống vào vị trí đầu tiên, nhưng, thường sẽ sử dụng:

d = {k: v for k,v in d.iteritems() if v} # re-bind to non-empty

Nếu trước 2.7:

d = dict( (k, v) for k,v in d.iteritems() if v )

hoặc chỉ:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]

+1: tùy chọn cuối cùng rất thú vị vì nó chỉ sao chép các phím của những mục cần xóa. Điều này có thể cho hiệu suất tốt hơn nếu chỉ một số lượng nhỏ các mục cần xóa liên quan đến kích thước của lệnh.
Mark Byers

@MarkByer yup - và nếu một số lượng lớn làm, thì ràng buộc lại lệnh này với một cái mới được lọc là một lựa chọn tốt hơn. Đó luôn là kỳ vọng về cách cấu trúc nên hoạt động
Jon Clements

4
Một mối nguy hiểm với việc rebinding là nếu ở đâu đó trong chương trình có một đối tượng có tham chiếu đến chính tả cũ, nó sẽ không thấy các thay đổi. Nếu bạn chắc chắn không phải vậy, thì chắc chắn ... đó là một cách tiếp cận hợp lý, nhưng điều quan trọng là phải hiểu rằng nó không hoàn toàn giống như sửa đổi chính tả ban đầu.
Mark Byers

@MarkByer điểm cực kỳ tốt - Bạn và tôi biết điều đó (và vô số người khác), nhưng tất cả đều không rõ ràng. Và tôi sẽ đặt tiền lên bàn, nó cũng không cắn bạn ở phía sau :)
Jon Clements

Điểm tránh để chèn các mục trống là một điều rất tốt.
Magnus Bodin

12

Đối với Python 3:

{k:v for k,v in d.items() if v}

Đẹp và súc tích. Làm việc cho tôi trong Python 2.7 cũng vậy.
donrondadon

6

Bạn không thể lặp qua từ điển trong khi nó thay đổi trong vòng lặp for. Tạo một danh sách để liệt kê và lặp lại danh sách đó, nó hoạt động với tôi.

    for key in list(d):
        if not d[key]: 
            d.pop(key)

1

Đối với các tình huống như thế này, tôi muốn tạo một bản sao sâu và lặp qua bản sao đó trong khi sửa đổi bản gốc.

Nếu trường tra cứu nằm trong danh sách, bạn có thể liệt kê trong vòng lặp for của danh sách và sau đó chỉ định vị trí là chỉ mục để truy cập vào trường trong lệnh gốc.


1

Điều này làm việc cho tôi:

dict = {1: 'a', 2: '', 3: 'b', 4: '', 5: '', 6: 'c'}
for key, value in list(dict.items()):
    if (value == ''):
        del dict[key]
print(dict)
# dict = {1: 'a', 3: 'b', 6: 'c'}  

Việc truyền các mục từ điển vào danh sách sẽ tạo ra một danh sách các mục của nó, vì vậy bạn có thể lặp lại nó và tránh RuntimeError.


1

dictc = {"stName": "asas"} Keys = dictc.keys () cho khóa trong danh sách (khóa): dictc [key.upper ()] = 'Giá trị mới' print (str (dictc))


0

Lý do cho lỗi thời gian chạy là bạn không thể lặp qua cấu trúc dữ liệu trong khi cấu trúc của nó đang thay đổi trong quá trình lặp.

Một cách để đạt được những gì bạn đang tìm kiếm là sử dụng danh sách để nối các khóa bạn muốn xóa và sau đó sử dụng chức năng pop trên từ điển để xóa khóa được xác định trong khi lặp qua danh sách.

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
pop_list = []

for i in d:
        if not d[i]:
                pop_list.append(i)

for x in pop_list:
        d.pop(x)
print (d)

0

Python 3 không cho phép xóa trong khi lặp (sử dụng vòng lặp ở trên) từ điển. Có nhiều lựa chọn thay thế để làm; một cách đơn giản là thay đổi dòng sau

for i in x.keys():

Với

for i in list(x)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.