Видаліть елемент зі словника


1392

Чи є спосіб видалити елемент із словника в Python?

Крім того, як я можу видалити елемент зі словника, щоб повернути його копію (тобто, не змінюючи оригінал)?


13
Навіщо потрібна функція, яка повертає словник, коли ви можете просто змінити словник безпосередньо?
amillerrhodes

5
Словник popметод змінює словник на місці . Тому він змінює посилання на словник, який був переданий від абонента до "помічникової функції". Тож "хелперній функції" нічого не потрібно повертати, оскільки вихідні посилання на словник у абонента вже будуть змінені. Не призначайте повернення dict.pop()ні до чого, якщо воно вам не потрібно. EG: do stuff with my_dict; my_dict.pop(my_key, None); do more stuff with my_dict # now doesn't have my_key. Використовуйте deepcopy(my_dict)за потреби.
Марк Мікофскі

1
Оскільки оригінальний заголовок не погоджувався з деталями і спеціально виключав очевидне рішення d.pop(), я зафіксував заголовок, щоб задати питання, вказане в деталях.
smci

1
Ми повинні додати застереження, запитуючи, чи дійсно ви хочете це зробити, як якщо б ви це зробили N разів у словнику з елементами E, ви просочите (/ використовуєте) O (N * E) пам'ять з усіма глибокими копіями. Якщо ви просто хочете лише для читання (неглибока копія), зробіть це d.pop(key). Але якщо що-небудь коли-небудь модифікує дрібну копію, у вас є відома проблема з псевдонімом . Це допомагає, якщо ви розкажете нам про ширший контекст. (Чи щось ще колись змінює значення dict? Ви намагаєтесь деструктивно повторити список? Якщо ні, то що?)
smci

5
"Навіщо вам потрібна функція, яка повертає словник, коли ви можете просто змінити словник безпосередньо?" Можливо, тому, що ви хочете записати чисті функції, які не змінюють своїх параметрів?
Джин Каллахан

Відповіді:


1726

delОператор видаляє елемент:

del d[key]

Однак це мутує існуючий словник, тому зміст словника змінюється для всіх інших, хто має посилання на той самий екземпляр. Щоб повернути новий словник, зробіть копію словника:

def removekey(d, key):
    r = dict(d)
    del r[key]
    return r

dict()Конструктор робить неповну копію . Щоб зробити глибоку копію, див. copyМодуль .


Зауважте, що копія для кожного диктату del/ завдання / тощо. означає, що ви переходите від постійного часу до лінійного часу, а також використовуєте лінійний простір. Для невеликих диктів це не проблема. Але якщо ви плануєте робити безліч копій великих диктів, ви, мабуть, хочете іншої структури даних, як HAMT (як описано в цій відповіді ).


15
це чудовий момент щодо незмінності словників +1 - хоча я не можу придумати час, коли мені хотілося копії словника, я завжди покладався на те, що копія "everyones" є однаковою. чудовий момент.
tMC

30
@tMC Якщо ви відредагуєте так, dictяк ви прокручуєте його, це RuntimeError: dictionary changed size during iteration
призведе

15
Що з popметодом, який насправді робить те саме? Хіба це не більше пітонічно? (будучи методом диктату, а не спеціальним зарезервованим словом)?
Серж

21
Ця відповідь має слабку сторону, вона може ввести в оману. Читачі можуть неправильно зрозуміти, що dict (d) може надати їм копію з "d". Але це неповна копія. Коли ви робите лише операції з клавішами дель, це добре. Але коли ви хочете зробити щось інше для вкладеного дикта, зміна 'r' за допомогою цього методу копіювання може призвести до зміни оригіналу 'd'. Щоб отримати автентичну копію, потрібно спочатку "імпортувати копію", а потім "r = copy.deepcopy (d)".
Дзен

10
@Zen: Досить справедливо, я додав замітку про дрібну та глибоку копію.
Грег Хьюгілл

264

pop вимкнює словник.

 >>> lol = {"hello": "gdbye"}
 >>> lol.pop("hello")
     'gdbye'
 >>> lol
     {}

Якщо ви хочете зберегти оригінал, можете просто скопіювати його.


29
"del" гаразд, але "поп" здається більш "пітонічним", на мій погляд.
ivanleoncz

1
@ivanleoncz чому?
кевр

3
popповертає значення, яке було 'popped', що дозволяє використовувати це значення з будь-якої подальшої причини. Якщо це не більше "піфонічне", я б сказав, що здається краще, точно :). Це не дикт, але він працює однаково для обох: github.com/ivanlmj/python-prototypes/blob/master/3.4/…
ivanleoncz

2
@ivanleoncz Це також краще з ще однієї причини, popможе надати значення за замовчуванням, яке буде повернуто, коли ключ відсутній у dict. Добре, коли вам потрібно видалити кілька клавіш, але деякі з них можуть бути відсутніми; delкинув би KeyErrorв такому випадку.
itachi

80

Я думаю, що ваше рішення найкращий спосіб зробити це. Але якщо ви хочете іншого рішення, ви можете створити новий словник за допомогою клавіш із старого словника, не включаючи вказаний ключ, наприклад, такий:

>>> a
{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> {i:a[i] for i in a if i!=0}
{1: 'one', 2: 'two', 3: 'three'}

2
Дійсно здорово. Мені подобається швидкий метод фільтрації словника без визначення нової функції.
Джо Дж.

6
Для тих, хто не знайомий із розуміннями, ви також можете зробити щось подібне: {i:a[i] for i in a if i not in [0, 1, 2]}якщо ви хочете видалити кілька елементів.
kmatheny

9
Краще було б, {k:v for k,v in a.items() if k != 0}думаю.
rlbond

1
Найкраще рішення для видалення елемента за ключем та повернення результату нового диктату в тому ж рядку. Наприклад, якщо вам потрібно використовувати вже побудований диктант без жодного елемента **kwargs,some_function(**{k:v for k,v in some_dict.items() if k not 'some_key'})
Коул

Тут найкраще рішення. Один вкладиш, і він не мутує початковий словник.
Ендрю Вінтерботам

55

Заява del - це те, що ви шукаєте. Якщо у вас є словник з ім'ям foo з ключем під назвою "bar", ви можете видалити "bar" з foo таким чином:

del foo['bar']

Зауважте, що це постійно змінює словник, яким керується. Якщо ви хочете зберегти оригінальний словник, вам доведеться заздалегідь створити його копію:

>>> foo = {'bar': 'baz'}
>>> fu = dict(foo)
>>> del foo['bar']
>>> print foo
{}
>>> print fu
{'bar': 'baz'}

dictВиклик робить неповну копію. Якщо ви хочете глибоку копію, використовуйте copy.deepcopy.

Ось метод, який ви можете скопіювати та вставити для вашої зручності:

def minus_key(key, dictionary):
    shallow_copy = dict(dictionary)
    del shallow_copy[key]
    return shallow_copy

1
@ pythonian29033, власне, ні . Прийнята відповідь працює як очікується - вона повертає дікт без одного ключа. Підхід з цієї відповіді мутує оригінальний вислів;) Є істотна різниця
maxkoryukov

1
@ arussell84, чому >>>часто використовується в прикладах python? Так, python-doc містить багато таких речей. Але такий код не зручний для copypaste . Я розгублений ...
maxkoryukov

@maxkoryukov yup це! але ця функція і ця відповідь точно однакова, за винятком того, що відповідь знаходиться всередині функції. і ви, мабуть, не кодували пітон на певний час, >>>імітує нотацію прослуховування з python у режимі cli
pythonian29033

2
@ pythonian29033 о >>>. Так, це стиль REPL, але давайте поговоримо відверто: єдиний чоловік написав цей зразок, а 1000 прочитали це. Думаю, було б чудово писати приклади таким чином, щоб легко копіювати та запускати. Мені не подобається знімати цей кутовий кронштейн вручну. Або скопіювати рядок за рядком .. Тож я не розумію: чому цей кут все ще є))) Можливо, я щось не знаю?
Макскорюков

3
Для вашої зручності я додав функцію, яку можна скопіювати / вставити.
arussell84

48

Приємних відповідей дуже багато, але я хочу наголосити на одному.

Для видалення елементів зі словника ви можете використовувати як dict.pop()метод, так і більш загальний delвислів . Вони обидва мутують оригінальний словник, тому вам потрібно зробити копію (див. Деталі нижче).

І обидва вони піднімуть a, KeyErrorякщо ключ, який ви їм надаєте, відсутній у словнику:

key_to_remove = "c"
d = {"a": 1, "b": 2}
del d[key_to_remove]  # Raises `KeyError: 'c'`

і

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove)  # Raises `KeyError: 'c'`

Ви повинні подбати про це:

зафіксувавши виняток:

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    del d[key_to_remove]
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

і

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    d.pop(key_to_remove)
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

виконавши перевірку:

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    del d[key_to_remove]

і

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    d.pop(key_to_remove)

але pop()також існує набагато більш стислий спосіб - надати повернене значення за замовчуванням:

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove, None)  # No `KeyError` here

Якщо ви не використовуєте pop()для отримання значення ключа, що вилучається, ви можете надати що-небудь, не потрібне None. Хоча може бути, що використання delз inперевіркою трохи швидше через pop()функціонування з власними ускладненнями, що спричиняють накладні витрати. Зазвичай це не так, тому pop()значення за замовчуванням досить добре.


Що стосується головного питання, вам доведеться зробити копію свого словника, зберегти оригінальний словник і створити новий, не видаляючи ключ.

Деякі інші люди пропонують зробити повну (глибоку) копію copy.deepcopy(), яка може бути надмірною, "нормальною" (неглибокою) копією, використовуючи copy.copy()або dict.copy(), можливо, достатньо. Словник зберігає посилання на об'єкт як значення для ключа. Отже, коли ви виймаєте ключ із словника, ця посилання видаляється, а не об'єкт, на який посилається. Сам об’єкт пізніше може бути автоматично видалений сміттєзбірником, якщо в ньому немає інших посилань на нього. Створення глибокої копії вимагає більше обчислень порівняно з дрібною копією, тому вона знижує продуктивність коду, роблячи копію, витрачаючи пам’ять та надаючи більше роботи в GC, іноді досить дрібної копії.

Однак якщо у вас є змінні об'єкти як значення словника і плануєте їх змінити пізніше у поверненому словнику без ключа, вам доведеться зробити глибоку копію.

З дрібною копією:

def get_dict_wo_key(dictionary, key):
    """Returns a **shallow** copy of the dictionary without a key."""
    _dict = dictionary.copy()
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}

З глибокою копією:

from copy import deepcopy


def get_dict_wo_key(dictionary, key):
    """Returns a **deep** copy of the dictionary without a key."""
    _dict = deepcopy(dictionary)
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}

20

… Як я можу видалити елемент зі словника, щоб повернути його копію (тобто, не змінюючи оригінал)?

A dict- неправильна структура даних, яку слід використовувати для цього.

Звичайно, копіювання диктату і вискакування з копії працює, і це означає, що створення нового диктату з розумінням, але все, на що копіювання вимагає часу - ви замінили операцію постійного часу на лінійну. І всі ці копії, що оживають одразу, займають місце - лінійний простір на копію.

Інші структури даних, як-от спроби зіставлення хеш-масиву , розроблені саме для такого випадку використання: додавання або видалення елемента повертає копію в логарифмічний час, обмінюючи більшу частину її сховища з оригіналом . 1

Звичайно, є і деякі недоліки. Продуктивність є логарифмічною, а не постійною (хоча з великою основою, як правило, 32-128). І хоча ви можете зробити API, що не мутує, ідентичний API dict, "мутуючий" API, очевидно, відрізняється. І, більш за все, немає ніяких батарей HAMT, що входять до Python. 2

pyrsistentБібліотека являє собою досить твердий реалізація HAMT на основі Dict-замінників (і різних інших типів) для Python. Він навіть має чудовий API evolver для максимально плавної передачі існуючого мутуючого коду до стійкого коду. Але якщо ви хочете чітко розповісти про повернення копій, а не про мутування, просто використовуйте це так:

>>> from pyrsistent import m
>>> d1 = m(a=1, b=2)
>>> d2 = d1.set('c', 3)
>>> d3 = d1.remove('a')
>>> d1
pmap({'a': 1, 'b': 2})
>>> d2
pmap({'c': 3, 'a': 1, 'b': 2})
>>> d3
pmap({'b': 2})

Саме про d3 = d1.remove('a')це і задається питання.

Якщо у вас є вбудовані структури даних на зразок dictта listвбудовані в pmap, у вас все ще будуть виникати проблеми, які можна вирішити, ви можете це виправити лише просунувшись непорушно, вбудовуючи pmaps і pvectors.


1. HAMT також стали популярними в таких мовах, як Scala, Clojure, Haskell, тому що вони дуже добре грають з програмуванням без блокування та транзакційною пам'яттю програмного забезпечення, але жодна з них не дуже актуальна в Python.

2. Насправді, це HAMT в STDLIB, використовуваних в реалізації contextvars. Раніше відкликаний ПЕП пояснює, чому. Але це прихована деталь реалізації бібліотеки, а не загальнодоступна колекція.



14

Просто зателефонуйте del d ['key'].

Однак у виробництві завжди корисно перевірити, чи існує "ключ" у d.

if 'key' in d:
    del d['key']

7
Хм, ні, у виробництві краще слідувати ідеології EAFP . Просто видаліть ключ у try-exceptблоці. Принаймні, це буде атомна операція;)
maxkoryukov

1
А якщо ви хочете бути лаконічними - використовуйте d.pop('key', None), це oneliner. Але власне питання полягало в тому, щоб отримати словник без одного ключа, а не про зміну дикту. Тож розуміння - тут хороший вибір;)
maxkoryukov

7

Ні, іншого шляху немає

def dictMinus(dct, val):
   copy = dct.copy()
   del copy[val]
   return copy

Однак часто створювати копії лише незначно змінених словників, мабуть, не є хорошою ідеєю, оскільки це призведе до порівняно великих вимог до пам'яті. Зазвичай краще записати старий словник (якщо він навіть необхідний), а потім змінити його.


7
# mutate/remove with a default
ret_val = body.pop('key', 5)
# no mutation with a default
ret_val = body.get('key', 5)

5
>>> def delete_key(dict, key):
...     del dict[key]
...     return dict
... 
>>> test_dict = {'one': 1, 'two' : 2}
>>> print delete_key(test_dict, 'two')
{'one': 1}
>>>

це не робить ніяких помилок, він передбачає, що ключ знаходиться в диктаті, ви можете перевірити це спочатку, і raiseякщо його немає


10
Чим ваш метод відрізняється від просто del test_dict[key]?
Божевільний фізик

5

Ось проектний підхід верхнього рівня:

def eraseElement(d,k):
    if isinstance(d, dict):
        if k in d:
            d.pop(k)
            print(d)
        else:
            print("Cannot find matching key")
    else:
        print("Not able to delete")


exp = {'A':34, 'B':55, 'C':87}
eraseElement(exp, 'C')

Я передаю словник і ключ, який я хочу, у своїй функції, перевіряє, чи це словник, і якщо ключ добре, і якщо обидва існують, видаляє значення зі словника і виводить зліва ліві слова.

Вихід: {'B': 55, 'A': 34}

Сподіваюся, що це допомагає!


3

Нижче фрагмент коду вам точно допоможе, я додав коментарі до кожного рядка, які допоможуть вам зрозуміти код.

def execute():
   dic = {'a':1,'b':2}
   dic2 = remove_key_from_dict(dic, 'b')  
   print(dict2)           # {'a': 1}
   print(dict)            # {'a':1,'b':2}

def remove_key_from_dict(dictionary_to_use, key_to_delete):
   copy_of_dict = dict(dictionary_to_use)     # creating clone/copy of the dictionary
   if key_to_delete in copy_of_dict :         # checking given key is present in the dictionary
       del copy_of_dict [key_to_delete]       # deleting the key from the dictionary 
   return copy_of_dict                        # returning the final dictionary

або ви також можете використовувати dict.pop ()

d = {"a": 1, "b": 2}

res = d.pop("c")  # No `KeyError` here
print (res)       # this line will not execute

або кращий підхід

res = d.pop("c", "key not found")
print (res)   # key not found
print (d)     # {"a": 1, "b": 2}

res = d.pop("b", "key not found")
print (res)   # 2
print (d)     # {"a": 1}

2

Ось ще один варіант із використанням розуміння списку:

original_d = {'a': None, 'b': 'Some'}
d = dict((k,v) for k, v in original_d.iteritems() if v)
# result should be {'b': 'Some'}

Підхід заснований на відповіді з цього допису: Ефективний спосіб видалення ключів із порожніми рядками з диктату


1
Якщо ви збираєтесь відповісти на багаторічне запитання, на яке вже є проста, відповідна, прийнята відповідь, принаймні переконайтесь, що ваша відповідь правильна. Це не робить те, про що вимагала ОП.
user2357112 підтримує Моніку

Я, як правило, не перевіряю дати на питання, які, на мою думку, могли б до них додавати цінну інформацію. Крім того, на один із коментарів до питання, до якого я посилався: "Зазвичай це саме те, що хтось хоче, і, мабуть, те, що потрібно ОП, але це не те, про що вимагала ОП" stackoverflow.com/questions/12118695/… я знав, що це не пряма відповідь на питання; скоріше розширення варіантів.
BigBlueHat

3
Ця відповідь, хоч і не повна, дає нам змогу дізнатися, що ми можемо видаляти предмети, якщо теж є умови. просто змінюється if vна if k is not 'a'відповіді оп. Але я не думаю, що це ефективний спосіб, це видаляє елемент у O (n), а не O (log n), як це роблять pop або del.
holgac

0
    species = {'HI': {'1': (1215.671, 0.41600000000000004),
  '10': (919.351, 0.0012),
  '1025': (1025.722, 0.0791),
  '11': (918.129, 0.0009199999999999999),
  '12': (917.181, 0.000723),
  '1215': (1215.671, 0.41600000000000004),
  '13': (916.429, 0.0005769999999999999),
  '14': (915.824, 0.000468),
  '15': (915.329, 0.00038500000000000003),
 'CII': {'1036': (1036.3367, 0.11900000000000001), '1334': (1334.532, 0.129)}}

У наведеному нижче коді буде зроблено копію dict speciesта видалено елементи, у яких немаєtrans_HI

trans_HI=['1025','1215']
for transition in species['HI'].copy().keys():
    if transition not in trans_HI:
        species['HI'].pop(transition)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.