วิธีที่มีประสิทธิภาพในการลบคีย์ที่มีสตริงว่างจากคำสั่ง


116

ฉันมีคำสั่งและต้องการลบคีย์ทั้งหมดที่มีสตริงค่าว่าง

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

วิธีที่ดีที่สุดในการทำคืออะไร?

คำตอบ:


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}

โปรดทราบว่าคีย์ทั้งหมดของคุณมีค่า เป็นเพียงค่าบางส่วนที่เป็นสตริงว่าง ไม่มีสิ่งที่เรียกว่าคีย์ในคำสั่งที่ไม่มีค่า ถ้าไม่มีค่าก็จะไม่อยู่ใน dict


29
+1 โปรดทราบว่าสิ่งนี้ไม่ได้ลบคีย์ออกจากพจนานุกรมที่มีอยู่ แต่เป็นการสร้างพจนานุกรมใหม่ โดยปกติแล้วนี่คือสิ่งที่ใครบางคนต้องการและอาจเป็นสิ่งที่ OP ต้องการ แต่ไม่ใช่สิ่งที่ OP ขอ
Steven Rumbalski

18
นอกจากนี้ยังฆ่า v = 0 ซึ่งก็ใช้ได้ถ้านั่นคือสิ่งที่ต้องการ
พอล

2
สิ่งนี้ยังทำให้ v = False ไม่ตรงตามที่ OP ถาม
Amir

4
@shredding: .items()คุณหมายถึง
BrenBarn

6
สำหรับ python รุ่นที่ใหม่กว่าคุณควรใช้ตัวสร้างพจนานุกรม:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

อาจสั้นกว่าโซลูชันของ BrenBarn ด้วยซ้ำ (และฉันคิดว่าอ่านได้มากกว่า)

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

ทดสอบด้วย Python 2.7.3


13
นอกจากนี้ยังฆ่าค่าศูนย์
พอล

10
หากต้องการรักษา 0 (ศูนย์) คุณสามารถใช้... if v!=Noneดังนี้ {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v for k, v ใน metadata.items () if v! = None} จะไม่กำจัดสตริงว่าง
philgo20

1
ความเข้าใจในพจนานุกรมรองรับเฉพาะ Python 2.7+ เท่านั้นสำหรับความเข้ากันได้กับเวอร์ชันก่อนหน้าโปรดใช้โซลูชันของ @ BrenBarn
Pavan Gupta

12
ควรเปรียบเทียบ None กับ 'is not' แทน '! =' stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

หากคุณต้องการแก้ไขพจนานุกรมต้นฉบับจริงๆ:

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

โปรดทราบว่าเราต้องสร้างรายการของคีย์ว่างเนื่องจากเราไม่สามารถแก้ไขพจนานุกรมได้ในขณะที่ทำซ้ำผ่าน (ดังที่คุณอาจสังเกตเห็น) สิ่งนี้มีราคาถูกกว่า (ใช้หน่วยความจำ) ได้ดีกว่าการสร้างพจนานุกรมใหม่ล่าสุดเว้นแต่จะมีรายการจำนวนมากที่มีค่าว่างเปล่า


สิ่งนี้จะลบค่า 0 และ 0 ออกด้วย
JVK

2
หากคุณกำลังใช้งูหลาม 3+ คุณต้องเปลี่ยน.iteritems()ด้วย.items()เป็นครั้งแรกที่ไม่ได้ทำงานอีกต่อไปในรุ่นล่าสุดหลาม
Mariano Ruiz


12

หากคุณต้องการวิธีการเต็มรูปแบบยังรวบรัดถึงการจัดการที่แท้จริงของโลกโครงสร้างข้อมูลซึ่งมักจะซ้อนกันและยังสามารถมีรอบผมขอแนะนำให้มองหาที่ยูทิลิตี้ remap จากแพคเกจยูทิลิตี้

หลังจากpip install boltonsหรือคัดลอกiterutils.pyไปยังโครงการของคุณให้ทำดังนี้

from boltons.iterutils import remap

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

หน้านี้มีตัวอย่างอื่น ๆ อีกมากมายรวมถึงตัวอย่างที่ทำงานกับออบเจ็กต์ขนาดใหญ่กว่ามากจาก API ของ Github

เป็น Python แท้ดังนั้นจึงใช้งานได้ทุกที่และได้รับการทดสอบอย่างสมบูรณ์ใน Python 2.7 และ 3.3+ ดีที่สุดของทั้งหมดที่ผมเขียนมันว่ากรณีเช่นนี้ดังนั้นถ้าคุณพบกรณีที่มันไม่ได้จัดการให้คุณสามารถที่ฉันไม่ชอบที่จะแก้ไขได้ที่นี่


1
วิธีนี้ใช้งานได้ดีสำหรับปัญหาที่คล้ายกันที่ฉันมี: การลอกค่าว่างจากรายการที่ซ้อนกันลึก ๆ ภายในพจนานุกรม ขอบคุณ!
Nicholas Tulach

1
นี่เป็นสิ่งที่ดีเนื่องจากคุณไม่ได้คิดค้นล้อใหม่และจัดหาวิธีแก้ปัญหาสำหรับวัตถุที่ซ้อนกัน ขอบคุณ!
vekerdyb

1
ฉันชอบบทความที่คุณเขียนสำหรับห้องสมุดของคุณมากและนี่คือห้องสมุดที่มีประโยชน์!
lifelogger

11

ตามวิธีการแก้ปัญหาของ Ryanหากคุณมีรายการและพจนานุกรมที่ซ้อนกัน:

สำหรับ 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

สำหรับ 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
ฮานามสกุลดี! เป็นทางออกที่ดีสำหรับพจนานุกรมดังต่อไปนี้d = { "things": [{ "name": "" }] }
Ryan Shea

6

หากคุณมีพจนานุกรมแบบซ้อนกันและคุณต้องการให้ใช้งานได้แม้กระทั่งสำหรับองค์ประกอบย่อยที่ว่างเปล่าคุณสามารถใช้คำแนะนำของ 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

ใช้items()แทนiteritems()Python 3
andydavies

6

คำตอบด่วน (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'}
'''

คำตอบโดยละเอียด

ปัญหา

  • บริบท: Python 2.x
  • สถานการณ์จำลอง:นักพัฒนาต้องการแก้ไขพจนานุกรมเพื่อไม่รวมค่าว่าง
    • aka ลบค่าว่างออกจากพจนานุกรม
    • aka ลบคีย์ที่มีค่าว่าง
    • aka กรองพจนานุกรมสำหรับค่าที่ไม่ว่างในแต่ละคู่คีย์ - ค่า

สารละลาย

  • example01 ใช้ไวยากรณ์รายการความเข้าใจ python ที่มีเงื่อนไขง่ายๆเพื่อลบค่า "ว่าง"

ผิดพลาด

  • example01 ทำงานเฉพาะกับสำเนาของพจนานุกรมต้นฉบับเท่านั้น (ไม่ได้แก้ไขในสถานที่)
  • example01 อาจให้ผลลัพธ์ที่ไม่คาดคิดขึ้นอยู่กับว่าผู้พัฒนาหมายถึงอะไรโดย "ว่าง"
    • ผู้พัฒนาหมายถึงการเก็บค่าที่เป็นเท็จหรือไม่?
    • หากค่าในพจนานุกรมไม่จำเป็นต้องเป็นสตริงผู้พัฒนาอาจสูญเสียข้อมูลโดยไม่คาดคิด
    • result01 แสดงให้เห็นว่ามีคู่คีย์ - ค่าเพียงสามคู่เท่านั้นที่ถูกเก็บรักษาไว้จากชุดเดิม

ตัวอย่างอื่น

  • example02 ช่วยจัดการกับข้อผิดพลาดที่อาจเกิดขึ้น
  • แนวทางคือการใช้คำจำกัดความที่ชัดเจนยิ่งขึ้นของ "ว่าง" โดยการเปลี่ยนเงื่อนไข
  • ที่นี่เราต้องการกรองเฉพาะค่าที่ประเมินเป็นสตริงว่างเท่านั้น
  • นอกจากนี้เรายังใช้ .strip () เพื่อกรองค่าที่ประกอบด้วยช่องว่างเท่านั้น

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

ดูสิ่งนี้ด้วย



4

สร้างจากคำตอบจากpatriciaszและnneonneoและพิจารณาถึงความเป็นไปได้ที่คุณอาจต้องการลบคีย์ที่มีเพียงบางสิ่งที่ไม่ถูกต้อง (เช่น'') แต่ไม่ใช่คีย์อื่น ๆ (เช่น0) หรือบางทีคุณอาจต้องการรวมสิ่งที่แท้จริงไว้ด้วย (เช่น'SPAM') จากนั้นคุณสามารถสร้าง Hitlist ที่เฉพาะเจาะจงมาก:

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

แต่น่าเสียดายที่นี้ไม่ได้ค่อนข้างทำงานเพราะตัวอย่างประเมิน0 in unwanted Trueเราจำเป็นต้องแยกแยะระหว่าง0สิ่งที่เป็นเท็จและอื่น ๆ ดังนั้นเราจึงต้องใช้is:

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

... ประเมินเป็นFalse.

ตอนนี้ใช้กับdelสิ่งที่ไม่ต้องการ:

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]

หากคุณต้องการพจนานุกรมใหม่แทนที่จะแก้ไขmetadataในสถานที่:

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

ช็อต
เด็ด

เย็น! มันใช้ได้กับตัวอย่างนี้ อย่างไรก็ตามจะไม่ทำงานเมื่อรายการในพจนานุกรมคือ[]
jsga

2

ฉันอ่านคำตอบทั้งหมดในเธรดนี้และบางส่วนก็อ้างถึงเธรดนี้ด้วย: ลบคำสั่งที่ว่างเปล่าในพจนานุกรมที่ซ้อนกันด้วยฟังก์ชันเรียกซ้ำ

เดิมทีฉันใช้โซลูชันที่นี่และใช้งานได้ดี:

ความพยายามที่ 1: ร้อนเกินไป (ไม่ใช่นักแสดงหรือหลักฐานในอนาคต) :

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 2.7:

  1. ใช้isinstanceแทนtype
  2. คลายรายการคอมพ์เป็นforลูปเพื่อประสิทธิภาพ
  3. ใช้ python3 ปลอดภัยitemsแทนiteritems

ความพยายามที่ 2: เย็นเกินไป (ไม่มีการจดจำ) :

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! นี่ไม่ใช่การเรียกซ้ำและไม่ใช่การท่องจำเลย

ความพยายาม 3: ถูกต้อง (จนถึงตอนนี้) :

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
เว้นแต่ฉันจะตาบอดฉันมองว่าความพยายามครั้งที่ 2 และ 3 นั้นเหมือนกันทุก
ประการ

1

Dicts ผสมกับ Arrays

  • คำตอบที่Attempt 3: Just Right (จนถึงตอนนี้)จากคำตอบของ BlissRageไม่ได้จัดการองค์ประกอบอาร์เรย์อย่างถูกต้อง ฉันรวมแพทช์เผื่อว่าใครต้องการ วิธีนี้คือจัดการรายการที่มีบล็อกคำสั่งif isinstance(v, list):ซึ่งจะขัดรายการโดยใช้การscrub_dict(d)ใช้งานดั้งเดิม
    @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

น่ากลัว . . ฉันได้ทำการเปลี่ยนแปลงนี้ในฐานรหัส แต่พลาดความคิดเห็นของคุณ _ / _
BlissRage

0

อีกทางเลือกหนึ่งที่คุณสามารถทำได้คือการใช้พจนานุกรมเพื่อความเข้าใจ สิ่งนี้ควรเข้ากันได้กับ2.7+

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

0

นี่คือตัวเลือกหากคุณใช้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

วิธีการบางอย่างที่กล่าวถึงข้างต้นจะเพิกเฉยหากมีจำนวนเต็มและลอยด้วยค่า 0 & 0.0

หากมีคนต้องการหลีกเลี่ยงข้างต้นสามารถใช้รหัสด้านล่าง (ลบสตริงว่างและไม่มีค่าออกจากพจนานุกรมที่ซ้อนกันและรายการที่ซ้อนกัน):

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

"ในขณะที่ฉันเขียนแอปพลิเคชันเดสก์ท็อปสำหรับการทำงานกับ Python ฉันพบในแอปพลิเคชันป้อนข้อมูลเมื่อมีรายการจำนวนมากและบางรายการไม่บังคับดังนั้นผู้ใช้สามารถเว้นว่างไว้เพื่อวัตถุประสงค์ในการตรวจสอบความถูกต้องจึงง่ายต่อการคว้า รายการทั้งหมดแล้วละทิ้งคีย์หรือค่าของพจนานุกรมที่ว่างเปล่าดังนั้นโค้ดของฉันด้านบนจะแสดงวิธีที่เราสามารถนำออกได้อย่างง่ายดายโดยใช้ความเข้าใจในพจนานุกรมและเก็บองค์ประกอบค่าพจนานุกรมซึ่งไม่ว่างฉันใช้ 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'}

โปรดระบุเวอร์ชัน python ด้วยจะรองรับเวอร์ชันล่าสุดหรือไม่
HaseeB Mir

ขณะนี้คำตอบของคุณถูกตั้งค่าสถานะว่าคุณภาพต่ำอาจถูกลบ โปรดตรวจสอบให้แน่ใจว่าคำตอบของคุณมีคำอธิบายนอกเหนือจากรหัสใด ๆ
Tim Stack

@TimStack โปรดแนะนำการลบสำหรับคำตอบ LQ
10 ตัวแทน

@ 10Rep ฉันจะไม่แนะนำให้ลบคำตอบที่อาจใช้เป็นวิธีแก้ปัญหาได้ แต่ขาดความคิดเห็นเชิงบรรยาย ฉันอยากจะแจ้งให้ผู้ใช้ทราบและสอนพวกเขาว่าคำตอบที่ดีกว่านั้นเป็นอย่างไร
Tim Stack

@HasseB Mir ฉันใช้ Python 3.8.3 ล่าสุด
KokoEfraim

-2

การเปรียบเทียบบางส่วน:

1. รายการความเข้าใจสร้างคำสั่งขึ้นมาใหม่

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. รายการความเข้าใจสร้างคำสั่งใหม่โดยใช้ 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. วนซ้ำและลบคีย์ถ้า v เป็นไม่มี

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

ดังนั้นการวนซ้ำและการลบจึงเร็วที่สุดที่ 160ns ความเข้าใจในรายการจึงช้าลงครึ่งหนึ่งที่ ~ 375ns และการโทรจะdict()ช้าลงครึ่งหนึ่งอีกครั้ง ~ 680ns

การห่อ 3 ลงในฟังก์ชันจะทำให้กลับลงมาเหลือประมาณ 275ns อีกครั้ง สำหรับฉันแล้ว PyPy เร็วกว่า neet python ประมาณสองเท่า


การวนซ้ำและการลบอาจทำให้เกิด RunTimeError ได้เนื่องจากไม่สามารถแก้ไขพจนานุกรมได้ในขณะที่ทำซ้ำมุมมอง docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

อาชายใช่ตกลงใน python 3 ที่เป็นจริง แต่ไม่ใช่ใน python 2.7 เนื่องจากรายการส่งคืนรายการดังนั้นคุณต้องเรียกlist(dic.items())ใน py 3 Dict comp understandion ftw แล้ว? เดลยังดูเหมือนเร็วกว่าสำหรับอัตราส่วนที่ต่ำของค่า Null / ว่าง ฉันเดาว่าการสร้างรายการนั้นไม่ดีต่อการใช้หน่วยความจำมากกว่าแค่การสร้างคำสั่งใหม่
Richard Mathie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.