Effiziente Möglichkeit, Schlüssel mit leeren Zeichenfolgen aus einem Diktat zu entfernen


116

Ich habe ein Diktat und möchte alle Schlüssel entfernen, für die es leere Wertzeichenfolgen gibt.

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

Was ist der beste Weg, dies zu tun?

Antworten:


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}

Beachten Sie, dass alle Ihre Schlüssel Werte haben. Es ist nur so, dass einige dieser Werte die leere Zeichenfolge sind. Es gibt keinen Schlüssel in einem Diktat ohne Wert; Wenn es keinen Wert hätte, wäre es nicht im Diktat.


29
+1. Es ist wichtig zu beachten, dass dadurch die Schlüssel nicht aus einem vorhandenen Wörterbuch entfernt werden. Vielmehr wird ein neues Wörterbuch erstellt. Normalerweise ist dies genau das, was jemand will und was das OP wahrscheinlich braucht, aber es ist nicht das, was das OP verlangt.
Steven Rumbalski

18
Dies tötet auch v = 0, was in Ordnung ist, wenn dies gewünscht wird.
Paul

2
Dies befreit auch v = False, was nicht genau das ist , was OP gefragt hat.
Amir

4
@hredding: Du meinst .items().
BrenBarn

6
Für spätere Versionen von Python sollten Sie auch den Wörterbuchgenerator verwenden:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

Es kann sogar kürzer werden als die Lösung von BrenBarn (und meiner Meinung nach besser lesbar)

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

Getestet mit Python 2.7.3.


13
Dies beendet auch Nullwerte.
Paul

10
Um 0 (Null) zu erhalten, können Sie ... if v!=Nonewie {k: v for k, v in metadata.items() if v!=None}
folgt

1
{k: v für k, v in metadata.items (), wenn v! = None} leere Zeichenfolgen nicht entfernt.
Philgo20

1
Das Verständnis des Wörterbuchs wird nur mit Python 2.7+ unterstützt, um die Kompatibilität mit früheren Versionen zu gewährleisten. Verwenden Sie bitte die Lösung von @ BrenBarn.
Pavan Gupta

12
Sollte immer None mit 'is not' anstelle von '! =' Vergleichen. stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Wenn Sie das Originalwörterbuch wirklich ändern müssen:

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

Beachten Sie, dass wir eine Liste der leeren Schlüssel erstellen müssen, da wir ein Wörterbuch nicht ändern können, während wir es durchlaufen (wie Sie vielleicht bemerkt haben). Dies ist jedoch kostengünstiger (speichertechnisch) als das Erstellen eines brandneuen Wörterbuchs, es sei denn, es gibt viele Einträge mit leeren Werten.



2
Wenn Sie Python verwenden 3+ Sie ersetzen müssen .iteritems()mit .items(), wird der erste nicht mehr funktioniert in neuesten Python - Versionen.
Mariano Ruiz

12

Die Lösung von BrenBarn ist ideal (und pythonisch, könnte ich hinzufügen). Hier ist jedoch eine andere (fp) Lösung:

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

12

Wenn Sie einen umfassenden und dennoch prägnanten Ansatz für den Umgang mit realen Datenstrukturen wünschen, die häufig verschachtelt sind und sogar Zyklen enthalten können, empfehlen wir Ihnen, das Dienstprogramm remap aus dem Dienstprogramm von bolons zu betrachten .

Nach pip install boltonsoder Kopieren iterutils.py in Ihr Projekt, nur tun:

from boltons.iterutils import remap

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

Diese Seite enthält viele weitere Beispiele, einschließlich solcher, die mit viel größeren Objekten aus der Github-API arbeiten.

Es ist reines Python, funktioniert also überall und ist in Python 2.7 und 3.3+ vollständig getestet. Das Beste von allem ist, dass ich es für genau solche Fälle geschrieben habe. Wenn Sie also einen Fall finden, der nicht funktioniert, können Sie mich nerven, um ihn hier zu beheben .


1
Diese Lösung funktionierte hervorragend für ein ähnliches Problem: Leere Werte aus tief verschachtelten Listen in Wörterbüchern entfernen. Vielen Dank!
Nicholas Tulach

1
Dies ist gut, da Sie das Rad nicht neu erfinden und eine Lösung für verschachtelte Objekte bereitstellen. Vielen Dank!
Vekerdyb

1
Der Artikel, den Sie für Ihre Bibliothek geschrieben haben, hat mir sehr gut gefallen, und dies ist eine nützliche Bibliothek!
Lebenslogger

11

Basierend auf Ryans Lösung , wenn Sie auch Listen und verschachtelte Wörterbücher haben:

Für 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

Für 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, schöne Erweiterung! Es ist eine gute Lösung für Wörterbücher wie die folgenden:d = { "things": [{ "name": "" }] }
Ryan Shea

6

Wenn Sie ein verschachteltes Wörterbuch haben und möchten, dass dies auch für leere Unterelemente funktioniert, können Sie eine rekursive Variante von BrenBarns Vorschlag verwenden:

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

Verwenden Sie items()anstelle von iteritems()für Python 3
andydavies

6

Schnelle Antwort (TL; DR)

Beispiel01

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

Detaillierte Antwort

Problem

  • Kontext: Python 2.x.
  • Szenario: Entwickler möchten ein Wörterbuch so ändern, dass leere Werte ausgeschlossen werden
    • aka leere Werte aus einem Wörterbuch entfernen
    • aka Löschen von Schlüsseln mit leeren Werten
    • aka Filterwörterbuch für nicht leere Werte über jedes Schlüssel-Wert-Paar

Lösung

  • Beispiel01 Verwenden Sie die Python-Listenverständnis-Syntax mit einer einfachen Bedingung, um "leere" Werte zu entfernen

Tücken

  • example01 bearbeitet nur eine Kopie des Originalwörterbuchs (wird nicht geändert)
  • Beispiel01 kann zu unerwarteten Ergebnissen führen, je nachdem, was Entwickler unter "leer" versteht.
    • Bedeutet Entwickler, Werte zu behalten, die falsch sind ?
    • Wenn nicht garantiert wird, dass die Werte im Wörterbuch Zeichenfolgen sind, kann es beim Entwickler zu unerwarteten Datenverlusten kommen.
    • Ergebnis01 zeigt, dass nur drei Schlüssel-Wert-Paare aus dem ursprünglichen Satz erhalten blieben

Alternatives Beispiel

  • Beispiel 02 hilft bei der Bewältigung potenzieller Fallstricke
  • Der Ansatz besteht darin, eine genauere Definition von "leer" zu verwenden, indem die Bedingung geändert wird.
  • Hier möchten wir nur Werte herausfiltern, die zu leeren Zeichenfolgen ausgewertet werden.
  • Hier verwenden wir auch .strip (), um Werte herauszufiltern, die nur aus Leerzeichen bestehen.

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

Siehe auch



4

Aufbauend auf den Antworten von patriciasz und nneonneo , und die Möglichkeit entfallen , dass Sie die Schlüssel löschen möchten , dass nur bestimmte falsy Dinge (zB '') , aber nicht andere (zB 0), oder vielleicht wollen Sie auch einige truthy Dinge enthalten (zB 'SPAM') Dann könnten Sie eine hochspezifische Hitliste erstellen:

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

Leider funktioniert das nicht ganz, weil zum Beispiel 0 in unwantedausgewertet wird True. Wir müssen zwischen 0und anderen falschen Dingen unterscheiden, also müssen wir verwenden is:

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

... bewertet zu False.

Verwenden Sie es jetzt für deldie unerwünschten Dinge:

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]

Wenn Sie ein neues Wörterbuch möchten, anstatt es zu ändern metadata:

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

wirklich schöner Schuss, es spricht viele Probleme gleichzeitig an und es löst die Frage, danke, um es klar zu machen
jlandercy

Cool! Es funktioniert für dieses Beispiel. Es funktioniert jedoch nicht, wenn ein Element im Wörterbuch ist[]
jsga

2

Ich habe alle Antworten in diesem Thread gelesen und einige haben auch auf diesen Thread verwiesen: Entfernen Sie leere Diktate im verschachtelten Wörterbuch mit rekursiver Funktion

Ich habe hier ursprünglich eine Lösung verwendet und es hat großartig funktioniert:

Versuch 1: Zu heiß (nicht performant oder zukunftssicher) :

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

In der Python 2.7-Welt wurden jedoch einige Bedenken hinsichtlich Leistung und Kompatibilität geäußert:

  1. verwenden isinstancestatttype
  2. Rollen Sie die Liste comp foraus Effizienzgründen in einer Schleife ab
  3. Verwenden Sie itemsstattdessen Python3 Safeiteritems

Versuch 2: Zu kalt (ohne Auswendiglernen) :

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! Dies ist nicht rekursiv und überhaupt nicht memoizant.

Versuch 3: Genau richtig (bisher) :

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
es sei denn, ich bin blind, es
scheint

1

Mit Arrays gemischte Diktate

  • Die Antwort bei Versuch 3: Genau richtig (bisher) von BlissRages Antwort behandelt Array-Elemente nicht richtig. Ich füge einen Patch hinzu, falls jemand ihn braucht. Die Methode behandelt Listen mit dem Anweisungsblock von if isinstance(v, list):, der die Liste mit der ursprünglichen scrub_dict(d)Implementierung bereinigt .
    @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

genial . . . Ich hatte diese Änderung in der Codebasis vorgenommen, aber Ihren Kommentar verpasst _ / _
BlissRage

0

Eine alternative Möglichkeit besteht darin, das Wörterbuchverständnis zu verwenden. Dies sollte kompatibel sein mit2.7+

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

0

Hier ist eine Option, wenn Sie verwenden 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

Einige der oben genannten Methoden ignorieren, ob Ganzzahlen vorhanden sind, und schweben mit den Werten 0 und 0,0

Wenn jemand das oben Genannte vermeiden möchte, kann er den folgenden Code verwenden (entfernt leere Zeichenfolgen und keine Werte aus dem verschachtelten Wörterbuch und der verschachtelten Liste):

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

"Da ich derzeit auch eine Desktop-Anwendung für meine Arbeit mit Python schreibe, habe ich in einer Dateneingabeanwendung festgestellt, dass viele Eingaben vorhanden sind und einige nicht obligatorisch sind, sodass der Benutzer sie leer lassen kann. Zu Validierungszwecken ist sie leicht zu greifen alle Einträge und verwerfen dann den leeren Schlüssel oder Wert eines Wörterbuchs. Mein Code über a zeigt also, wie wir sie einfach herausnehmen können, indem wir das Wörterbuchverständnis verwenden und das Wörterbuchwertelement behalten, das nicht leer ist. Ich verwende 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'}

Bitte erwähnen Sie die Python-Version. Unterstützt sie auch die neueste Version?
HaseeB Mir

Ihre Antwort ist derzeit als minderwertig gekennzeichnet und wird möglicherweise gelöscht. Bitte stellen Sie sicher, dass Ihre Antwort neben jedem Code eine Erklärung enthält.
Tim Stack

@ TimStack Bitte empfehlen Sie das Löschen für LQ-Antworten.
10 Rep

@ 10Rep Ich empfehle nicht das Löschen für eine Antwort, die als Lösung funktioniert, aber lediglich keine beschreibenden Kommentare enthält. Ich möchte den Benutzer lieber informieren und ihm beibringen, wie eine bessere Antwort aussieht.
Tim Stack

@HasseB Mir Ich benutze die neueste Python 3.8.3
KokoEfraim

-2

Einige Benchmarking:

1. Listenverständnis neu erstellen Diktat

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. Listenverständnis Diktat neu erstellen mit 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. Schleife und Löschschlüssel, wenn v Keine ist

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 ist Loop and Delete bei 160 ns am schnellsten, das Listenverständnis ist bei ~ 375 ns halb so langsam und bei einem Aufruf von dict()ist es wieder halb so langsam ~ 680 ns.

Wenn Sie 3 in eine Funktion einwickeln, wird sie wieder auf etwa 275 ns reduziert. Auch für mich war PyPy etwa doppelt so schnell wie Neet Python.


Durch Schleifen und Löschen kann auch ein RunTimeError ausgelöst werden, da das Ändern eines Wörterbuchs während der Iteration einer Ansicht nicht gültig ist. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ah man yeah ok in Python 3 ist das wahr, aber nicht in Python 2.7, da Elemente eine Liste zurückgeben, also müssen Sie list(dic.items())Py 3 aufrufen. del scheint für ein niedriges Verhältnis von Null / Leer-Werten immer noch schneller zu sein. Ich denke, das Erstellen dieser Liste ist für den Speicherverbrauch genauso schlecht wie das Neuerstellen des Diktats.
Richard Mathie
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.