Powiedzmy, że mamy słownik Pythona d
i iterujemy go w ten sposób:
for k,v in d.iteritems():
del d[f(k)] # remove some item
d[g(k)] = v # add a new item
( f
i g
to tylko niektóre transformacje czarnej skrzynki).
Innymi słowy, próbujemy dodawać / usuwać elementy do d
podczas iteracji po nim za pomocą iteritems
.
Czy to dobrze zdefiniowane? Czy mógłbyś podać jakieś odniesienia na poparcie swojej odpowiedzi?
(Jest całkiem oczywiste, jak to naprawić, jeśli jest zepsute, więc nie o to mi chodzi.)
python
dictionary
NPE
źródło
źródło
Odpowiedzi:
Jest to wyraźnie wspomniane na stronie dokumentacji Pythona (dla Pythona 2.7 ), że
Podobnie dla Pythona 3 .
To samo dotyczy
iter(d)
,d.iterkeys()
id.itervalues()
, i pójdę tak daleko, jak mówi, że robi dlafor k, v in d.items():
(nie pamiętam dokładnie, cofor
robi, ale nie zdziwiłbym się, gdyby realizacja nazywaiter(d)
).źródło
d.items()
powinno być bezpieczne w Pythonie 2.7 (gra zmienia się wraz z Pythonem 3), ponieważ tworzy to, co w zasadzie jest kopiąd
, więc nie modyfikujesz tego, nad czym iterujesz.viewitems()
Alex Martelli rozważa to tutaj .
Zmiana kontenera (np. Dykt) podczas wykonywania pętli nad kontenerem może nie być bezpieczna. Więc
del d[f(k)]
może nie być bezpieczne. Jak wiesz, obejściem jest użycied.items()
(aby zapętlić niezależną kopię kontenera) zamiastd.iteritems()
(który używa tego samego podstawowego kontenera).Modyfikowanie wartości w istniejącym indeksie dyktatu jest w porządku , ale wstawianie wartości w nowych indeksach (np.
d[g(k)]=v
) Może nie działać.źródło
Nie możesz tego zrobić, przynajmniej z
d.iteritems()
. Próbowałem, ale Python zawodziJeśli zamiast tego użyjesz
d.items()
, to działa.W Pythonie 3
d.items()
jest to widok do słownika, podobnie jakd.iteritems()
w Pythonie 2. Aby to zrobić w Pythonie 3, użyj zamiast tegod.copy().items()
. Pozwoli to nam podobnie na iterację kopii słownika, aby uniknąć modyfikowania struktury danych, nad którą iterujemy.źródło
2to3
) Py2 nad.items()
Py3 jestlist(d.items())
, chociażd.copy().items()
prawdopodobnie ma porównywalną wydajność.Mam duży słownik zawierający tablice Numpy, więc dict.copy (). Keys () sugerowane przez @ murgatroid99 nie było wykonalne (chociaż zadziałało). Zamiast tego po prostu przekonwertowałem keys_view na listę i działało dobrze (w Pythonie 3.4):
for item in list(dict_d.keys()): temp = dict_d.pop(item) dict_d['some_key'] = 1 # Some value
Zdaję sobie sprawę, że nie zagłębia się to w filozoficzną sferę wewnętrznego działania Pythona, jak powyższe odpowiedzi, ale zapewnia praktyczne rozwiązanie przedstawionego problemu.
źródło
Poniższy kod pokazuje, że nie jest to dobrze zdefiniowane:
def f(x): return x def g(x): return x+1 def h(x): return x+10 try: d = {1:"a", 2:"b", 3:"c"} for k, v in d.iteritems(): del d[f(k)] d[g(k)] = v+"x" print d except Exception as e: print "Exception:", e try: d = {1:"a", 2:"b", 3:"c"} for k, v in d.iteritems(): del d[f(k)] d[h(k)] = v+"x" print d except Exception as e: print "Exception:", e
Pierwszy przykład wywołuje g (k) i zgłasza wyjątek (słownik zmienił rozmiar podczas iteracji).
Drugi przykład wywołuje h (k) i nie zgłasza wyjątku, ale wyświetla:
{21: 'axx', 22: 'bxx', 23: 'cxx'}
Co, patrząc na kod, wydaje się błędne - spodziewałbym się czegoś takiego:
{11: 'ax', 12: 'bx', 13: 'cx'}
źródło
{11: 'ax', 12: 'bx', 13: 'cx'}
ale 21,22,23 powinno dać ci wskazówkę, co się właściwie wydarzyło: twoja pętla przeszła przez pozycje 1, 2, 3, 11, 12, 13, ale nie udało się odebrać drugiej runda nowych elementów, które zostały wstawione przed elementami, które już przeszedłeś. Zmień,h()
aby powrócić,x+5
a otrzymasz kolejny x:'axxx'
itd. Lub 'x + 3' i otrzymasz wspaniały'axxxxx'
{11: 'ax', 12: 'bx', 13: 'cx'}
taki, jak powiedziałeś, więc zaktualizuję o tym mój post. Tak czy inaczej, nie jest to wyraźnie określone zachowanie.Mam ten sam problem i zastosowałem następującą procedurę, aby rozwiązać ten problem.
Lista Pythona może być iterowana, nawet jeśli modyfikujesz ją podczas iteracji. więc dla następującego kodu będzie drukować jedynki w nieskończoność.
for i in list: list.append(1) print 1
Więc używając listy i dyktuj wspólnie, możesz rozwiązać ten problem.
d_list=[] d_dict = {} for k in d_list: if d_dict[k] is not -1: d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted) d_dict[g(k)] = v # add a new item d_list.append(g(k))
źródło
Python 3 powinieneś po prostu:
prefix = 'item_' t = {'f1': 'ffw', 'f2': 'fca'} t2 = dict() for k,v in t.items(): t2[k] = prefix + v
albo użyj:
Nigdy nie należy modyfikować oryginalnego słownika, prowadzi to do nieporozumień, a także potencjalnych błędów lub błędów RunTimeErrors. Chyba że po prostu dołączysz do słownika nowe nazwy kluczy.
źródło
Dzisiaj miałem podobny przypadek użycia, ale zamiast po prostu materializować klucze w słowniku na początku pętli, chciałem, aby zmiany w dyktandzie wpływały na iterację dyktu, który był nakazem uporządkowanym.
Skończyło się na stworzeniu następującej procedury, którą można również znaleźć w jaraco.itertools :
def _mutable_iter(dict): """ Iterate over items in the dict, yielding the first one, but allowing it to be mutated during the process. >>> d = dict(a=1) >>> it = _mutable_iter(d) >>> next(it) ('a', 1) >>> d {} >>> d.update(b=2) >>> list(it) [('b', 2)] """ while dict: prev_key = next(iter(dict)) yield prev_key, dict.pop(prev_key)
Dokumentacja ilustruje użycie. Funkcji tej można użyć zamiast
d.iteritems()
powyższej, aby uzyskać pożądany efekt.źródło