Эффективный способ удалить ключи с пустыми строками из dict


116

У меня есть диктатор, и я хочу удалить все ключи, для которых есть пустые строки значений.

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

Как лучше всего это сделать?

Ответы:


195

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}

Обратите внимание, что все ваши ключи имеют значения. Просто некоторые из этих значений - пустая строка. В слове dict без значения не бывает ключа; если бы у него не было значения, его бы не было в dict.


29
+1. Важно отметить, что это фактически не удаляет ключи из существующего словаря. Скорее, он создает новый словарь. Обычно это именно то, что кто-то хочет и, вероятно, именно то, что нужно OP, но это не то, что OP просил.
Стивен Румбальский,

18
Это также убивает v = 0, и это нормально, если это то, что нужно.
Пол

2
Это также избавляет от v = False, что не совсем то , что просил OP.
Амир

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 02

1
{k: v вместо k, v в metadata.items () if v! = None} не избавляется от пустых строк.
philgo20 01

1
понимание словаря поддерживается только в Python 2.7+ для совместимости с предыдущими версиями, пожалуйста, используйте решение @BrenBarn.
Паван Гупта,

12
Всегда следует сравнивать None с 'is not' вместо '! ='. 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.

Это чистый Python, поэтому он работает везде и полностью протестирован на Python 2.7 и 3.3+. Лучше всего то, что я написал его именно для таких случаев, поэтому, если вы обнаружите, что он не обрабатывается, вы можете исправить ошибку прямо здесь .


1
Это решение отлично сработало для решения похожей проблемы, с которой я столкнулся: удаление пустых значений из глубоко вложенных списков внутри словарей. Спасибо!
Николай

1
Это хорошо, поскольку вы не изобретаете колесо, а предлагаете решение для вложенных объектов. Спасибо!
vekerdyb

1
Мне очень понравилась статья, которую вы написали для своей библиотеки, и это полезная библиотека!
Lifelogger

12

На основе решения Райана , если у вас также есть списки и вложенные словари:

Для 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": "" }] }
Райан Ши

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)

Example01

### 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
  • Сценарий: разработчик желает изменить словарь, чтобы исключить пустые значения
    • иначе удалить пустые значения из словаря
    • иначе удалить ключи с пустыми значениями
    • иначе словарь фильтров для непустых значений по каждой паре ключ-значение

Решение

  • example01 используйте синтаксис понимания списка Python с простым условием для удаления «пустых» значений

Ловушки

  • example01 работает только с копией исходного словаря (не изменяется на месте)
  • example01 может давать неожиданные результаты в зависимости от того, что разработчик подразумевает под "пустым"
    • Имеет ли в виду разработчик сохранять ложные значения ?
    • Если значения в словаре не считаются строками, разработчик может неожиданно потерять данные.
    • result01 показывает, что из исходного набора сохранились только три пары "ключ-значение".

Альтернативный пример

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

Example02

### 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

DOH! Это не рекурсивно и совсем не запоминает.

Попытка 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

Dicts, смешанные с массивами

  • Ответ при попытке 3: Just Right (пока) из ответа BlissRage не обрабатывает элементы массивов должным образом. Я включаю патч на случай, если он кому-то понадобится. Метод обрабатывает список с блоком операторов 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 Mir,

Ваш ответ в настоящее время отмечен как низкое качество, возможно, он будет удален. Убедитесь, что ваш ответ помимо кода содержит пояснения.
Тим Стэк,

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

@ 10Rep Я не буду рекомендовать удаление ответа, который может работать как решение, но в нем просто отсутствуют какие-либо описательные комментарии. Я лучше проинформирую пользователя и научу его, как выглядит лучший ответ.
Тим Стэк

@HasseB Mir Я использую последнюю
версию

-2

Некоторые тесты:

1. Понимание списка воссоздает dict

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 с помощью 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 равно None

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

поэтому цикл и удаление являются самыми быстрыми при 160 нс, понимание списка вдвое медленнее при ~ 375 нс, а с вызовом снова dict()вдвое медленнее ~ 680 нс.

Обертывание 3 в функцию снова снижает его примерно до 275 нс. Также для меня PyPy был примерно в два раза быстрее, чем neet python.


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

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