Skuteczny sposób na usuwanie kluczy z pustymi łańcuchami z dyktu


116

Mam dyktando i chciałbym usunąć wszystkie klucze, dla których są puste ciągi wartości.

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

Jaki jest najlepszy sposób, aby to zrobić?

Odpowiedzi:


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}

Pamiętaj, że wszystkie klucze mają wartości. Po prostu niektóre z tych wartości to pusty ciąg. W dyktandzie nie ma czegoś takiego jak klucz bez wartości; gdyby nie miał wartości, nie byłby w dyktandzie.


29
+1. Należy zauważyć, że nie powoduje to usunięcia kluczy z istniejącego słownika. Raczej tworzy nowy słownik. Zwykle jest to dokładnie to, czego ktoś chce i prawdopodobnie tego potrzebuje PO, ale nie o to prosił PO.
Steven Rumbalski

18
To również zabija v = 0, co jest w porządku, jeśli tego chcemy.
Paweł

2
To również eliminuje v = False, co nie jest dokładnie tym , o co prosił OP.
Amir,

4
@shredding: Masz na myśli .items().
BrenBarn

6
W przypadku późniejszych wersji Pythona powinieneś także użyć generatora słowników:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

Może być jeszcze krótszy niż rozwiązanie BrenBarn (i myślę, że jest bardziej czytelny)

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

Przetestowano w Pythonie 2.7.3.


13
To również zabija wartości zerowe.
Paweł

10
Aby zachować 0 (zero), możesz użyć w następujący ... if v!=Nonesposób: {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v dla k, v in metadata.items () if v! = None} nie usuwa pustych ciągów.
philgo20

1
rozumienia słownikowe są obsługiwane tylko w Pythonie 2.7+ dla zgodności z wcześniejszymi wersjami, użyj rozwiązania @ BrenBarn.
Pavan Gupta

12
Należy zawsze porównać wartość None z wartością „nie jest” zamiast „! =”. stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Jeśli naprawdę potrzebujesz zmodyfikować oryginalny słownik:

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

Zauważ, że musimy zrobić listę pustych kluczy, ponieważ nie możemy modyfikować słownika podczas iteracji po nim (jak być może zauważyłeś). Jest to jednak mniej kosztowne (pod względem pamięci) niż tworzenie zupełnie nowego słownika, chyba że istnieje wiele wpisów z pustymi wartościami.


spowoduje to również usunięcie wartości 0 i 0 nie jest puste
JVK

2
Jeśli używasz Pythona 3+, musisz zastąpić .iteritems()go .items(), pierwszy nie działa już w najnowszych wersjach Pythona.
Mariano Ruiz


12

Jeśli chcesz mieć w pełni funkcjonalne, ale zwięzłe podejście do obsługi rzeczywistych struktur danych, które są często zagnieżdżone, a nawet mogą zawierać cykle, polecam przyjrzenie się narzędziu do remapowania z pakietu narzędzi boltons .

Po pip install boltonsskopiowaniu lub skopiowaniu iterutils.py do projektu po prostu wykonaj:

from boltons.iterutils import remap

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

Ta strona zawiera wiele innych przykładów, w tym te działające ze znacznie większymi obiektami z API Github.

Jest to czysty Python, więc działa wszędzie i jest w pełni przetestowany w Pythonie 2.7 i 3.3+. A co najlepsze, napisałem go dla dokładnie takich przypadków, więc jeśli znajdziesz przypadek, którego nie obsługuje, możesz mnie naprawić tutaj .


1
To rozwiązanie działało świetnie w przypadku podobnego problemu, który miałem: usuwanie pustych wartości z głęboko zagnieżdżonych list w słownikach. Dzięki!
Nicholas Tulach

1
To dobrze, ponieważ nie wymyślasz na nowo koła i nie zapewniasz rozwiązania dla zagnieżdżonych obiektów. Dzięki!
vekerdyb,

1
Bardzo podobał mi się artykuł, który napisałeś dla swojej biblioteki, a to jest przydatna biblioteka!
lifelogger

11

W oparciu o rozwiązanie Ryana , jeśli masz również listy i zagnieżdżone słowniki:

W przypadku Pythona 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

W przypadku Pythona 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, niezłe rozszerzenie! Jest to dobre rozwiązanie dla słowników takich jak:d = { "things": [{ "name": "" }] }
Ryan Shea

6

Jeśli masz zagnieżdżony słownik i chcesz, aby działał nawet dla pustych elementów podrzędnych, możesz użyć rekurencyjnego wariantu sugestii BrenBarna:

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

Użyj items()zamiast iteritems()dla Python 3
andydavies

6

Szybka odpowiedź (TL; DR)

Przykład01

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

Szczegółowa odpowiedź

Problem

  • Kontekst: Python 2.x
  • Scenariusz: programista chce zmodyfikować słownik, aby wykluczyć puste wartości
    • aka usuń puste wartości ze słownika
    • czyli usuń klucze z pustymi wartościami
    • aka słownik filtrów dla wartości niepustych w każdej parze klucz-wartość

Rozwiązanie

  • example01 używa składni listy Pythona z prostym warunkiem, aby usunąć „puste” wartości

Pułapki

  • example01 działa tylko na kopii oryginalnego słownika (nie zmienia się w miejscu)
  • przykład01 może dać nieoczekiwane wyniki, w zależności od tego, co deweloper rozumie przez „pusty”
    • Czy deweloper chce zachować fałszywe wartości ?
    • Jeśli wartości w słowniku nie są gwarantowane jako ciągi, programista może mieć nieoczekiwaną utratę danych.
    • Wynik01 pokazuje, że tylko trzy pary klucz-wartość zostały zachowane z oryginalnego zestawu

Alternatywny przykład

  • example02 pomaga radzić sobie z potencjalnymi pułapkami
  • Podejście polega na zastosowaniu bardziej precyzyjnej definicji „pustego” poprzez zmianę warunku.
  • Tutaj chcemy tylko odfiltrować wartości, których wynikiem są puste ciągi.
  • Tutaj również używamy .strip () do odfiltrowywania wartości, które składają się tylko z białych znaków.

Przykład02

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

Zobacz też



4

Opierając się na odpowiedziach patriciasz i nneonneo i biorąc pod uwagę możliwość, że możesz chcieć usunąć klucze, które mają tylko pewne fałszywe rzeczy (np. ''), Ale nie inne (np. 0), A może nawet chcesz uwzględnić pewne prawdziwe rzeczy (np. 'SPAM') , wtedy możesz stworzyć bardzo szczegółową listę trafień:

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

Niestety to nie do końca działa, ponieważ na przykład 0 in unwantedwartościowane są do True. Musimy rozróżniać 0i inne fałszywe rzeczy, więc musimy użyć is:

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

... ocenia się do False.

Teraz użyj go do delniechcianych rzeczy:

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]

Jeśli chcesz mieć nowy słownik, zamiast modyfikować metadataw miejscu:

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

naprawdę fajny strzał, rozwiązuje wiele problemów naraz i rozwiązuje pytanie, dziękuję za wyjaśnienie
jlandercy

Chłodny! Działa w tym przykładzie. Jednak to nie działa, gdy pozycja w słowniku to[]
jsga

2

Przeczytałem wszystkie odpowiedzi w tym wątku, a niektóre odniosły się również do tego wątku: Usuń puste dykty w zagnieżdżonym słowniku z funkcją rekurencyjną

Pierwotnie zastosowałem tutaj rozwiązanie i działało świetnie:

Próba 1: Too Hot (niewydajne lub przyszłościowe) :

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

Jednak w świecie Pythona 2.7 pojawiły się pewne problemy z wydajnością i zgodnością:

  1. użyj isinstancezamiasttype
  2. rozwinąć listę kompilacji w forpętlę w celu zwiększenia wydajności
  3. użyj bezpiecznego Python3 itemszamiastiteritems

Próba 2: Za zimno (brak zapamiętania) :

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! To nie jest rekurencyjne i wcale nie jest zapamiętywane.

Próba 3: w sam raz (jak dotąd) :

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
chyba że jestem ślepy, wydaje mi się, że próby 2 i 3 są dokładnie takie same ...
luckyguy73

1

Dicts zmieszane z Arrays

  • Odpowiedź z próby 3: Just Right (jak dotąd) z odpowiedzi BlissRage nie obsługuje poprawnie elementów tablic. Dołączam łatkę na wypadek, gdyby ktoś jej potrzebował. Metoda obsługuje list z blokiem instrukcji of if isinstance(v, list):, który czyści listę przy użyciu oryginalnej scrub_dict(d)implementacji.
    @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

niesamowite . . .
Dokonałem

0

Alternatywnym sposobem, w jaki możesz to zrobić, jest użycie rozumienia ze słownika. Powinno to być zgodne z2.7+

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

0

Oto opcja, jeśli używasz 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

Niektóre z wyżej wymienionych metod ignorują obecność liczb całkowitych i zmiennoprzecinkowe z wartościami 0 i 0,0

Jeśli ktoś chce uniknąć powyższego, może użyć poniższego kodu (usuwa puste ciągi i wartości None z zagnieżdżonego słownika i listy zagnieżdżonej):

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

„Ponieważ obecnie piszę aplikację komputerową do pracy w języku Python, znalazłem w aplikacji do wprowadzania danych, w której jest dużo wpisów, a niektóre z nich nie są obowiązkowe, więc użytkownik może pozostawić ją pustą, w celu walidacji łatwo ją złapać wszystkie wpisy, a następnie odrzuć pusty klucz lub wartość słownika. Mój kod powyżej pokazuje, jak możemy je łatwo usunąć, używając rozumienia słownikowego i zachować element słownika, który nie jest pusty. Używam Pythona 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'}

Proszę wspomnieć, że wersja Pythona również będzie obsługiwać najnowszą wersję?
HaseeB Mir

Twoja odpowiedź jest obecnie oznaczona jako niska jakość może zostać usunięta. Upewnij się, że Twoja odpowiedź zawiera wyjaśnienie oprócz kodu.
Tim Stack

@TimStack Zalecamy usuwanie odpowiedzi LQ.
10 Rep

@ 10Rep Nie polecam usuwania odpowiedzi, która może działać jako rozwiązanie, ale nie zawiera jedynie opisowych komentarzy. Wolałbym raczej poinformować użytkownika i nauczyć go, jak wygląda lepsza odpowiedź.
Tim Stack

@HasseB Mir Używam najnowszego Pythona 3.8.3
KokoEfraim

-2

Niektóre testy porównawcze:

1. Lista ze zrozumieniem odtworzyć dyktando

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. Lista ze zrozumieniem odtworzyć dict za pomocą 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. Klawisz zapętlania i usuwania, jeśli v ma wartość Brak

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

więc pętla i usuwanie są najszybsze przy 160 ns, rozumienie listy jest o połowę wolniejsze przy ~ 375 ns iz wywołaniem dict() jest znowu o połowę wolniejsze ~ 680 ns.

Pakowanie 3 do funkcji sprowadza ją z powrotem do około 275ns. Również dla mnie PyPy był około dwa razy szybszy niż Neet Python.


Pętla i usuwanie mogą również generować błąd RunTimeError, ponieważ modyfikowanie słownika podczas iteracji widoku jest nieprawidłowe. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ah man yeah ok w Pythonie 3, to prawda, ale nie w Pythonie 2.7, ponieważ elementy zwracają listę, więc musisz zadzwonić list(dic.items())do py 3. Więc powiedz ze zrozumieniem? del nadal wydaje się szybszy przy niskim stosunku wartości Null / puste. Wydaje mi się, że tworzenie tej listy jest równie szkodliwe dla zużycia pamięci niż tylko odtworzenie dyktatu.
Richard Mathie
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.