Üzerinde yineleme yaparken bir Python diktesini değiştirme


87

Diyelim ki bir Python sözlüğümüz var dve üzerinde şu şekilde yineliyoruz:

for k,v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

( fve gsadece bazı kara kutu dönüşümleridir.)

Başka bir deyişle, dkullanarak üzerinde iterasyon yaparken ürün eklemeye / çıkarmaya çalışıyoruz iteritems.

Bu iyi tanımlanmış mı? Cevabınızı desteklemek için bazı referanslar verebilir misiniz?

(Kırılırsa bunun nasıl düzeltileceği oldukça açık, bu yüzden peşinde olduğum açı bu değil.)




Bunu yapmaya çalıştım ve öyle görünüyor ki, ilk dikte boyutunu değiştirmeden bırakırsanız - örneğin, onları kaldırmak yerine herhangi bir anahtarı / değeri değiştirin, o zaman bu kod istisna oluşturmaz
Artsiom Rudzenka

Bu konuyu arayan herkes için (kendim dahil) "kırılırsa bunun nasıl düzeltileceğinin oldukça açık" olduğuna katılmıyorum ve kabul edilen cevabın en azından buna değinmesini diliyorum.
Alex Peters

Yanıtlar:


54

Python belge sayfasında ( Python 2.7 için ) açıkça belirtilmiştir:

Kullanılması iteritems()ekleyerek veya artırabilir sözlükte girdileri silerken RuntimeErrorveya tüm girişleri üzerinde yineleme için başarısız.

Benzer şekilde Python 3 için .

Aynısı için iter(d)de geçerli d.iterkeys()ve d.itervalues()bunun için olduğunu söyleyeceğim kadar ileri gideceğim for k, v in d.items():(tam olarak ne olduğunu hatırlayamıyorum for, ancak uygulama çağrılırsa şaşırmam iter(d)).


51
Kod parçacığını kullandığımı belirterek topluluk iyiliği için kendimi utandıracağım. Bir RuntimeError alamadığım için her şeyin iyi olduğunu düşündüğümü düşünerek. Ve bir süredir öyleydi. Anally retentive birim testleri bana başparmak veriyordu ve piyasaya çıktığında bile iyi çalışıyordu. Sonra tuhaf davranışlar sergilemeye başladım. Olan şey, sözlükteki öğelerin atlanması ve bu nedenle sözlükteki tüm öğelerin taranmamasıydı. Çocuklar, hayatımda yaptığım hatalardan ders alın ve sadece hayır deyin! ;)
Alan Cabrera

3
Mevcut anahtardaki değeri değiştiriyorsam (ancak herhangi bir anahtar eklemiyorsam veya kaldırmıyorsam) problemlerle karşılaşabilir miyim? Bunun herhangi bir soruna yol açmayacağını düşünebilirim, ama bilmek istiyorum!
Gershy

@GershomMaes Hiçbirini bilmiyorum, ancak döngü vücudunuz değeri kullanıyorsa ve değişmesini beklemiyorsa, yine de bir mayın tarlasına koşuyor olabilirsiniz.
Raphaël Saint-Pierre

3
d.items()Python 2.7'de güvenli olmalı (oyun Python 3 ile değişir), çünkü esasen kopyası olanı yapar d, böylece üzerinde yinelediğiniz şeyi değiştirmezsiniz.
Paul Price

Bunun için de geçerli olup olmadığını bilmek ilginç olurduviewitems()
jlh

51

Alex Martelli bu konuyu ele alıyor . .

Kap üzerinde döngü yaparken kabı değiştirmek (örneğin dikte) güvenli olmayabilir. Yani del d[f(k)]güvenli olmayabilir. Bildiğiniz gibi, geçici çözüm d.items()(kabın bağımsız bir kopyası üzerinde döngü yapmak için) yerined.iteritems() (aynı temel konteyneri kullanan .

Mevcut bir dikt indeksindeki değeri değiştirmek sorun değildir , ancak yeni indislere değer eklemek (örn. d[g(k)]=v) Çalışmayabilir.


3
Bunun benim için kilit bir cevap olduğunu düşünüyorum. Birçok kullanım durumunda, bir şeyleri ekleyen bir işlem ve başka bir şeyleri temizleme / silme işlemi olacaktır, böylece d.items () kullanma tavsiyesi işe yarar. Python 3 uyarıları
dayanmıyor

4
Python 3 uyarıları hakkında daha fazla bilgi , yukarıda bahsedilen Python 2 dikte yöntemlerinin anlamsal eşdeğerlerinin numaralandırıldığı PEP 469'da bulunabilir .
Lionel Brooks

1
"Mevcut bir dikt indeksindeki değeri değiştirmekte sorun yoktur " - bunun için bir referansınız var mı?
Jonathon Reinhart

1
@JonathonReinhart: Hayır, bunun için bir referansım yok, ancak Python'da oldukça standart olduğunu düşünüyorum. Örneğin, Alex Martelli bir Python çekirdek geliştiricisiydi ve burada kullanımını gösteriyor .
unutbu

27

En azından bunu yapamazsın d.iteritems(). Denedim ve Python başarısız oldu

RuntimeError: dictionary changed size during iteration

Onun yerine kullanırsan d.items() , o zaman çalışır.

Python 3'te, Python 2'de olduğu d.items()gibi sözlüğe bir görünümdür d.iteritems(). Bunu Python 3'te yapmak için bunun yerine kullanın d.copy().items(). Bu, benzer şekilde, üzerinde yinelediğimiz veri yapısını değiştirmekten kaçınmak için sözlüğün bir kopyasını yinelememize izin verecektir.


2
Cevabıma Python 3 ekledim.
murgatroid99

2
Bilginize, 2to3Py2'lerin Py3'e harfi harfine çevirisi (örneğin tarafından kullanıldığı d.items()şekliyle) list(d.items()), ancak d.copy().items()muhtemelen karşılaştırılabilir verimliliktedir.
Søren Løvborg

2
Dict nesnesi çok büyükse, d.copy (). İtems () verimli mi?
yusufçuk

12

Numpy dizilerini içeren büyük bir sözlüğüm var, bu nedenle @ murgatroid99 tarafından önerilen dict.copy (). Keys () şey uygun değildi (işe yarasa da). Bunun yerine, keys_view'u bir listeye dönüştürdüm ve iyi çalıştı (Python 3.4'te):

for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

Bunun, yukarıdaki cevaplar gibi Python'un iç işleyişinin felsefi alanına dalmadığının farkındayım, ancak belirtilen soruna pratik bir çözüm sağlıyor.


6

Aşağıdaki kod, bunun iyi tanımlanmadığını gösterir:

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

İlk örnek, g (k) 'yi çağırır ve bir istisna atar (yineleme sırasında sözlük boyutunun değişmesi).

İkinci örnek h (k) 'yi çağırır ve istisna oluşturmaz, ancak çıktılar:

{21: 'axx', 22: 'bxx', 23: 'cxx'}

Ki bu, koda bakmak yanlış görünüyor - şöyle bir şey bekliyordum:

{11: 'ax', 12: 'bx', 13: 'cx'}

Neden beklediğinizi anlayabiliyorum {11: 'ax', 12: 'bx', 13: 'cx'}ama 21,22,23, gerçekte ne olduğuna dair size ipucu vermeli: Döngünüz 1., 2., 3., 11., 12., 13. maddelerden geçti ancak ikinciyi almayı başaramadı zaten üzerinde yinelediğiniz öğelerin önüne eklenen yeni öğeler. h()Geri dönmek için değiştirin x+5ve başka bir x: 'axxx'vb. Veya 'x + 3' elde edin ve muhteşem 'axxxxx'
Duncan

Evet, korkarım benim hatam - beklediğim çıktı {11: 'ax', 12: 'bx', 13: 'cx'}dediğin gibiydi , bu yüzden bu konudaki yazımı güncelleyeceğim. Her iki durumda da, bu açıkça iyi tanımlanmış bir davranış değildir.
battledave

1

Aynı sorunu yaşadım ve bu sorunu çözmek için aşağıdaki prosedürü kullandım.

Python Listesi, üzerinde yineleme sırasında değişiklik yapsanız bile yinelenebilir. bu nedenle aşağıdaki kod için sonsuz olarak 1'leri basacaktır.

for i in list:
   list.append(1)
   print 1

Yani liste ve dikte kullanarak bu sorunu çözebilirsiniz.

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))

Yineleme sırasında bir listeyi değiştirmenin güvenli olup olmadığından emin değilim (bazı durumlarda işe yarayabilir). Örneğin bu soruya bakın ...
Roman

@Roman Bir listenin öğelerini silmek istiyorsanız, bir sonraki öğenin dizini silindikten sonra normal sırada değişeceğinden, güvenli bir şekilde ters sırada yineleyebilirsiniz. Bu örneğe bakın.
mbomb007

1

Python 3 yapmanız gerekenler:

prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict() 
for k,v in t.items():
    t2[k] = prefix + v

ya da kullan:

t2 = t1.copy()

Asla orijinal sözlüğü değiştirmemelisiniz, bu kafa karışıklığına ve olası hatalara veya RunTimeErrors'a yol açar. Sözlüğe yeni anahtar isimleriyle eklemediğiniz sürece.


0

Bugün benzer bir kullanım durumum vardı, ancak döngünün başlangıcında sözlükteki anahtarları basitçe somutlaştırmak yerine, sıralı bir dikt olan diktenin yinelemesini etkilemek için diktede değişiklik yapmak istedim.

Jaraco.itertools'da da bulunabilen aşağıdaki rutini oluşturdum :

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)

Doküman dizgisi kullanımı göstermektedir. Bu işlev d.iteritems(), istenen etkiye sahip olmak için yukarıdaki yerine kullanılabilir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.