Xóa nhiều khóa khỏi từ điển một cách an toàn


128

Tôi biết để xóa một mục nhập, 'khóa' khỏi từ điển của mình d, một cách an toàn, bạn thực hiện:

if d.has_key('key'):
    del d['key']

Tuy nhiên, tôi cần xóa nhiều mục từ từ điển một cách an toàn. Tôi đã nghĩ đến việc xác định các mục nhập trong một bộ tuple vì tôi sẽ cần phải làm điều này nhiều lần.

entitiesToREmove = ('a', 'b', 'c')
for x in entitiesToRemove:
    if d.has_key(x):
        del d[x]

Tuy nhiên, tôi đã tự hỏi liệu có cách nào thông minh hơn để làm điều này không?


3
Thời gian truy xuất từ ​​từ điển gần bằng O (1) vì quá trình băm. Trừ khi bạn đang loại bỏ một phần đáng kể các mục nhập, tôi không nghĩ rằng bạn sẽ làm tốt hơn nhiều.
ncmathsadist

1
Câu trả lời của @mattaticski có vẻ chính thống hơn và cũng ngắn gọn hơn.
Ioannis Filippidis

2
StackOverflow đã phán: key in dlà hơn Pythonic hơn d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper

Nếu bạn có thể dành một chút bộ nhớ, bạn có thể làm được for x in set(d) & entities_to_remove: del d[x]. Điều này có lẽ sẽ chỉ hiệu quả hơn nếu entities_to_removelà "lớn".
DylanYoung

Câu trả lời:


56

Tại sao không như thế này:

entries = ('a', 'b', 'c')
the_dict = {'b': 'foo'}

def entries_to_remove(entries, the_dict):
    for key in entries:
        if key in the_dict:
            del the_dict[key]

Một phiên bản nhỏ gọn hơn được cung cấp bởi mattldrenki sử dụng dict.pop ()


14
Thêm điều này cho những người đến từ một công cụ tìm kiếm. Nếu các khóa được xác định (khi an toàn không phải là vấn đề), nhiều khóa có thể bị xóa trong một dòng như thế nàydel dict['key1'], dict['key2'], dict['key3']
Tirtha R

Tùy thuộc vào số lượng khóa bạn đang xóa, việc sử dụng for key in set(the_dict) & entries:và bỏ qua key in dictkiểm tra có thể hiệu quả hơn .
DylanYoung

236
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)

38
Điều này. Đây là sự lựa chọn thông minh của Pythonista. dict.pop()loại bỏ nhu cầu kiểm tra sự tồn tại chính. Thông minh.
Cecil Curry

4
Đối với những gì nó đáng giá, tôi nghĩ .pop()là xấu và không phức tạp, và tôi muốn câu trả lời được chấp nhận hơn câu trả lời này.
Arne

5
Một số lượng người đáng kinh ngạc dường như không bị ảnh hưởng bởi điều này :) Tôi không bận tâm đến dòng bổ sung để kiểm tra sự tồn tại cá nhân, và nó dễ đọc hơn đáng kể trừ khi bạn đã biết về pop (). Mặt khác, nếu bạn đang cố gắng làm điều này ở dạng hiểu hoặc lambda nội tuyến, thủ thuật này có thể giúp ích rất nhiều. Tôi cũng muốn nói rằng điều quan trọng là gặp mọi người ở nơi họ đang ở. Tôi không chắc rằng "xấu và không nghe được" sẽ cung cấp cho những người đang đọc những câu trả lời này hướng dẫn thực tế mà họ đang tìm kiếm.
mattldrenki

5
Có một lý do đặc biệt tốt để sử dụng điều này. Mặc dù thêm một dòng bổ sung có thể cải thiện "khả năng đọc" hoặc "độ rõ ràng", nó cũng bổ sung thêm một tra cứu cho từ điển. Phương pháp này là loại bỏ tương đương với làm setdefault. Nếu được triển khai đúng cách (và tôi chắc chắn là như vậy), nó chỉ thực hiện một lần tra cứu vào bản đồ băm dict, thay vì hai.
Mad Physicist

2
Cá nhân tôi sẽ quan tâm đến tính đúng đắn và khả năng bảo trì trước tiên, và tốc độ chỉ khi nó được chứng minh là không đủ nhanh. Sự khác biệt về tốc độ giữa các hoạt động này sẽ rất nhỏ khi thu nhỏ đến mức ứng dụng. Có thể là trường hợp nhanh hơn, nhưng tôi hy vọng rằng trong việc sử dụng trong thế giới thực, bạn sẽ không để ý hay quan tâm, và nếu bạn để ý và quan tâm, bạn sẽ được phục vụ tốt hơn khi viết lại bằng thứ gì đó hiệu quả hơn Python.
mattldrenki

90

Sử dụng hiểu chính xác

final_dict = {key: t[key] for key in t if key not in [key1, key2]}

trong đó key1key2 sẽ bị xóa.

Trong ví dụ dưới đây, các phím "b" và "c" sẽ được xóa và nó được giữ trong danh sách khóa.

>>> a
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> keys = ["b", "c"]
>>> print {key: a[key] for key in a if key not in keys}
{'a': 1, 'd': 4}
>>> 

4
từ điển mới? danh sách hiểu? Bạn nên điều chỉnh câu trả lời cho người đặt ra câu hỏi;)
Glaslos

6
Giải pháp này có tác động nghiêm trọng đến hiệu suất khi biến giữ hàm được sử dụng nhiều hơn trong chương trình. Nói cách khác, một mệnh lệnh mà các khóa đã bị xóa sẽ hiệu quả hơn nhiều so với một mệnh lệnh mới được tạo với các mục được giữ lại.
Apalala

14
để dễ đọc, tôi đề xuất {k: v cho k, v trong t.items () nếu k không có trong [key1, key2]}
Frederic Bazin

8
Điều này cũng có vấn đề về hiệu suất khi danh sách các khóa quá lớn, khi tìm kiếm O(n). Toàn bộ hoạt động là O(mn), ở đâu mlà số khóa trong dict và nsố khóa trong danh sách. Tôi đề nghị sử dụng một bộ {key1, key2}thay thế, nếu có thể.
ldavid

4
Gửi Apalala: bạn có thể giúp tôi hiểu tại sao lại có một màn trình diễn ăn khách không?
Sean

21

một giải pháp đang sử dụng mapfiltercác chức năng

trăn 2

d={"a":1,"b":2,"c":3}
l=("a","b","d")
map(d.__delitem__, filter(d.__contains__,l))
print(d)

trăn 3

d={"a":1,"b":2,"c":3}
l=("a","b","d")
list(map(d.__delitem__, filter(d.__contains__,l)))
print(d)

bạn lấy:

{'c': 3}

Điều này không làm việc cho tôi với python 3.4:>>> d={"a":1,"b":2,"c":3} >>> l=("a","b","d") >>> map(d.__delitem__, filter(d.__contains__,l)) <map object at 0x10579b9e8> >>> print(d) {'a': 1, 'b': 2, 'c': 3}
Risadinha

@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... trong python chức năng 3.4 bản đồ trả về một iterator
Jose Ricardo Bustos M.

4
hoặc deque(map(...), maxlen=0)để tránh xây dựng một danh sách các giá trị Không có; lần nhập đầu tiên vớifrom collections import deque
Jason

19

Nếu bạn cũng cần truy xuất các giá trị cho các khóa bạn đang xóa, đây sẽ là một cách khá tốt để làm điều đó:

valuesRemoved = [d.pop(k, None) for k in entitiesToRemove]

Tất nhiên, bạn vẫn có thể làm điều này chỉ để xóa các khóa khỏi d, nhưng bạn sẽ không cần thiết phải tạo danh sách các giá trị với khả năng hiểu danh sách. Cũng hơi không rõ ràng khi sử dụng khả năng hiểu danh sách chỉ cho tác dụng phụ của hàm.


3
Hoặc nếu bạn muốn giữ các mục đã xóa làm từ điển: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) v.v.
kindall

Bạn có thể bỏ việc gán cho một biến. Theo cách này hay cách khác, đó là giải pháp ngắn nhất và khó nhất và nên được đánh dấu là câu trả lời đúng IMHO.
Gerhard Hagerer,

12

Đã tìm ra giải pháp với popmap

d = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'b', 'c']
list(map(d.pop, keys))
print(d)

Đầu ra của cái này:

{'d': 'valueD'}

Tôi đã trả lời câu hỏi này quá muộn chỉ vì tôi nghĩ rằng nó sẽ hữu ích trong tương lai nếu có ai đó tìm kiếm tương tự. Và điều này có thể hữu ích.

Cập nhật

Đoạn mã trên sẽ xuất hiện một lỗi nếu một khóa không tồn tại trong dict.

DICTIONARY = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'l', 'c']

def remove_keys(key):
    try:
        DICTIONARY.pop(key, None)
    except:
        pass  # or do any action

list(map(remove_key, keys))
print(DICTIONARY)

đầu ra:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}

1
Câu trả lời này sẽ đưa ra một ngoại lệ nếu bất kỳ khóa nào trong keyskhông tồn tại d- bạn sẽ phải lọc khóa đó trước.
ingofreyer

@ingofreyer đã cập nhật mã để xử lý ngoại lệ. Cảm ơn vì đã tìm ra vấn đề này. Tôi nghĩ bây giờ nó sẽ hoạt động. :)
Shubham Srivastava

Cảm ơn, điều này sẽ giúp mọi người tìm thấy câu trả lời này :-)
ingofreyer.

Tạo một danh sách như một sản phẩm phụ của việc sử dụng bản đồ, khiến việc này khá chậm, thực sự tốt hơn là lặp lại nó.
Charlie Clark

4

Tôi không có vấn đề với bất kỳ câu trả lời hiện có nào, nhưng tôi rất ngạc nhiên khi không tìm thấy giải pháp này:

keys_to_remove = ['a', 'b', 'c']
my_dict = {k: v for k, v in zip("a b c d e f g".split(' '), [0, 1, 2, 3, 4, 5, 6])}

for k in keys_to_remove:
    try:
        del my_dict[k]
    except KeyError:
        pass

assert my_dict == {'d': 3, 'e': 4, 'f': 5, 'g': 6}

Lưu ý: Tôi tình cờ gặp câu hỏi này đến từ đây . Và câu trả lời của tôi có liên quan đến câu trả lời này .


3

Tại sao không:

entriestoremove = (2,5,1)
for e in entriestoremove:
    if d.has_key(e):
        del d[e]

Tôi không biết ý bạn là "cách thông minh hơn". Chắc chắn có những cách khác, có thể với cách hiểu từ điển:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}

2

nội tuyến

import functools

#: not key(c) in d
d = {"a": "avalue", "b": "bvalue", "d": "dvalue"}

entitiesToREmove = ('a', 'b', 'c')

#: python2
map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove)

#: python3

list(map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove))

print(d)
# output: {'d': 'dvalue'}

2

Một số bài kiểm tra thời gian cho cpython 3 cho thấy rằng một vòng lặp for đơn giản là cách nhanh nhất và nó khá dễ đọc. Thêm một hàm cũng không gây ra nhiều chi phí:

kết quả thời gian (10k lần lặp):

  • all(x.pop(v) for v in r) # 0.85
  • all(map(x.pop, r)) # 0.60
  • list(map(x.pop, r)) # 0.70
  • all(map(x.__delitem__, r)) # 0.44
  • del_all(x, r) # 0.40
  • <inline for loop>(x, r) # 0.35
def del_all(mapping, to_remove):
      """Remove list of elements from mapping."""
      for key in to_remove:
          del mapping[key]

Đối với các lần lặp nhỏ, thực hiện 'nội tuyến' đó nhanh hơn một chút, do chi phí của lệnh gọi hàm. Nhưng del_alllint an toàn, có thể tái sử dụng và nhanh hơn tất cả các cấu trúc lập bản đồ và hiểu python.


0

Tôi nghĩ rằng sử dụng thực tế rằng các khóa có thể được coi là một tập hợp là cách tốt nhất nếu bạn đang sử dụng python 3:

def remove_keys(d, keys):
    to_remove = set(keys)
    filtered_keys = d.keys() - to_remove
    filtered_values = map(d.get, filtered_keys)
    return dict(zip(filtered_keys, filtered_values))

Thí dụ:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}

0

Sẽ rất tuyệt nếu có hỗ trợ đầy đủ cho các phương thức thiết lập cho từ điển (và không phải là mớ hỗn độn đáng ghét mà chúng tôi đang gặp phải với Python 3.9) để bạn có thể chỉ cần "loại bỏ" một bộ khóa. Tuy nhiên, miễn là không phải như vậy và bạn có một từ điển lớn với số lượng lớn các khóa cần xóa, bạn có thể muốn biết về hiệu suất. Vì vậy, tôi đã tạo một số mã tạo ra thứ gì đó đủ lớn để so sánh có ý nghĩa: ma trận 100.000 x 1000, tổng cộng 10.000.00 mục.

from itertools import product
from time import perf_counter

# make a complete worksheet 100000 * 1000
start = perf_counter()
prod = product(range(1, 100000), range(1, 1000))
cells = {(x,y):x for x,y in prod}
print(len(cells))

print(f"Create time {perf_counter()-start:.2f}s")
clock = perf_counter()
# remove everything above row 50,000

keys = product(range(50000, 100000), range(1, 100))

# for x,y in keys:
#     del cells[x, y]

for n in map(cells.pop, keys):
    pass

print(len(cells))
stop = perf_counter()
print(f"Removal time {stop-clock:.2f}s")

10 triệu mặt hàng trở lên không phải là bất thường trong một số cài đặt. So sánh hai phương pháp trên máy cục bộ của tôi, tôi thấy có một chút cải thiện khi sử dụng mappopcó lẽ là do ít lệnh gọi hàm hơn, nhưng cả hai đều mất khoảng 2,5 giây trên máy của tôi. Nhưng điều này nhạt nhẽo so với thời gian cần thiết để tạo từ điển ngay từ đầu (55 giây), hoặc bao gồm các kiểm tra trong vòng lặp. Nếu điều này có thể xảy ra thì tốt nhất bạn nên tạo một tập hợp là giao điểm của các khóa từ điển và bộ lọc của bạn:

keys = cells.keys() & keys

Tóm lại: delđã được tối ưu hóa rất nhiều, vì vậy đừng lo lắng về việc sử dụng nó.


-1

Tôi đến muộn với cuộc thảo luận này nhưng cho bất kỳ ai khác. Một giải pháp có thể là tạo một danh sách các khóa như vậy.

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

Sau đó, sử dụng pop () trong một danh sách hiểu, hoặc vòng lặp for, để lặp lại các phím và bật từng phím một.

new_dictionary = [dictionary.pop(x, 'n/a') for x in k]

'N / a' là trong trường hợp khóa không tồn tại, giá trị mặc định cần được trả về.


8
new_dictionarytrông giống một danh sách khủng khiếp;)
DylanYoung
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.