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


116

Я маю дікт і хотів би видалити всі ключі, для яких є порожні рядки значень.

metadata = {u'Composite:PreviewImage': u'(Binary data 101973 bytes)',
            u'EXIF:CFAPattern2': u''}

Який найкращий спосіб зробити це?

Відповіді:


194

Python 2.X

dict((k, v) for k, v in metadata.iteritems() if v)

Python 2.7 - 3.X

{k: v for k, v in metadata.items() if v is not None}

Зауважте, що всі ваші ключі мають значення. Просто деякі з цих значень є порожнім рядком. Немає такої речі, як ключ у диктаті без значення; якби вона не мала значення, вона не була б у дікті.


29
+1. Важливо зазначити, що це фактично не видаляє ключі з наявного словника. Швидше, це створює новий словник. Зазвичай це саме те, що хтось хоче, і це, мабуть, те, що потрібно ОП, але це не те, про що вимагала ОП.
Стівен Румбальський

18
Це також вбиває v = 0, що добре, якщо це те, що потрібно.
Павло

2
Це також позбавляє v = хибне, що не зовсім те , що запитував ОП.
Амір

4
@shredding: Ти маєш на увазі .items().
BrenBarn

6
Для пізніших версій python слід також скористатися генератором словників:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

Це може бути навіть коротшим, ніж рішення BrenBarn (і я вважаю, що читабельніше)

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

Тестовано з Python 2.7.3.


13
Це також вбиває нульові значення.
Поль

10
Щоб зберегти 0 (нуль), ви можете використовувати ... if v!=Noneтак: {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v для k, v в metadata.items (), якщо v! = None} не позбудеться порожніх рядків.
philgo20

1
Зрозуміння словника підтримується лише з Python 2.7+ для сумісності з попередніми версіями, будь ласка, використовуйте рішення @ BrenBarn.
Pavan Gupta

12
Завжди слід порівнювати None з ", ні", а не "! =". stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Якщо вам дійсно потрібно змінити оригінальний словник:

empty_keys = [k for k,v in metadata.iteritems() if not v]
for k in empty_keys:
    del metadata[k]

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


це також видалить значення 0 і 0 не буде порожнім
JVK

2
Якщо ви використовуєте Python 3 + ви повинні замінити .iteritems()з .items(), перший більше не працює в останніх версіях Python.
Маріано Руїс


12

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

Після pip install boltonsабо скопіювавши iterutils.py у свій проект, просто виконайте:

from boltons.iterutils import remap

drop_falsey = lambda path, key, value: bool(value)
clean = remap(metadata, visit=drop_falsey)

На цій сторінці є ще багато прикладів, включаючи ті, що працюють із значно більшими об’єктами з API Github.

Це pure-Python, тому він працює скрізь і повністю перевірений у Python 2.7 та 3.3+. Найкраще, що я написав це саме для таких випадків, тож якщо ви знайдете справу, з якою вона не справляється, ви можете помилити мене, щоб виправити це тут .


1
Це рішення чудово спрацювало для подібної проблеми, яку я мав: вилучення порожніх значень із глибоко вкладених списків всередині словників. Дякую!
Микола Тулач

1
Це добре, оскільки ви не винаходите колесо, а пропонуєте рішення для вкладених об'єктів. Дякую!
векердиб

1
Мені дуже сподобалася стаття, яку ви написали для своєї бібліотеки, і це корисна бібліотека!
lifelogger

11

На основі рішення Райана , якщо у вас також є списки та вкладені словники:

Для Python 2:

def remove_empty_from_dict(d):
    if type(d) is dict:
        return dict((k, remove_empty_from_dict(v)) for k, v in d.iteritems() if v and remove_empty_from_dict(v))
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
    else:
        return d

Для Python 3:

def remove_empty_from_dict(d):
    if type(d) is dict:
        return dict((k, remove_empty_from_dict(v)) for k, v in d.items() if v and remove_empty_from_dict(v))
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
    else:
        return d

1
Ха, приємне продовження! Це гарне рішення для таких словників:d = { "things": [{ "name": "" }] }
Ryan Shea

6

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

def scrub_dict(d):
    if type(d) is dict:
        return dict((k, scrub_dict(v)) for k, v in d.iteritems() if v and scrub_dict(v))
    else:
        return d

Використовуйте items()замість iteritems()Python 3
andydavies

6

Швидкий відповідь (TL; DR)

Приклад01

### example01 -------------------

mydict  =   { "alpha":0,
              "bravo":"0",
              "charlie":"three",
              "delta":[],
              "echo":False,
              "foxy":"False",
              "golf":"",
              "hotel":"   ",                        
            }
newdict =   dict([(vkey, vdata) for vkey, vdata in mydict.iteritems() if(vdata) ])
print newdict

### result01 -------------------
result01 ='''
{'foxy': 'False', 'charlie': 'three', 'bravo': '0'}
'''

Детальний відповідь

Проблема

  • Контекст: Python 2.x
  • Сценарій: Розробник бажає змінити словник, щоб виключити порожні значення
    • aka видалити порожні значення зі словника
    • aka видалити ключі з порожніми значеннями
    • aka словник фільтра для порожніх значень для кожної пари ключ-значення

Рішення

  • example01 використовувати синтаксис розуміння списку python з простим умовним для видалення "порожніх" значень

Підводні камені

  • example01 працює лише над копією оригінального словника (не змінюється на місці)
  • example01 може отримати несподівані результати залежно від того, що розробник означає "порожній"
    • Чи має на увазі розробник зберігати помилкові значення ?
    • Якщо значення в словнику не гарантовано є рядками, розробник може мати несподівані втрати даних.
    • Результат01 показує, що від початкового набору збереглися лише три пари ключ-значення

Черговий приклад

  • example02 допомагає боротися з потенційними підводними каменями
  • Підхід полягає у використанні більш точного визначення поняття "порожній" шляхом зміни умовного.
  • Тут ми хочемо лише відфільтрувати значення, які оцінюються, до порожніх рядків.
  • Тут ми також використовуємо .strip () для фільтрації значень, що складаються лише з пробілів.

Приклад02

### example02 -------------------

mydict  =   { "alpha":0,
              "bravo":"0",
              "charlie":"three",
              "delta":[],
              "echo":False,
              "foxy":"False",
              "golf":"",
              "hotel":"   ",
            }
newdict =   dict([(vkey, vdata) for vkey, vdata in mydict.iteritems() if(str(vdata).strip()) ])
print newdict

### result02 -------------------
result02 ='''
{'alpha': 0,
  'bravo': '0', 
  'charlie': 'three', 
  'delta': [],
  'echo': False,
  'foxy': 'False'
  }
'''

Дивитися також



4

Спираючись на відповіді patriciasz та nneonneo , і враховуючи можливість того, що ви можете видалити ключі, у яких є лише певні хибні речі (наприклад ''), але не інші (наприклад 0), або, можливо, ви навіть хочете включити деякі неприємні речі (наприклад 'SPAM') , тоді ви можете скласти чітко визначений список звернень:

unwanted = ['', u'', None, False, [], 'SPAM']

На жаль, це не зовсім працює, тому що, наприклад, 0 in unwantedоцінює True. Нам потрібно розрізняти 0і інші хибні речі, тому ми повинні використовувати is:

any([0 is i for i in unwanted])

... оцінює до False.

Тепер використовуйте його для delнебажаних речей:

unwanted_keys = [k for k, v in metadata.items() if any([v is i for i in unwanted])]
for k in unwanted_keys: del metadata[k]

Якщо ви хочете новий словник, замість зміни metadataна місці:

newdict = {k: v for k, v in metadata.items() if not any([v is i for i in unwanted])}

дійсно приємний знімок, він вирішує багато проблем відразу, і це вирішує питання, дякую, щоб було зрозуміло
jlandercy

Класно! Це працює для цього прикладу. Однак він не працює, коли елемент у словнику[]
jsga

2

Я читаю всі відповіді в цій темі, а деякі посилаються також на цю тему: Видаліть порожні дикти в вкладеному словнику з рекурсивною функцією

Я спочатку використовував тут рішення, і воно спрацювало чудово:

Спроба 1: Занадто гаряча (не є виконавіцею чи майбутнім) :

def scrub_dict(d):
    if type(d) is dict:
        return dict((k, scrub_dict(v)) for k, v in d.iteritems() if v and scrub_dict(v))
    else:
        return d

Але деякі проблеми щодо продуктивності та сумісності були підняті у світі Python 2.7:

  1. використовувати isinstanceзамістьtype
  2. розгорніть список списку в forцикл для ефективності
  3. використовуйте безпечний python3 itemsзамістьiteritems

Спроба 2: Занадто холодно (не вистачає пам'яті) :

def scrub_dict(d):
    new_dict = {}
    for k, v in d.items():
        if isinstance(v,dict):
            v = scrub_dict(v)
        if not v in (u'', None, {}):
            new_dict[k] = v
    return new_dict

ДОХ! Це не рекурсивно і зовсім не запам'ятовується.

Спроба 3: Якраз правильно (поки що) :

def scrub_dict(d):
    new_dict = {}
    for k, v in d.items():
        if isinstance(v,dict):
            v = scrub_dict(v)
        if not v in (u'', None, {}):
            new_dict[k] = v
    return new_dict

1
якщо я не сліпий, мені здається, що спроби 2 і 3 точно такі ж ...
luckyguy73

1

Дікти змішані з масивами

  • Відповідь у спробі 3: Тільки правильно (поки що) з відповіді BlissRage не належним чином обробляє елементи масивів. Я включаю патч у випадку, якщо комусь це потрібно. Метод обробляє список з блоком оператора of if isinstance(v, list):, який скрупує список за допомогою оригінальної scrub_dict(d)реалізації.
    @staticmethod
    def scrub_dict(d):
        new_dict = {}
        for k, v in d.items():
            if isinstance(v, dict):
                v = scrub_dict(v)
            if isinstance(v, list):
                v = scrub_list(v)
            if not v in (u'', None, {}):
                new_dict[k] = v
        return new_dict

    @staticmethod
    def scrub_list(d):
        scrubbed_list = []
        for i in d:
            if isinstance(i, dict):
                i = scrub_dict(i)
            scrubbed_list.append(i)
        return scrubbed_list

приголомшливий . . Я вніс цю зміну в кодову базу, але пропустив ваш коментар _ / _
BlissRage

0

Альтернативний спосіб зробити це - використання розуміння словника. Це має бути сумісним із2.7+

result = {
    key: value for key, value in
    {"foo": "bar", "lorem": None}.items()
    if value
}

0

Ось варіант, якщо ви використовуєте pandas:

import pandas as pd

d = dict.fromkeys(['a', 'b', 'c', 'd'])
d['b'] = 'not null'
d['c'] = ''  # empty string

print(d)

# convert `dict` to `Series` and replace any blank strings with `None`;
# use the `.dropna()` method and
# then convert back to a `dict`
d_ = pd.Series(d).replace('', None).dropna().to_dict()

print(d_)

0

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

Якщо хтось хоче уникнути вищезазначеного, може використовувати код нижче (видаляє порожні рядки та значення None із вкладеного словника та вкладеного списку):

def remove_empty_from_dict(d):
    if type(d) is dict:
        _temp = {}
        for k,v in d.items():
            if v == None or v == "":
                pass
            elif type(v) is int or type(v) is float:
                _temp[k] = remove_empty_from_dict(v)
            elif (v or remove_empty_from_dict(v)):
                _temp[k] = remove_empty_from_dict(v)
        return _temp
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if( (str(v).strip() or str(remove_empty_from_dict(v)).strip()) and (v != None or remove_empty_from_dict(v) != None))]
    else:
        return d

0

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

data = {'':'', '20':'', '50':'', '100':'1.1', '200':'1.2'}

dic = {key:value for key,value in data.items() if value != ''}

print(dic)

{'100': '1.1', '200': '1.2'}

Будь ласка, згадайте версію python, чи підтримуватиме вона останню версію?
HaseeB Мир

На даний момент ваша відповідь позначена, оскільки низька якість може бути видалена. Переконайтесь, що ваша відповідь містить пояснення, окрім будь-якого коду.
Тім Стек

@TimStack Будь ласка, рекомендуйте видалити для відповідей LQ.
10 повтор

@ 10Rep Я не рекомендую видаляти відповідь, яка може працювати як рішення, але просто не вистачає жодних описових коментарів. Я б швидше повідомив користувача та навчив їх, як виглядає краща відповідь.
Тім Стек

@HasseB Mir Я використовую останній Python 3.8.3
KokoEfraim

-2

Деякі показники:

1. Перелічити розуміння відтворити дікт

In [7]: %%timeit dic = {str(i):i for i in xrange(10)}; dic['10'] = None; dic['5'] = None
   ...: dic = {k: v for k, v in dic.items() if v is not None} 
   1000000 loops, best of 7: 375 ns per loop

2. Перелічити розуміння відтворити дікт за допомогою dict ()

In [8]: %%timeit dic = {str(i):i for i in xrange(10)}; dic['10'] = None; dic['5'] = None
   ...: dic = dict((k, v) for k, v in dic.items() if v is not None)
1000000 loops, best of 7: 681 ns per loop

3. Цикл та видалення ключа, якщо v - Ні

In [10]: %%timeit dic = {str(i):i for i in xrange(10)}; dic['10'] = None; dic['5'] = None
    ...: for k, v in dic.items():
    ...:   if v is None:
    ...:     del dic[k]
    ...: 
10000000 loops, best of 7: 160 ns per loop

тому цикл і видалення є найшвидшим за 160ns, розуміння списку вдвічі повільніше ~ ~ 375ns і з викликом до dict() - знову наполовину повільніше ~ 680ns.

Якщо ввімкнути 3 у функцію, вона знову знизиться до приблизно 275 секунд. Також для мене PyPy був приблизно вдвічі швидшим, ніж неет-пітон.


Цикл та видалення також можуть запускати RunTimeError, оскільки це неправильно змінювати словник під час ітерації подання. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ага, так, нормально, в python 3 це правда, але не в python 2.7, оскільки елементи повертають список, тож вам доведеться зателефонувати list(dic.items())в py 3. Розуміння розгляду ftw тоді? del все ще здається швидшим для низького співвідношення значень Null / empty. Я думаю, складання цього списку так само погано для споживання пам’яті, ніж просто відтворення диктату.
Річард Матті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.