วิธีลบรายการออกจากพจนานุกรมในขณะที่วนซ้ำมัน


295

การลบรายการออกจากพจนานุกรมใน Python นั้นถูกต้องตามกฎหมายหรือไม่

ตัวอย่างเช่น:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

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

นี่เป็นทางออกที่ดีใช่ไหม มีวิธีที่หรูหรา / มีประสิทธิภาพมากขึ้น?


1
คำถามที่เกี่ยวข้องกับคำตอบที่น่าสนใจมาก: stackoverflow.com/questions/9023078/… .
สูงสุด

หนึ่งสามารถลองได้อย่างง่ายดาย หากล้มเหลวก็ไม่ถูกต้อง
Trilarion

26
@Trilarion One สามารถลองได้อย่างง่ายดาย ... และเรียนรู้อะไรที่มีคุณค่าอย่างง่ายดาย หากประสบความสำเร็จก็ไม่จำเป็นต้องถูกต้องตามกฎหมาย กรณีขอบและคำเตือนที่ไม่คาดคิดมากมาย คำถามนี้เป็นคำถามที่ไม่น่าสนใจสำหรับ Pythonistas การไล่ออกจากมือไปตามคำสั่งของ "ใครจะลองได้อย่างง่ายดาย!" ไม่ช่วยเหลือและขัดต่อจิตวิญญาณของการสอบถามกองซ้อน
Cecil Curry

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

1
@CecilCurry การทดสอบความคิดด้วยตัวคุณเองก่อนที่จะนำเสนอที่นี่เป็นจิตวิญญาณของ stackoverflow หากฉันไม่เข้าใจผิด นั่นคือทั้งหมดที่ฉันต้องการสื่อ ขออภัยหากมีการรบกวนใด ๆ เนื่องจากการที่ นอกจากนี้ฉันคิดว่ามันเป็นคำถามที่ดีและไม่ได้ลงคะแนน ฉันชอบคำตอบของJochen Ritzelมากที่สุด ฉันไม่คิดว่าคนเราจะต้องทำทุกอย่างที่จะลบทันทีเมื่อลบในขั้นตอนที่สองนั้นง่ายกว่ามาก นั่นควรเป็นวิธีที่ต้องการในมุมมองของฉัน
Trilarion

คำตอบ:


305

แก้ไข:

คำตอบนี้จะไม่ทำงานสำหรับ Python3 RuntimeErrorและจะให้

RuntimeError: พจนานุกรมเปลี่ยนขนาดระหว่างการวนซ้ำ

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


การทดสอบอย่างง่ายในคอนโซลแสดงว่าคุณไม่สามารถแก้ไขพจนานุกรมได้ในขณะที่วนซ้ำมัน:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

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

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}

หากคุณต้องการลบตามค่าไอเท็มให้ใช้items()เมธอดแทน:

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}

53
โปรดทราบว่าใน Python 3 dict.items () จะคืนค่าตัววนซ้ำ (และ dict.iteritems () หายไป)
Tim Lesher

83
หากต้องการอธิบายอย่างละเอียดเกี่ยวกับความคิดเห็น @TimLesher ... สิ่งนี้จะไม่ทำงานใน Python 3
สูงสุด

99
หากต้องการทำอย่างละเอียดเกี่ยวกับการทำอย่างละเอียดของ @ max มันจะทำงานถ้าคุณแปลงโค้ดข้างต้นด้วย 2to3 หนึ่งใน fixers เริ่มต้นจะทำให้มองห่วงเช่นfor k, v in list(mydict.items()):ที่ทำงานได้ดีในหลาม 3. เดียวกันสำหรับการเป็นkeys() list(keys())
วอลเตอร์ Mundt

8
มันใช้งานไม่ได้ ฉันได้รับข้อผิดพลาด:RuntimeError: dictionary changed size during iteration
Tomáš Zato - Reinstate Monica

15
@ TomášZatoตามที่ Walter ชี้ให้เห็นสำหรับ python3 คุณต้องใช้for k in list(mydict.keys()): เป็น python3 ทำให้ปุ่ม () วิธีการ iterator และยังไม่อนุญาตให้ลบรายการ dict ในระหว่างการทำซ้ำ โดยการเพิ่มรายการ () โทรคุณเปลี่ยนปุ่ม () ตัววนซ้ำลงในรายการ ดังนั้นเมื่อคุณอยู่ในเนื้อความของ for loop คุณจะไม่วนซ้ำตัวพจนานุกรมอีกต่อไป
เจฟฟ์ครอมป์ตัน

89

คุณสามารถทำได้สองขั้นตอน:

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

วิธีการโปรดของฉันคือการสร้าง dict ใหม่:

# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)

11
@senderle: ตั้งแต่ 2.7 จริง ๆ
Jochen Ritzel

5
วิธีการที่เข้าใจได้จาก dict นั้นจะทำสำเนาของพจนานุกรม โชคดีที่ค่าอย่างน้อยไม่ได้คัดลอกแบบลึกเพียงแค่เชื่อมโยง แม้ว่าคุณจะมีกุญแจจำนวนมากมันอาจจะแย่ ด้วยเหตุนี้ฉันชอบremoveวิธีการวนรอบมากขึ้น
สูงสุด

1
นอกจากนี้คุณยังสามารถรวมขั้นตอนต่าง ๆ :for k in [k for k in mydict if k == val]: del mydict[k]
เอเอ็กซ์โอ

ทางออกแรกคือทางเดียวที่มีประสิทธิภาพสำหรับ dicts ขนาดใหญ่ในเธรดนี้จนถึงขณะนี้เนื่องจากมันไม่ได้ทำสำเนาแบบเต็มความยาว
kxr

21

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

  • คัดลอกคีย์ทั้งหมด (หรือค่าหรือทั้งสองอย่างขึ้นอยู่กับสิ่งที่คุณต้องการ) จากนั้นวนซ้ำปุ่มเหล่านั้น คุณสามารถใช้.keys()et al สำหรับเรื่องนี้ (ใน Python 3 ผ่านตัววนซ้ำผลลัพธ์ไปที่list) อาจเสียพื้นที่อย่างชาญฉลาดแม้ว่า
  • ย้ำกว่าตามปกติประหยัดกุญแจที่จะลบในคอลเลกชันที่แยกต่างหากmydict to_deleteเมื่อคุณทำเสร็จ iterating mydictลบรายการทั้งหมดในจากto_delete mydictบันทึกบางพื้นที่ (ขึ้นอยู่กับจำนวนคีย์ที่ถูกลบและจำนวนที่อยู่) ในวิธีแรก แต่ยังต้องใช้อีกสองสามบรรทัด

You can't modify a collection while iterating it.นี่เป็นเพียงแก้ไขสำหรับ dicts และเพื่อน ๆ แต่คุณสามารถแก้ไขรายการในระหว่างการทำซ้ำ:L = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
Nils Lindemann

3
@ ไม่มีมันไม่ได้เกิดข้อยกเว้น แต่ก็ยังไม่ถูก สังเกต: codepad.org/Yz7rjDVT - ดูเช่นstackoverflow.com/q/6260089/395760สำหรับคำอธิบาย

รับฉันที่นี่ ยังคงcan'tถูกต้องเฉพาะสำหรับ dict และเพื่อน ๆ ในขณะที่มันควรจะเป็นshouldn'tสำหรับรายการ
Nils Lindemann

21

ทำซ้ำสำเนามากกว่าเช่นสำเนาที่ส่งคืนโดยitems():

for k, v in list(mydict.items()):

1
นั่นไม่สมเหตุสมผลนัก - แล้วคุณไม่สามารถทำได้del vโดยตรงดังนั้นคุณจึงได้ทำสำเนาของ v แต่ละตัวที่คุณไม่เคยใช้และคุณต้องเข้าถึงรายการต่างๆโดยใช้ปุ่มต่อไป dict.keys()เป็นทางเลือกที่ดีกว่า
jscs

2
@ จอช: มันทั้งหมดขึ้นอยู่กับว่าคุณจะต้องใช้vเป็นเกณฑ์ในการลบ
Ignacio Vazquez-Abrams

3
ภายใต้ Python 3 dict.items()ส่งคืนตัววนซ้ำมากกว่าการคัดลอก ดูความเห็นสำหรับคำตอบของBlairซึ่ง (น่าเศร้า) ก็ถือว่า Python 2 semantics
เซซิลแกงกะหรี่

11

มันสะอาดที่สุดที่จะใช้list(mydict):

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
{'four': 4, 'two': 2, 'one': 1}

สิ่งนี้สอดคล้องกับโครงสร้างแบบขนานสำหรับรายการ:

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

ทั้งทำงานใน python2 และ python3


สิ่งนี้ไม่ดีในกรณีที่ชุดข้อมูลของคุณมีขนาดใหญ่ นี่เป็นการคัดลอกวัตถุทั้งหมดในหน่วยความจำใช่ไหม
AFP_555

1
@ AFP_555 ใช่ - เป้าหมายของฉันที่นี่มีไว้เพื่อโค้ดที่ชัดเจนแบบขนาน หากคุณต้องการประสิทธิภาพของหน่วยความจำวิธีที่ดีที่สุดที่ฉันรู้จักคือการวนซ้ำและสร้างรายการของคีย์เพื่อลบหรือรายการใหม่ของรายการที่จะบันทึก ความงามเป็นสิ่งที่ฉันให้ความสำคัญกับ Python สำหรับชุดข้อมูลขนาดใหญ่ที่ฉันใช้ Go หรือ Rust
rsanden

9

คุณสามารถใช้ความเข้าใจในพจนานุกรม

d = {k:d[k] for k in d if d[k] != val}


นี่คือ Pythonic ที่สุด
Yehosef

แต่มันจะสร้างพจนานุกรมใหม่แทนของการปรับเปลี่ยนdในสถานที่
Aristide

9

ด้วย python3, ทำซ้ำใน dic.keys () จะเพิ่มข้อผิดพลาดขนาดพจนานุกรม คุณสามารถใช้ทางเลือกนี้:

ทดสอบกับ python3 ทำงานได้ดีและข้อผิดพลาด " พจนานุกรมเปลี่ยนขนาดระหว่างการทำซ้ำ " ไม่ได้ถูกยกขึ้น:

my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# {}

4

คุณสามารถสร้างรายการของคีย์เพื่อลบจากนั้นทำซ้ำในรายการนั้นเพื่อลบทิ้ง

dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]

มันค่อนข้างจะเหมือนกันกับโซลูชั่นที่ 1 ของ @ Ritzel (มีประสิทธิภาพใน dicts ขนาดใหญ่ที่ไม่มีสำเนาเต็ม) แม้ว่าจะมีความเข้าใจในรายการ "long read" w / o กระนั้นมันเร็วขึ้นหรือไม่
kxr

3

มีวิธีที่อาจเหมาะสมถ้ารายการที่คุณต้องการลบอยู่เสมอที่ "เริ่มต้น" ของการทำซ้ำ dict

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

"เริ่มต้น" นั้นรับประกันว่าจะสอดคล้องกับ Python บางรุ่น / การใช้งานบางอย่างเท่านั้น ตัวอย่างจากมีอะไรใหม่ใน Python 3.7

ลักษณะการเก็บรักษาคำสั่งแทรกของวัตถุ dict ได้รับการประกาศให้เป็นส่วนหนึ่งอย่างเป็นทางการของข้อมูลจำเพาะภาษา Python

วิธีนี้จะหลีกเลี่ยงสำเนาของ dict ที่คำตอบอื่น ๆ จำนวนมากแนะนำอย่างน้อยใน Python 3


1

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

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.