Dictos misturados com matrizes

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.

Nneonneo
fonte
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

Respostas:

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()))
Joel Cornett
fonte
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 .

Mahmoud Hashemi
fonte
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
Chachan
fonte
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
Ryan Shea
fonte
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

dreftymac
fonte
5

Para python 3

dict((k, v) for k, v in metadata.items() if v)
Hector Oliveros
fonte
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])}
kwinkunks
fonte
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
BlissRage
fonte
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
Marcello de Sales
fonte
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
}
Serguei Fedorov
fonte
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_)
Jeschwar
fonte
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
dheeraj .A
fonte
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'}
KokoEfraim
fonte
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.

Richard Mathie
fonte
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