166

I have a dict and would like to remove all the keys for which there are empty value strings.

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

What is the best way to do this?

Airsource Ltd
  • 32,379
  • 13
  • 71
  • 75
ensnare
  • 40,069
  • 64
  • 158
  • 224

20 Answers20

266

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}

Note that all of your keys have values. It's just that some of those values are the empty string. There's no such thing as a key in a dict without a value; if it didn't have a value, it wouldn't be in the dict.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 39
    +1. It's important to note that this does not actually remove the keys from an existing dictionary. Rather, it creates a new dictionary. Usually this is exactly what someone wants and is probably what the OP needs, but it is not what the OP asked for. – Steven Rumbalski Aug 25 '12 at 03:06
  • 20
    This also kills v=0, which is fine, if that is what is wanted. – Paul Nov 15 '14 at 10:23
  • 6
    This also rids v=False, which isn't _exactly_ what OP asked. – Amir Mar 09 '16 at 20:07
  • its `.item()` for python3 – shredding Nov 14 '16 at 14:15
  • 4
    @shredding: You mean `.items()`. – BrenBarn Nov 14 '16 at 18:54
  • 6
    For later versions of python you should also use the dictionary generator: `{k: v for k, v in metadata.items() if v is not None}` – Schiavini Jan 25 '17 at 13:31
  • The value in a python dict can be None, which essentially describes exactly the case of a key in a dict without a value. – Airsource Ltd May 02 '17 at 15:51
  • @AirsourceLtd: No, it does not. It describes the case of a key whose value is None. In the same way, `[]` is a list that does not contain any value, which is different from `[None]`, which is a list containing the value `None`. – BrenBarn May 02 '17 at 18:17
  • 2
    I think for python3 the best is: `{k: v for k, v in metadata.items() if v}` – Ardi Nusawan Oct 19 '18 at 12:33
  • @ArdiNusawan thanks, this actually works for empty strings, unlike the given answer (3.8.3) – YTZ Oct 30 '20 at 18:49
  • 1
    "v is not None" doesn't remove the empty strings (e.g. '') – salouri Jul 05 '21 at 21:34
85

It can get even shorter than BrenBarn's solution (and more readable I think)

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

Tested with Python 2.7.3.

Community
  • 1
  • 1
patriciasz
  • 1,343
  • 9
  • 10
  • 18
    This also kills zero values. – Paul Nov 15 '14 at 10:22
  • 10
    To preserve 0 (zero) you can use `... if v!=None` like so: `{k: v for k, v in metadata.items() if v!=None}` – Dannid Apr 02 '15 at 16:09
  • 1
    {k: v for k, v in metadata.items() if v!=None} doesn't get rid of empty strings. – philgo20 Oct 01 '15 at 21:07
  • 1
    dictionary comprehensions is supported only with Python 2.7+ for compatibility with prior versions please use @BrenBarn's solution. – Pavan Gupta Oct 24 '15 at 18:51
  • 15
    Should always compare None with, 'is not', instead of '!='. http://stackoverflow.com/a/14247419/2368836 – rocktheartsm4l Apr 12 '16 at 23:11
  • This solution will not work for `bool` attribute if it will be `false`. – msmq Apr 08 '19 at 12:59
  • works on Python 3.9 too. Works when v is False. Works when v is '' (empty string). – salouri Jul 05 '21 at 21:40
  • 1
    Note: for those wondering about the PO request to remove items from dict, you can't remove a key from a dict in-place without copying the dict. You will get a runtime error for changing the size of dict during iteration – salouri Jul 05 '21 at 21:48
24

If you really need to modify the original dictionary:

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

Note that we have to make a list of the empty keys because we can't modify a dictionary while iterating through it (as you may have noticed). This is less expensive (memory-wise) than creating a brand-new dictionary, though, unless there are a lot of entries with empty values.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
21

If you want a full-featured, yet succinct approach to handling real-world data structures which are often nested, and can even contain cycles, I recommend looking at the remap utility from the boltons utility package.

After pip install boltons or copying iterutils.py into your project, just do:

from boltons.iterutils import remap

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

This page has many more examples, including ones working with much larger objects from Github's API.

It's pure-Python, so it works everywhere, and is fully tested in Python 2.7 and 3.3+. Best of all, I wrote it for exactly cases like this, so if you find a case it doesn't handle, you can bug me to fix it right here.

Mahmoud Hashemi
  • 2,655
  • 30
  • 19
  • 1
    This solution worked great for a similar problem i had: stripping empty values from deeply nested lists inside of dictionaries. Thanks! – Nicholas Tulach Apr 19 '17 at 20:22
  • 1
    This is good, as you are not reinventing the wheel, and providing a solution for nested objects. Thanks! – vekerdyb Aug 21 '18 at 11:15
  • 1
    I really liked the article you wrote for your library, and this is a useful library! – Fanglin Oct 13 '18 at 04:55
  • 1
    Thank you for this. Not to repeat props but this cleanly handled nested values. Very nice! – aaronbriel Oct 14 '20 at 21:07
  • This is an excellent answer, and it can go beyond this particular question. Though, if somebody found themselves trying to answer that question, they will probably come across similar questions that can be solved with this remap function. – geo909 Feb 05 '21 at 09:21
16

Based on Ryan's solution, if you also have lists and nested dictionaries:

For 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

For 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
cjbarth
  • 4,189
  • 6
  • 43
  • 62
chachan
  • 2,382
  • 1
  • 26
  • 41
  • 1
    Ha, nice extension! It is a good solution for dictionaries like the following: `d = { "things": [{ "name": "" }] }` – Ryan Shea Jul 22 '14 at 23:07
15

BrenBarn's solution is ideal (and pythonic, I might add). Here is another (fp) solution, however:

from operator import itemgetter
dict(filter(itemgetter(1), metadata.items()))
Community
  • 1
  • 1
Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
9

If you have a nested dictionary, and you want this to work even for empty sub-elements, you can use a recursive variant of BrenBarn's suggestion:

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
Ryan Shea
  • 4,252
  • 4
  • 32
  • 32
7

For python 3

dict((k, v) for k, v in metadata.items() if v)
Hector Oliveros
  • 391
  • 4
  • 8
6

Quick Answer (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'}
'''

Detailed Answer

Problem

  • Context: Python 2.x
  • Scenario: Developer wishes modify a dictionary to exclude blank values
    • aka remove empty values from a dictionary
    • aka delete keys with blank values
    • aka filter dictionary for non-blank values over each key-value pair

Solution

  • example01 use python list-comprehension syntax with simple conditional to remove "empty" values

Pitfalls

  • example01 only operates on a copy of the original dictionary (does not modify in place)
  • example01 may produce unexpected results depending on what developer means by "empty"
    • Does developer mean to keep values that are falsy?
    • If the values in the dictionary are not gauranteed to be strings, developer may have unexpected data loss.
    • result01 shows that only three key-value pairs were preserved from the original set

Alternate example

  • example02 helps deal with potential pitfalls
  • The approach is to use a more precise definition of "empty" by changing the conditional.
  • Here we only want to filter out values that evaluate to blank strings.
  • Here we also use .strip() to filter out values that consist of only whitespace.

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'
  }
'''

See also

dreftymac
  • 31,404
  • 26
  • 119
  • 182
5

Building on the answers from patriciasz and nneonneo, and accounting for the possibility that you might want to delete keys that have only certain falsy things (e.g. '') but not others (e.g. 0), or perhaps you even want to include some truthy things (e.g. 'SPAM'), then you could make a highly specific hitlist:

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

Unfortunately, this doesn't quite work, because for example 0 in unwanted evaluates to True. We need to discriminate between 0 and other falsy things, so we have to use is:

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

...evaluates to False.

Now use it to del the unwanted things:

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]

If you want a new dictionary, instead of modifying metadata in place:

newdict = {k: v for k, v in metadata.items() if not any([v is i for i in unwanted])}
Community
  • 1
  • 1
Matt Hall
  • 7,614
  • 1
  • 23
  • 36
  • really nice shot, it address many problems at once and it solves the question, thank you to make it clear – jlandercy Apr 29 '16 at 07:14
  • Cool! It works for this example. However, it doesn't work when an item in the dictionary is `[]` – jsga Dec 27 '19 at 13:02
2

I read all replies in this thread and some referred also to this thread: Remove empty dicts in nested dictionary with recursive function

I originally used solution here and it worked great:

Attempt 1: Too Hot (not performant or future-proof):

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

But some performance and compatibility concerns were raised in Python 2.7 world:

  1. use isinstance instead of type
  2. unroll the list comp into for loop for efficiency
  3. use python3 safe items instead of iteritems

Attempt 2: Too Cold (Lacks Memoization):

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! This is not recursive and not at all memoizant.

Attempt 3: Just Right (so far):

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
Dan D.
  • 73,243
  • 15
  • 104
  • 123
SmokeRaven
  • 111
  • 7
2

To preserve 0 and False values but get rid of empty values you could use:

{k: v for k, v in metadata.items() if v or v == 0 or v is False}

For a nested dict with mixed types of values you could use:

def remove_empty_from_dict(d):
  if isinstance(d, dict):
    return dict((k, remove_empty_from_dict(v)) for k, v in d.items() \
            if v or v == 0 or v is False and remove_empty_from_dict(v) is not None)
  elif isinstance(d, list):
    return [remove_empty_from_dict(v) for v in d 
            if v or v == 0 or v is False and remove_empty_from_dict(v) is not None]
  else:
    if d or d == 0 or d is False:
      return d
Mat
  • 111
  • 1
  • 3
1

Dicts mixed with Arrays

  • The answer at Attempt 3: Just Right (so far) from BlissRage's answer does not properly handle arrays elements. I'm including a patch in case anyone needs it. The method is handles list with the statement block of if isinstance(v, list):, which scrubs the list using the original scrub_dict(d) implementation.
    @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
Marcello DeSales
  • 21,361
  • 14
  • 77
  • 80
1

"As I also currently write a desktop application for my work with Python, I found in data-entry application when there is lots of entry and which some are not mandatory thus user can left it blank, for validation purpose, it is easy to grab all entries and then discard empty key or value of a dictionary. So my code above a show how we can easy take them out, using dictionary comprehension and keep dictionary value element which is not blank. I use 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'}
KokoEfraim
  • 198
  • 8
  • Please mention python version also will it support latest version ? – Haseeb Mir Jul 22 '20 at 10:27
  • Your answer is currently flagged as low quality may be deleted. Please make sure your answer contains an explanation aside from any code. – Tim Stack Jul 22 '20 at 14:14
  • @TimStack Please recommend deletion for LQ answers. – 10 Rep Jul 22 '20 at 16:04
  • @10Rep I will not recommend deletion for an answer that may work as a solution but is merely lacking any descriptive comments. I would rather inform the user and teach them what a better answer looks like. – Tim Stack Jul 23 '20 at 07:29
  • @HasseB Mir I use the latest Python 3.8.3 – KokoEfraim Jul 24 '20 at 07:02
  • Hi @TimStack. My apology not giving any explanation, because I think the code is so simple. But I can understand your point, not giving any word or explanation can miss interpreted as rude or arrogant. I am sorry. – KokoEfraim Jul 24 '20 at 07:07
0

An alternative way you can do this, is using dictionary comprehension. This should be compatible with 2.7+

result = {
    key: value for key, value in
    {"foo": "bar", "lorem": None}.items()
    if value
}
Serguei Fedorov
  • 7,763
  • 9
  • 63
  • 94
0

Here is an option if you are using 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_)
jeschwar
  • 1,286
  • 7
  • 10
0

Some of Methods mentioned above ignores if there are any integers and float with values 0 & 0.0

If someone wants to avoid the above can use below code(removes empty strings and None values from nested dictionary and nested list):

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
dheeraj .A
  • 1,073
  • 7
  • 6
0
metadata ={'src':'1921','dest':'1337','email':'','movile':''}
ot = {k: v for k, v in metadata.items() if v != ''}
print(f"Final {ot}")
MADHUR GUPTA
  • 1,014
  • 10
  • 14
0

You also have an option with filter method:

filtered_metadata = dict( filter(lambda val: val[1] != u'', metadata.items()) )
hdev
  • 430
  • 1
  • 3
  • 10
-2

Some benchmarking:

1. List comprehension recreate 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. List comprehension recreate dict using 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. Loop and delete key if v is 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

so loop and delete is the fastest at 160ns, list comprehension is half as slow at ~375ns and with a call to dict() is half as slow again ~680ns.

Wrapping 3 into a function brings it back down again to about 275ns. Also for me PyPy was about twice as fast as neet python.

Community
  • 1
  • 1
Richard Mathie
  • 306
  • 3
  • 6
  • Loop and delete may also throw a RunTimeError, since it is not valid to modify a dictionary while iterating a view. https://docs.python.org/3/library/stdtypes.html s4.10.1 – Airsource Ltd Jun 16 '17 at 12:53
  • ah man yeah ok in python 3 that is true but not in python 2.7 as items returns a list, so you have to call `list(dic.items())` in py 3. Dict comprehension ftw then? del still seems faster for a low ratio of Null/empty values. I guess building that list is just as bad for memory consumption than just recreating the dict. – Richard Mathie Nov 28 '17 at 11:48