Làm thế nào để xóa các mục từ một từ điển trong khi lặp qua nó?


295

Có hợp pháp để xóa các mục từ một từ điển trong Python trong khi lặp lại nó không?

Ví dụ:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

Ý tưởng là loại bỏ các yếu tố không đáp ứng một điều kiện nhất định khỏi từ điển, thay vì tạo một từ điển mới, đó là một tập hợp con của từ được lặp đi lặp lại.

Đây có phải là một giải pháp tốt? Có những cách thanh lịch / hiệu quả hơn?


1
Một câu hỏi liên quan với câu trả lời rất thú vị: stackoverflow.com/questions/9023078/ .
tối đa

Người ta có thể đã cố gắng dễ dàng. Nếu thất bại, nó không hợp pháp.
Trilarion

26
@Trilarion Một người có thể đã cố gắng dễ dàng ... và dễ dàng không học được gì về giá trị. Nếu nó thành công, nó không nhất thiết phải hợp pháp. Trường hợp cạnh và cảnh báo bất ngờ rất nhiều. Câu hỏi này là mối quan tâm không hề nhỏ đối với tất cả những người muốn trở thành Pythonistas. Vẫy tay vẫy tay theo lệnh "Người ta có thể thử dễ dàng!" là không có ích và trái với tinh thần tò mò của yêu cầu stackoverflow.
Cecil Curry

Sau khi xem xét kỹ maxcâu hỏi có liên quan , tôi phải đồng tình. Có lẽ bạn chỉ muốn xem qua câu hỏi sâu sắc và câu trả lời được viết tốt của nó. Tâm trí Pythonic của bạn sẽ được thổi.
Cecil Curry

1
@CecilCurry Thử nghiệm một ý tưởng cho chính mình trước khi trình bày nó ở đây là một loại tinh thần của stackoverflow nếu tôi không nhầm. Đó là tất cả những gì tôi muốn truyền đạt. Xin lỗi nếu có bất kỳ sự xáo trộn nào vì điều đó. Ngoài ra tôi nghĩ rằng đó là một câu hỏi hay và đã không đánh giá thấp nó. Tôi thích câu trả lời của Jochen Ritzel nhất. Tôi không nghĩ người ta cần phải làm tất cả những thứ cần xóa một cách nhanh chóng khi xóa trong bước thứ hai đơn giản hơn nhiều. Đó nên là cách ưa thích trong quan điểm của tôi.
Trilarion

Câu trả lời:


305

BIÊN TẬP:

Câu trả lời này sẽ không hoạt động cho Python3 và sẽ cung cấp cho RuntimeError.

RuntimeError: từ điển thay đổi kích thước trong quá trình lặp.

Điều này xảy ra bởi vì mydict.keys()trả về một iterator không phải là một danh sách. Như đã chỉ ra trong các bình luận chỉ cần chuyển đổi mydict.keys()thành một danh sách list(mydict.keys())và nó sẽ hoạt động.


Một thử nghiệm đơn giản trong bảng điều khiển cho thấy bạn không thể sửa đổi một từ điển trong khi lặp qua nó:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

Như đã nêu trong câu trả lời của delnan, việc xóa các mục gây ra vấn đề khi iterator cố gắng chuyển sang mục tiếp theo. Thay vào đó, sử dụng keys()phương thức để lấy danh sách các khóa và làm việc với nó:

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}

Nếu bạn cần xóa dựa trên giá trị vật phẩm, hãy sử dụng items()phương pháp thay thế:

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}

53
Lưu ý rằng trong Python 3, dict.items () trả về một iterator (và dict.iteritems () đã biến mất).
Tim Lesher

83
Để giải thích về nhận xét @TimLesher ... Điều này sẽ KHÔNG hoạt động trong Python 3.
tối đa

99
Để giải thích chi tiết về công phu của @ max, nó sẽ hoạt động nếu bạn chuyển đổi mã trên bằng 2to3. Một trong những trình sửa lỗi mặc định sẽ làm cho vòng lặp trông giống như for k, v in list(mydict.items()):hoạt động tốt trong Python 3. Tương tự để keys()trở thành list(keys()).
Walter Mundt

8
Điều này không hoạt động. Tôi gặp lỗi:RuntimeError: dictionary changed size during iteration
Tomáš Zato - Tái lập lại

15
@ TomášZato như Walter đã chỉ ra, đối với python3, bạn cần sử dụng for k in list(mydict.keys()): như python3 làm cho phương thức các khóa () trở thành một trình vòng lặp và cũng không cho phép xóa các mục chính tả trong quá trình lặp. Bằng cách thêm một danh sách () cuộc gọi, bạn biến các phím () iterator thành một danh sách. Vì vậy, khi bạn ở trong thân vòng lặp for, bạn không còn lặp lại từ điển.
Geoff Crompton

89

Bạn cũng có thể làm điều đó theo hai bước:

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

Cách tiếp cận yêu thích của tôi thường là chỉ đưa ra một lệnh mới:

# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)

11
@senderle: Kể từ 2.7 thực sự.
Jochen Ritzel

5
Cách tiếp cận hiểu chính tả làm cho một bản sao của từ điển; may mắn thay, các giá trị ít nhất không được sao chép sâu, chỉ cần liên kết. Tuy nhiên, nếu bạn có nhiều chìa khóa, nó có thể là xấu. Vì lý do đó, tôi thích removecách tiếp cận vòng lặp hơn.
tối đa

1
Bạn cũng có thể kết hợp các bước:for k in [k for k in mydict if k == val]: del mydict[k]
AXO

giải pháp đầu tiên là giải pháp hiệu quả duy nhất trên các đường dẫn lớn trong luồng này cho đến nay - vì nó không tạo ra một bản sao đầy đủ.
kxr

21

Bạn không thể sửa đổi một bộ sưu tập trong khi lặp nó. Đó là cách điên rồ - đáng chú ý nhất, nếu bạn được phép xóa và xóa mục hiện tại, trình lặp sẽ phải tiếp tục (+1) và cuộc gọi tiếp theo nextsẽ đưa bạn vượt qua điều đó (+2), vì vậy bạn cuối cùng bỏ qua một phần tử (phần tử ngay phía sau phần tử bạn đã xóa). Bạn có hai lựa chọn:

  • Sao chép tất cả các khóa (hoặc giá trị hoặc cả hai, tùy thuộc vào những gì bạn cần), sau đó lặp lại trên các khóa đó. Bạn có thể sử dụng .keys()et al cho việc này (trong Python 3, chuyển iterator kết quả cho list). Có thể rất lãng phí không gian khôn ngoan mặc dù.
  • Lặp lại mydictnhư bình thường, lưu các phím để xóa trong bộ sưu tập riêng biệt to_delete. Khi bạn hoàn thành việc lặp lại mydict, hãy xóa tất cả các mục to_deletetừ mydict. Lưu một số (tùy thuộc vào số lượng khóa bị xóa và số lần lưu lại) trong cách tiếp cận đầu tiên, nhưng cũng cần thêm một vài dòng.

You can't modify a collection while iterating it.Điều này chỉ đúng cho người dùng và bạn bè, nhưng bạn có thể sửa đổi danh sách trong khi lặp:L = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
Nils Lindemann

3
@Nils Nó không ném ngoại lệ nhưng vẫn không chính xác. Quan sát: codepad.org/Yz7rjDVT - xem ví dụ stackoverflow.com/q/6260089/395760 để được giải thích

Có tôi ở đây. Vẫn can'tchỉ đúng cho dict và bạn bè, trong khi nó nên shouldn'tdành cho danh sách.
Nils Lindemann

21

Thay vào đó, lặp lại một bản sao, chẳng hạn như bản được trả về bởi items():

for k, v in list(mydict.items()):

1
Điều đó không có ý nghĩa nhiều - sau đó bạn không thể del vtrực tiếp, vì vậy bạn đã tạo một bản sao của mỗi v mà bạn sẽ không bao giờ sử dụng và bạn phải truy cập các mục bằng mọi cách. dict.keys()là một lựa chọn tốt hơn.
jscs

2
@Josh: Tất cả phụ thuộc vào số tiền bạn sẽ cần sử dụng vlàm tiêu chí để xóa.
Ignacio Vazquez-Abrams

3
Trong Python 3, dict.items()trả về một iterator chứ không phải là một bản sao. Xem bình luận cho câu trả lời của Blair , trong đó (đáng buồn thay) cũng giả sử ngữ nghĩa Python 2.
Cecil Curry

11

Nó sạch nhất để sử dụng list(mydict):

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
{'four': 4, 'two': 2, 'one': 1}

Điều này tương ứng với một cấu trúc song song cho các danh sách:

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

Cả hai đều hoạt động trong python2 và python3.


Điều này không tốt trong trường hợp dữ liệu của bạn lớn. Đây là sao chép tất cả các đối tượng trong bộ nhớ, phải không?
AFP_555

1
@ AFP_555 Có - mục tiêu của tôi ở đây là mã pythonic sạch, song song. Nếu bạn cần hiệu quả bộ nhớ, cách tiếp cận tốt nhất mà tôi biết là lặp lại và xây dựng một danh sách các phím cần xóa hoặc một lệnh mới của các mục cần lưu. Làm đẹp là ưu tiên hàng đầu của tôi với Python; cho các bộ dữ liệu lớn tôi đang sử dụng Go hoặc Rust.
rsanden

9

Bạn có thể sử dụng một từ điển hiểu.

d = {k:d[k] for k in d if d[k] != val}


Đây là Pythonic nhất.
Yehosef

Nhưng nó tạo ra một từ điển mới thay vì sửa đổi dtại chỗ.
Aristide

9

Với python3, iterate trên dic.keys () sẽ tăng lỗi kích thước từ điển. Bạn có thể sử dụng cách thay thế này:

Đã thử nghiệm với python3, nó hoạt động tốt và lỗi " từ điển thay đổi kích thước trong khi lặp " không được nêu ra:

my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# {}

4

Trước tiên, bạn có thể tạo một danh sách các khóa cần xóa và sau đó lặp lại danh sách đó để xóa chúng.

dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]

Đây đúng là một bản sao của giải pháp thứ 1 của @ Ritzel (hiệu quả trên các bản sao lớn với bản sao đầy đủ). Mặc dù một "đọc dài" w / o danh sách hiểu. Tuy nhiên, nó có thể nhanh hơn?
kxr

3

Có một cách có thể phù hợp nếu các mục bạn muốn xóa luôn ở phần "bắt đầu" của phép lặp dict

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

"Bắt đầu" chỉ được đảm bảo phù hợp với các phiên bản / triển khai Python nhất định. Ví dụ từ những gì mới trong Python 3.7

bản chất bảo quản thứ tự chèn của các đối tượng dict đã được khai báo là một phần chính thức của đặc tả ngôn ngữ Python.

Cách này tránh được một bản sao của câu lệnh mà rất nhiều câu trả lời khác gợi ý, ít nhất là trong Python 3.


1

Tôi đã thử các giải pháp trên trong Python3 nhưng dường như đây là giải pháp duy nhất hoạt động với tôi khi lưu trữ các đối tượng trong một lệnh. Về cơ bản, bạn tạo một bản sao của dict () và lặp đi lặp lại trong khi xóa các mục trong từ điển gốc của bạn.

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
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.