Maneira eficiente de remover chaves com strings vazias de um dicionário


116

Eu tenho um dicionário e gostaria de remover todas as chaves para as quais existem cadeias de caracteres de valor vazias.

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

Qual é a melhor maneira de fazer isso?

Respostas:


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}

Observe que todas as suas chaves têm valores. É que alguns desses valores são strings vazias. Não existe uma chave em um dicionário sem um valor; se não tivesse valor, não estaria no dict.


29
+1. É importante observar que isso não remove as chaves de um dicionário existente. Em vez disso, ele cria um novo dicionário. Normalmente, isso é exatamente o que alguém deseja e provavelmente é o que o OP precisa, mas não é o que o OP pediu.
Steven Rumbalski

18
Isso também mata v = 0, o que é bom, se é isso o que se deseja.
Paul

2
Isso também elimina v = False, que não é exatamente o que OP pediu.
Amir

4
@shredding: Você quer dizer .items().
BrenBarn

6
Para versões posteriores do python, você também deve usar o gerador de dicionário:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

Pode ser ainda mais curto do que a solução do BrenBarn (e mais legível, eu acho)

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

Testado com Python 2.7.3.


13
Isso também mata os valores zero.
Paul

10
Para preservar 0 (zero) você pode usar da seguinte ... if v!=Noneforma: {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v para k, v em metadata.items () if v! = None} não elimina strings vazias.
philgo20 01 de

1
as compreensões de dicionário são suportadas apenas com Python 2.7+ para compatibilidade com versões anteriores, use a solução de @BrenBarn.
Pavan Gupta

12
Deve sempre comparar None com 'não é', em vez de '! ='. stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Se você realmente precisa modificar o dicionário original:

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

Observe que temos que fazer uma lista das chaves vazias porque não podemos modificar um dicionário durante a iteração por ele (como você deve ter notado). Isso é menos caro (em termos de memória) do que criar um dicionário totalmente novo, a menos que haja muitas entradas com valores vazios.


isso também removerá o valor 0 e 0 não está vazio
JVK

2
Se você estiver usando o Python 3+, terá que substituir .iteritems()por .items(), o primeiro não funciona mais nas versões mais recentes do Python.
Mariano Ruiz

12

A solução de BrenBarn é ideal (e pítônica, devo acrescentar). Aqui está outra solução (fp), no entanto:

from operator import itemgetter
dict(filter(itemgetter(1), metadata.items()))

12

Se você deseja uma abordagem completa, mas sucinta, para lidar com estruturas de dados do mundo real que geralmente estão aninhadas e podem até conter ciclos, recomendo olhar o utilitário de remapeamento do pacote de utilitários boltons .

Depois de pip install boltonscopiar iterutils.py em seu projeto, basta fazer:

from boltons.iterutils import remap

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

Esta página tem muitos outros exemplos, incluindo aqueles que trabalham com objetos muito maiores da API do Github.

É puro Python, por isso funciona em qualquer lugar e é totalmente testado em Python 2.7 e 3.3+. O melhor de tudo é que eu o escrevi exatamente para casos como esse, então se você encontrar um caso que ele não resolve, você pode me bugar para consertá-lo aqui .


1
Esta solução funcionou muito bem para um problema semelhante que eu tive: retirar valores vazios de listas profundamente aninhadas dentro de dicionários. Obrigado!
Nicholas Tulach de

1
Isso é bom, pois você não está reinventando a roda e fornecendo uma solução para objetos aninhados. Obrigado!
vekerdyb

1
Gostei muito do artigo que você escreveu para sua biblioteca, e esta é uma biblioteca útil!
Lifelogger

11

Com base na solução de Ryan , se você também tiver listas e dicionários aninhados:

Para 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

Para 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
Ha, bela extensão! É uma boa solução para dicionários como o seguinte:d = { "things": [{ "name": "" }] }
Ryan Shea

6

Se você tiver um dicionário aninhado e quiser que funcione mesmo para subelementos vazios, pode usar uma variante recursiva da sugestão de 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

Use em items()vez de iteritems()para Python 3
andydavies

6

Resposta Rápida (TL; DR)

Exemplo01

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

Resposta Detalhada

Problema

  • Contexto: Python 2.x
  • Cenário: desenvolvedor deseja modificar um dicionário para excluir valores em branco
    • também conhecido como remover valores vazios de um dicionário
    • também conhecido como excluir chaves com valores em branco
    • também conhecido como filtro de dicionário para valores não em branco em cada par de valor-chave

Solução

  • example01 usa sintaxe de compreensão de lista python com condicional simples para remover valores "vazios"

Armadilhas

  • example01 opera apenas em uma cópia do dicionário original (não modifica no local)
  • example01 pode produzir resultados inesperados, dependendo do que o desenvolvedor entende por "vazio"
    • O desenvolvedor pretende manter os valores que são falsos ?
    • Se os valores no dicionário não forem garantidos como strings, o desenvolvedor pode ter perda de dados inesperada.
    • result01 mostra que apenas três pares de valores-chave foram preservados do conjunto original

Exemplo alternativo

  • example02 ajuda a lidar com potenciais armadilhas
  • A abordagem é usar uma definição mais precisa de "vazio", alterando a condicional.
  • Aqui, queremos apenas filtrar os valores avaliados como strings em branco.
  • Aqui também usamos .strip () para filtrar os valores que consistem apenas em espaços em branco.

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

Veja também



4

Com base nas respostas de patriciasz e nneonneo e levando em consideração a possibilidade de que você queira excluir chaves que têm apenas certas coisas falsas (por exemplo ''), mas não outras (por exemplo 0), ou talvez você até queira incluir algumas coisas verdadeiras (por exemplo 'SPAM') , então você poderia fazer uma lista de ocorrências altamente específica:

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

Infelizmente, isso não funciona muito bem, porque, por exemplo, 0 in unwantedavalia para True. Precisamos discriminar entre 0outras coisas falsas, então temos que usar is:

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

... avalia para False.

Agora use-o para delas coisas indesejadas:

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]

Se você quiser um novo dicionário, em vez de modificar metadatano local:

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

tiro muito bom, ele resolve muitos problemas ao mesmo tempo e resolve a questão, obrigado para deixar claro
jlandercy

Legal! Funciona para este exemplo. No entanto, ele não funciona quando um item no dicionário é[]
jsga

2

Eu li todas as respostas neste tópico e algumas também se referiram a este tópico: Remover dicts vazios no dicionário aninhado com função recursiva

Eu usei originalmente a solução aqui e funcionou muito bem:

Tentativa 1: Muito quente (sem desempenho ou à prova de futuro) :

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

Mas algumas questões de desempenho e compatibilidade foram levantadas no mundo Python 2.7:

  1. use em isinstancevez detype
  2. desenrole a lista comp em forloop para eficiência
  3. use python3 safe em itemsvez deiteritems

Tentativa 2: Muito frio (falta memorização) :

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! Isso não é recursivo e nem um pouco memoizant.

Tentativa 3: na medida certa (até agora) :

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
a menos que eu seja cego, parece-me que as tentativas 2 e 3 são exatamente iguais ...
luckyguy73

1

Dictos misturados com matrizes

  • A resposta na tentativa 3: na medida certa (até agora) da resposta de BlissRage não trata adequadamente os elementos de arrays. Estou incluindo um patch, caso alguém precise. O método é a lista de identificadores com o bloco de instrução de if isinstance(v, list):, que limpa a lista usando a scrub_dict(d)implementação original .
    @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

impressionante . . . Eu fiz essa alteração na base de código, mas perdi seu comentário _ / _
BlissRage

0

Uma maneira alternativa de fazer isso é usar a compreensão do dicionário. Isso deve ser compatível com2.7+

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

0

Aqui está uma opção se você estiver usando 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

Alguns dos métodos mencionados acima ignoram se houver números inteiros e flutuam com valores 0 e 0,0

Se alguém quiser evitar o acima, pode usar o código abaixo (remove strings vazias e valores None do dicionário aninhado e da lista aninhada):

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

"Como também escrevo atualmente um aplicativo de desktop para meu trabalho com Python, encontrei um aplicativo de entrada de dados quando há muitas entradas e algumas não são obrigatórias, portanto o usuário pode deixá-lo em branco, para fins de validação, é fácil de pegar todas as entradas e, em seguida, descartar a chave ou valor vazio de um dicionário. Portanto, meu código acima mostra como podemos removê-los facilmente, usando a compreensão do dicionário e manter o elemento de valor do dicionário que não está em branco. Eu uso 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'}

Por favor, mencione a versão do python. Ela suportará a versão mais recente?
HaseeB Mir

Sua resposta está sinalizada como baixa qualidade e pode ser excluída. Certifique-se de que sua resposta contém uma explicação além de qualquer código.
Tim Stack

@TimStack Recomenda a exclusão para respostas LQ.
10 Rep.

@ 10Rep Não recomendarei a exclusão de uma resposta que pode funcionar como uma solução, mas que simplesmente não contém comentários descritivos. Prefiro informar o usuário e ensiná-lo como seria uma resposta melhor.
Tim Stack

@HasseB Mir Eu uso o Python 3.8.3 mais recente
KokoEfraim

-2

Alguns benchmarking:

1. Lista de compreensão recriar 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. Recriação de compreensão de lista de dict usando 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. Faça um loop e exclua a chave se v for Nenhum

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

então loop e delete são os mais rápidos em 160ns, a compreensão da lista é metade mais lenta em ~ 375ns e com uma chamada para dict()é a metade mais lenta novamente em ~ 680ns.

Envolver 3 em uma função o traz de volta para cerca de 275 ns. Além disso, para mim, o PyPy era duas vezes mais rápido do que o neet python.


Loop e delete também podem lançar um RunTimeError, já que não é válido modificar um dicionário durante a iteração de uma visão. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ah cara, ok, em python 3 isso é verdade, mas não em python 2.7, pois os itens retornam uma lista, então você tem que chamar list(dic.items())py 3. Compreensão de dict, então? del ainda parece mais rápido para uma proporção baixa de valores nulos / vazios. Eu acho que construir essa lista é tão ruim para o consumo de memória do que apenas recriar o dicionário.
Richard Mathie
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.