2017-06-26 3 views
6

Der Ausgang desÄndern eines Wörterbuchs während des Iterierens. Fehler in Python dict?

d = {1: 1} 
for k in d.keys(): 
    d['{}'.format(k)] = d.pop(k) 
print(d) 

{'1': 1} ist. Die Ausgabe von

d = {1: 1} 
for k in d.keys(): 
    d['i{}'.format(k)] = d.pop(k) 
print(d) 

ist {'iiiii1': 1}. Ist das ein Fehler? Ich laufe Python 3.6.1 :: Anaconda 4.4.0 (x86_64).

+9

Warum sollte das ein Fehler sein? Sie entfernen und fügen Schlüssel * hinzu, während Sie iterieren *. Du hast Glück, dass du nicht in einer Endlosschleife gelandet bist. –

+3

Niemals * eine Sammlung ändern, während * darüber * iteriert wird. Vor allem keine Wörterbücher, da es zu merkwürdigem Verhalten führen kann. –

+0

@ user2725109: Woher weißt du, dass diese erste Schleife nur einmal ausgeführt wurde? Für alles, was Sie wissen, lief es 1000 mal. –

Antwort

13

Nein, das ist kein Fehler. Dies ist in der Tat explicitly documented:

Schlüssel und Werte werden in einer beliebigen Reihenfolge iteriert, die nicht zufällig ist, variiert über Python-Implementierungen, und ist abhängig von der Geschichte des Wörterbuch von Einfügungen und Löschungen. Wenn Schlüssel-, Werte- und Elementansichten ohne intervenierende Änderungen am Wörterbuch durchlaufen werden, entspricht die Reihenfolge der Elemente direkt.

[...]

Iterieren Ansichten beim Hinzufügen oder Einträge im Wörterbuch löschen kann eine RuntimeError erhöhen oder scheitern alle Einträge iterieren.

Kühne Betonung meins.

Sie iterieren über die Schlüssel, während Sie gleichzeitig Einträge im Wörterbuch hinzufügen und löschen. Das funktioniert für ein paar Iterationen, und dann schlagen Sie eine nicht über alle Einträge iterieren Fall und Iteration gestoppt.

Was passiert ist, dass Sie eine Re-Größe bei 6 Additionen auslösen, und dass die Iteration an diesem Punkt fehlschlägt; Der 'nächste' Schlüssel ist jetzt in einen 'früheren' Slot gesteckt. Dies geschieht für beiden Tests, Sie erkennen einfach nicht 5 mal in beiden Fällen iteriert:

>>> d = {1: 1} 
>>> for i, k in enumerate(d): 
...  print(i) 
...  d['{}'.format(k)] = d.pop(k) 
... 
0 
1 
2 
3 
4 
>>> d = {1: 1} 
>>> for i, k in enumerate(d): 
...  print(i) 
...  d['i{}'.format(k)] = d.pop(k) 
... 
0 
1 
2 
3 
4 

Es 5 mal ausgeführt wird, da die aktuelle dict Implementierung mit einem hash table of size 8 beginnt, und ein resize ausgelöst when the table is 2/3s full (Ihre erste dict hat 1, 5-Einfügungen machen > (8 * 2/3 == 5.333333). Die Tabelle mit DKIX_DUMMY entities gefüllt wird immer, eingegeben, wenn Sie einen Schlüssel löschen (benötigt Hash-Kollisionen richtig zu handhaben.)

Beachten Sie, dass dies alles sehr Implementierung abhängig ist. In Python 3.5 und davor werden beide Snippets nur iteriert einmal (auch wenn Sie for k in d: verwenden, um das Erstellen eines Listenobjekts für die Schlüssel zu vermeiden); Die Iteration wird in 3.6 fortgesetzt, da sich die Implementierung geändert hat und die Iteration nun der Reihenfolge der Inserierung folgt. Zukünftige Python-Versionen können die Implementierung erneut ändern.

Verwandte Themen