วิธีหลีกเลี่ยงข้อผิดพลาด“ RuntimeError: พจนานุกรมเปลี่ยนขนาดระหว่างการทำซ้ำ”


258

ฉันตรวจสอบคำถามอื่นทั้งหมดที่มีข้อผิดพลาดเดียวกัน แต่ยังไม่พบวิธีแก้ปัญหาที่เป็นประโยชน์ = /

ฉันมีพจนานุกรมรายการ:

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

ซึ่งบางค่าว่างเปล่า ในตอนท้ายของการสร้างรายการเหล่านี้ฉันต้องการลบรายการว่างเหล่านี้ก่อนที่จะส่งคืนพจนานุกรมของฉัน ปัจจุบันฉันพยายามทำสิ่งนี้ดังนี้:

for i in d:
    if not d[i]:
        d.pop(i)

อย่างไรก็ตามนี่เป็นข้อผิดพลาดรันไทม์ของฉัน ฉันทราบว่าคุณไม่สามารถเพิ่ม / ลบองค์ประกอบในพจนานุกรมในขณะที่วนซ้ำมัน ... สิ่งที่จะเป็นวิธีนี้

คำตอบ:


454

ใน Python 2.x การโทรkeysทำสำเนาของคีย์ที่คุณสามารถทำซ้ำได้ในขณะที่แก้ไขdict:

for i in d.keys():

โปรดทราบว่าสิ่งนี้ไม่ได้ผลใน Python 3.x เพราะkeysคืนค่าตัววนซ้ำแทนรายการ

อีกวิธีหนึ่งคือการใช้listเพื่อบังคับให้คัดลอกคีย์ที่จะทำ อันนี้ใช้ได้ใน Python 3.x:

for i in list(d):

1
ฉันเชื่อว่าคุณหมายถึง 'การโทรkeysทำสำเนาของคีย์ที่คุณสามารถทำซ้ำมากกว่า' หรือที่เรียกว่าpluralปุ่มใช่ไหม มิฉะนั้นจะวนซ้ำหนึ่งคีย์มากกว่าหนึ่งคีย์ได้อย่างไร ฉันไม่จู้จี้จุกจิกโดยวิธีการอย่างแท้จริงฉันสนใจที่จะรู้ว่าจริง ๆ แล้วมันเป็นกุญแจหรือกุญแจ
HighOnMeat

6
หรือทูเปิลแทนรายการตามที่เร็วขึ้น
Brambor

8
เพื่อชี้แจงพฤติกรรมของ python 3.x, d.keys () จะส่งกลับค่าiterable (ไม่ใช่ตัววนซ้ำ) ซึ่งหมายความว่ามันเป็นมุมมองบนกุญแจของพจนานุกรมโดยตรง การใช้for i in d.keys()งานจริงใช้งานได้ใน python 3.x โดยทั่วไปแต่เนื่องจากเป็นการวนซ้ำในมุมมองที่ทำซ้ำได้ของคีย์ของพจนานุกรมการเรียกd.pop()ระหว่างลูปจะทำให้เกิดข้อผิดพลาดเดียวกับที่คุณพบ for i in list(d)เลียนแบบการทำงานของ python 2 ที่ไม่มีประสิทธิภาพเล็กน้อยในการคัดลอกคีย์ไปยังรายการก่อนทำการวนซ้ำสำหรับสถานการณ์พิเศษเช่นคุณ
Michael Krebs

โซลูชัน python3 ของคุณไม่ทำงานเมื่อคุณต้องการลบวัตถุใน dict ด้านใน เช่นคุณมี dic A และ dict B ใน dict A หากคุณต้องการลบวัตถุใน dict B มันเกิดข้อผิดพลาด
Ali-T

1
ใน python3.x list(d.keys())สร้างเอาต์พุตเดียวกันกับการlist(d)เรียกlistใช้เพื่อdictส่งคืนคีย์ การkeysโทร (แม้ว่าจะไม่แพง) ก็ไม่จำเป็น
Sean Breckenridge

46

เพียงใช้ความเข้าใจในพจนานุกรมเพื่อคัดลอกรายการที่เกี่ยวข้องลงใน Dict ใหม่

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.iteritems() if v}
>>> d
{'a': [1], 'b': [1, 2]}

สำหรับสิ่งนี้ใน Python 3

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.items() if v}
>>> d
{'a': [1], 'b': [1, 2]}

11
d.iteritems()ทำให้ฉันมีข้อผิดพลาด ฉันใช้d.items()แทน - ใช้ python3
wcyn

4
สิ่งนี้ใช้ได้กับปัญหาที่เกิดขึ้นในคำถามของ OP อย่างไรก็ตามทุกคนที่มาที่นี่หลังจากกดปุ่ม RuntimeError นี้ในโค้ดแบบมัลติเธรดโปรดทราบว่า GIL ของ CPython สามารถเปิดตัวได้ในช่วงกลางของรายการความเข้าใจเช่นกันและคุณต้องแก้ไขมันต่างออกไป
Yirkha

40

คุณจะต้องใช้ "คัดลอก":

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

In [1]: d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

In [2]: for i in d.copy():
   ...:     if not d[i]:
   ...:         d.pop(i)
   ...:         

In [3]: d
Out[3]: {'a': [1], 'b': [1, 2]}

ทำงาน! ขอบคุณ.
Guilherme Carvalho Lithg

13

ฉันจะพยายามหลีกเลี่ยงการแทรกรายการเปล่าในตอนแรก แต่โดยทั่วไปแล้วจะใช้:

d = {k: v for k,v in d.iteritems() if v} # re-bind to non-empty

หากก่อน 2.7:

d = dict( (k, v) for k,v in d.iteritems() if v )

หรือเพียงแค่:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]

+1: ตัวเลือกสุดท้ายเป็นสิ่งที่น่าสนใจเพราะคัดลอกคีย์ของรายการเหล่านั้นที่ต้องการลบเท่านั้น สิ่งนี้อาจให้ประสิทธิภาพที่ดีขึ้นหากมีรายการจำนวนน้อยที่ต้องการลบเมื่อเทียบกับขนาดของ dict
Mark Byers

@MarkByers yup - และถ้ามีคนจำนวนมากทำแล้วผูก dict ใหม่ให้ใหม่ที่ถูกกรองเป็นตัวเลือกที่ดีกว่า มันเป็นความคาดหวังเสมอว่าโครงสร้างควรทำงานอย่างไร
Jon Clements

4
อันตรายอย่างหนึ่งกับการปฏิเสธคือถ้ามีบางอย่างในโปรแกรมมีวัตถุที่มีการอ้างอิงถึง dict เก่ามันจะไม่เห็นการเปลี่ยนแปลง หากคุณแน่ใจว่าไม่ใช่กรณีนี้ให้แน่ใจว่า ... เป็นวิธีการที่เหมาะสม แต่สิ่งสำคัญคือต้องเข้าใจว่ามันไม่เหมือนกับการปรับเปลี่ยนต้นฉบับ dict
Mark Byers

@ MarkByers เป็นจุดที่ดีมาก - คุณและฉันรู้ว่า (และอื่น ๆ อีกมากมาย) แต่ก็ไม่ชัดเจนสำหรับทุกคน และฉันจะเอาเงินไปวางบนโต๊ะมันไม่ได้กัดคุณไว้ด้านหลัง :)
Jon Clements

จุดของการหลีกเลี่ยงการแทรกรายการที่ว่างเปล่าเป็นสิ่งที่ดีมาก
Magnus Bodin


6

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

    for key in list(d):
        if not d[key]: 
            d.pop(key)

1

สำหรับสถานการณ์เช่นนี้ฉันชอบทำสำเนาลึกและวนซ้ำผ่านสำเนานั้นในขณะที่ปรับเปลี่ยน dict ดั้งเดิม

หากเขตข้อมูลการค้นหาอยู่ในรายการคุณสามารถระบุจำนวนในลูป for ของรายการจากนั้นระบุตำแหน่งเป็นดัชนีเพื่อเข้าถึงฟิลด์ใน dict ดั้งเดิม


1

สิ่งนี้ใช้ได้กับฉัน:

dict = {1: 'a', 2: '', 3: 'b', 4: '', 5: '', 6: 'c'}
for key, value in list(dict.items()):
    if (value == ''):
        del dict[key]
print(dict)
# dict = {1: 'a', 3: 'b', 6: 'c'}  

RuntimeErrorหล่อรายการพจนานุกรมรายการสร้างรายชื่อของรายการของมันเพื่อให้คุณสามารถย้ำมากกว่านั้นและหลีกเลี่ยง


1

dictc = {"stName": "asas"} keys = dictc.keys () สำหรับคีย์ในรายการ (คีย์): dictc [key.upper ()] = 'ค่าใหม่' พิมพ์ (str (dictc))


0

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

วิธีหนึ่งในการบรรลุสิ่งที่คุณต้องการคือการใช้รายการเพื่อผนวกคีย์ที่คุณต้องการลบจากนั้นใช้ฟังก์ชั่นป๊อปในพจนานุกรมเพื่อลบคีย์ที่ระบุขณะทำซ้ำผ่านรายการ

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
pop_list = []

for i in d:
        if not d[i]:
                pop_list.append(i)

for x in pop_list:
        d.pop(x)
print (d)

0

Python 3 ไม่อนุญาตให้ลบในขณะทำซ้ำ (ใช้สำหรับลูปด้านบน) พจนานุกรม มีทางเลือกต่าง ๆ ให้ทำ; วิธีง่าย ๆ อย่างหนึ่งคือการเปลี่ยนบรรทัดต่อไปนี้

for i in x.keys():

กับ

for i in list(x)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.