Boş dizeleri olan anahtarları bir dikteden kaldırmanın etkili yolu


116

Bir emrim var ve boş değer dizeleri olan tüm anahtarları kaldırmak istiyorum.

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

Bunu yapmanın en iyi yolu nedir?

Yanıtlar:


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}

Tüm anahtarlarınızın değerleri olduğunu unutmayın. Sadece bu değerlerden bazıları boş dizedir. Değeri olmayan bir diktede anahtar diye bir şey yoktur; bir değeri olmasaydı, diktede olmazdı.


29
+1. Bunun aslında mevcut bir sözlükten anahtarları kaldırmadığını unutmamak önemlidir. Bunun yerine yeni bir sözlük yaratır. Genellikle bu tam olarak birinin istediği şeydir ve muhtemelen OP'nin ihtiyacı olan şeydir, ancak OP'nin istediği şey bu değildir.
Steven Rumbalski

18
Bu aynı zamanda v = 0'ı da öldürür, bu iyi, eğer isteniyorsa.
Paul

2
Bu aynı zamanda v = False olur, bu tam olarak OP'nin sorduğu şey değildir .
Amir

4
@shredding: Yani demek istiyorsun .items().
BrenBarn

6
{k: v for k, v in metadata.items() if v is not None}
Python'un

75

BrenBarn'ın çözümünden bile daha kısa olabilir (ve bence daha okunaklı)

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

Python 2.7.3 ile test edilmiştir.


13
Bu aynı zamanda sıfır değerleri de öldürür.
Paul

10
0'ı (sıfır) korumak için şu şekilde kullanabilirsiniz ... if v!=None: {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v for k, v in metadata.items () if v! = None} boş dizelerden kurtulmaz.
philgo20

1
sözlük anlamaları yalnızca önceki sürümlerle uyumluluk için Python 2.7+ ile desteklenir, lütfen @ BrenBarn'ın çözümünü kullanın.
Pavan Gupta

12
Her zaman Hiçbiri ile "değil", "! =" Yerine karşılaştırılmalıdır. stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Orijinal sözlüğü gerçekten değiştirmeniz gerekiyorsa:

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

Boş anahtarların bir listesini yapmamız gerektiğini unutmayın, çünkü bir sözlüğü yinelerken değiştiremeyiz (fark etmiş olabileceğiniz gibi). Bu, boş değerlere sahip çok sayıda giriş olmadığı sürece, yepyeni bir sözlük oluşturmaktan daha ucuzdur (bellek açısından).


bu aynı zamanda 0 değerini de kaldırır ve 0 boş değildir
JVK

2
Eğer değiştirmek zorunda 3+ Python kullanıyorsanız .iteritems()ile .items(), öncelikle en son Python sürümleri artık çalışmaz.
Mariano Ruiz

12

BrenBarn'ın çözümü idealdir (ve ekleyebilirim pythonic). Bununla birlikte, işte başka bir (fp) çözümü:

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

12

Genellikle iç içe geçmiş ve hatta döngüleri içerebilen gerçek dünya veri yapılarını işlemek için tam özellikli, ancak özlü bir yaklaşım istiyorsanız , boltons yardımcı program paketinden remap yardımcı programına bakmanızı öneririm .

İterutils.py'yi projenize pip install boltonskopyaladıktan veya kopyaladıktan sonra şunları yapın:

from boltons.iterutils import remap

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

Bu sayfada , Github API'sinden çok daha büyük nesnelerle çalışanlar dahil olmak üzere daha birçok örnek var.

Saf Python, bu yüzden her yerde çalışıyor ve Python 2.7 ve 3.3+ ile tamamen test edildi. Hepsinden iyisi, bunu tam olarak bunun gibi durumlar için yazdım, bu yüzden ele alınmadığı bir vaka bulursanız, hemen burada düzeltmem için beni rahatsız edebilirsiniz .


1
Bu çözüm, sahip olduğum benzer bir sorun için harika çalıştı: sözlüklerin içindeki derinlemesine iç içe geçmiş listelerden boş değerleri çıkarmak. Teşekkürler!
Nicholas Tulach

1
Tekerleği yeniden icat etmediğiniz ve iç içe geçmiş nesneler için bir çözüm sunmadığınız için bu iyidir. Teşekkürler!
vekerdyb

1
Kitaplığınız için yazdığınız makaleyi gerçekten beğendim ve bu kullanışlı bir kitaplık!
lifelogger

11

Ryan'ın çözümüne dayanarak, listeleriniz ve iç içe geçmiş sözlükleriniz varsa:

Python 2 için:

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

Python 3 için:

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, güzel uzantı! Aşağıdaki gibi sözlükler için iyi bir çözümdür:d = { "things": [{ "name": "" }] }
Ryan Shea

6

İç içe geçmiş bir sözlüğünüz varsa ve bunun boş alt öğeler için bile çalışmasını istiyorsanız, BrenBarn'ın önerisinin özyinelemeli bir varyantını kullanabilirsiniz:

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

Python 3 items()yerine kullanıniteritems()
andydavies

6

Hızlı Cevap (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'}
'''

Ayrıntılı Cevap

Sorun

  • Bağlam: Python 2.x
  • Senaryo: Geliştirici, boş değerleri hariç tutmak için bir sözlüğü değiştirmek istiyor
    • aka bir sözlükten boş değerleri kaldırın
    • aka boş değerli anahtarları sil
    • aka her bir anahtar / değer çifti üzerindeki boş olmayan değerler için filtre sözlüğü

Çözüm

  • example01 "boş" değerleri kaldırmak için basit koşullu python liste-anlama sözdizimini kullanın

tuzaklar

  • example01 yalnızca orijinal sözlüğün bir kopyasında çalışır (yerinde değiştirilmez)
  • example01, geliştiricinin "boş" ile ne demek istediğine bağlı olarak beklenmedik sonuçlar üretebilir
    • Geliştirici yanlış olan değerleri korumak mı istiyor ?
    • Sözlükteki değerlerin dizge olması garanti edilmezse, geliştiricinin beklenmedik veri kaybı olabilir.
    • sonuç01, orijinal kümeden yalnızca üç anahtar / değer çiftinin korunduğunu gösterir

Alternatif örnek

  • example02, olası tuzaklarla başa çıkmaya yardımcı olur
  • Yaklaşım, koşulu değiştirerek daha kesin bir "boş" tanımı kullanmaktır.
  • Burada, yalnızca boş dizeler olarak değerlendirilen değerleri filtrelemek istiyoruz.
  • Burada ayrıca, sadece boşluklardan oluşan değerleri filtrelemek için .strip () kullanıyoruz.

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

Ayrıca bakınız



4

Patriciasz ve nneonneo'dan gelen yanıtlara dayanarak ve yalnızca belirli sahte şeyleri içeren (örneğin '') ancak diğerlerine sahip olmayan (örneğin ) anahtarları silmek isteyebileceğiniz olasılığını hesaba katarak 0veya belki bazı doğru şeyleri dahil etmek isteyebilirsiniz (örneğin 'SPAM') , o zaman oldukça spesifik bir isabet listesi oluşturabilirsiniz:

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

Ne yazık ki, bu pek işe yaramıyor, çünkü örneğin 0 in unwanteddeğerlendiriyor True. 0Diğer sahte şeyleri ayırt etmemiz gerekiyor , bu yüzden kullanmalıyız is:

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

... olarak değerlendirilir False.

Şimdi onu delistenmeyen şeylerde kullanın:

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]

Yerinde değişiklik metadatayapmak yerine yeni bir sözlük istiyorsanız :

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

gerçekten güzel bir çekim, aynı anda birçok sorunu ele alıyor ve soruyu çözüyor, netleştirmek için teşekkürler
jlandercy

Güzel! Bu örnek için çalışıyor. Ancak, sözlükteki bir öğe[]
jsga

2

Bu konudaki tüm yanıtları okudum ve bazıları da bu konuya atıfta bulundu: İç içe geçmiş sözlükteki boş sözcükleri özyinelemeli işlevle kaldırın

Başlangıçta çözümü burada kullandım ve harika çalıştı:

Deneme 1: Çok Sıcak (performanslı veya geleceğe dönük değil) :

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

Ancak Python 2.7 dünyasında bazı performans ve uyumluluk endişeleri ortaya çıktı:

  1. isinstanceyerine kullantype
  2. forverimlilik için liste kompozisyonunu döngü haline getirin
  3. itemsyerine python3 safe kullanıniteritems

Deneme 2: Çok Soğuk (Hatırlama Yoktur) :

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! Bu yinelemeli değildir ve hiç de hatırlatıcı değildir.

Deneme 3: Tam Doğru (şimdiye kadar) :

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
kör olmadıkça, 2 ve 3 numaralı denemelerin tamamen aynı olduğunu düşünüyorum ...
luckyguy73

1

Dizilerle karışık diktler

  • En cevap Girişimi 3: Just Right (şimdiye kadar) den BlissRage cevabı düzgün diziler unsurları işlemez. Herhangi birinin ihtiyacı olması durumunda bir yama ekliyorum. Yöntem, listeyi if isinstance(v, list):orijinal scrub_dict(d)uygulamayı kullanarak temizleyen ifade bloğuna sahip tutamaçlar listesidir .
    @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

harika. . . bu değişikliği kod tabanında yaptım ancak yorumunuzu kaçırdım _ / _
BlissRage

0

Bunu yapmanın alternatif bir yolu, sözlük anlamayı kullanmaktır. Bu uyumlu olmalıdır2.7+

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

0

İşte kullanıyorsanız bir seçenek 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

Yukarıda bahsedilen Yöntemlerden bazıları, herhangi bir tam sayı varsa ve 0 ve 0.0 değerlerine sahip kayan nokta varsa yok sayar.

Birisi yukarıdakilerden kaçınmak isterse aşağıdaki kodu kullanabilir (iç içe geçmiş sözlük ve iç içe geçmiş listeden boş dizeleri ve Yok değerleri kaldırır):

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

"Şu anda Python ile çalışmam için bir masaüstü uygulaması da yazdığım için, veri girişi uygulamasında çok sayıda giriş olduğunda ve bazıları zorunlu olmadığında kullanıcı bunu boş bırakabilir, doğrulama amacıyla kapmak kolay tüm girişler ve sonra bir sözlüğün boş anahtarını veya değerini atın. Bu yüzden, bir sözlüğün üzerindeki kodum, sözlük anlamasını kullanarak bunları nasıl kolayca çıkarabileceğimizi ve boş olmayan sözlük değer öğesini saklayabileceğimizi gösterir. Python 3.8.3 kullanıyorum.

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

Lütfen python sürümünden bahsedin, ayrıca en son sürümü destekleyecek mi?
HaseeB Mir

Cevabınız şu anda düşük kaliteli silinebilir olarak işaretlendi. Lütfen cevabınızın herhangi bir kodun dışında bir açıklama içerdiğinden emin olun.
Tim Stack

@TimStack LQ cevapları için lütfen silme öneriniz.
10

@ 10Rep Çözüm olarak işe yarayabilecek ancak yalnızca açıklayıcı yorumlardan yoksun olan bir cevabın silinmesini önermeyeceğim. Kullanıcıyı bilgilendirmeyi ve onlara daha iyi bir cevabın neye benzediğini öğretmeyi tercih ederim.
Tim Stack

@HasseB Mir En son Python 3.8.3'ü kullanıyorum
KokoEfraim

-2

Bazı kıyaslamalar:

1. Liste anlama dikteyi yeniden oluşturma

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. Anlama listesini dict () kullanarak dikteyi yeniden oluşturun

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. v, Yok ise anahtarı döngüye alın ve silin

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

bu yüzden döngü ve silme 160ns'de en hızlı olanıdır, liste anlama ~ 375ns'de yarısı kadar yavaştır ve bir çağrı ile dict()yine ~ 680ns'nin yarısı kadar yavaştır.

3'ü bir işleve sarmak, onu tekrar yaklaşık 275ns'ye düşürür. Ayrıca benim için PyPy, neet python'dan yaklaşık iki kat daha hızlıydı.


Döngü ve silme, bir görünümü yinelerken bir sözlüğü değiştirmek geçerli olmadığından bir RunTimeError da atabilir. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ah adamım evet tamam python 3'te bu doğru ancak python 2.7'de değil, öğeler bir liste döndürdüğü için list(dic.items())py 3'te çağırmanız gerekiyor . Öyleyse dict anlayış ftw? del, Null / boş değerlerin düşük bir oranı için hala daha hızlı görünüyor. Sanırım bu listeyi oluşturmak hafıza tüketimi için dikteyi yeniden oluşturmaktan daha kötü.
Richard Mathie
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.