Bir sözlükteki öğeleri tekrarlarken nasıl silinir?


296

Üzerinde yineleme yaparken Python'daki bir sözlükten öğe silmek meşru mudur?

Örneğin:

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

Fikir, yinelenen bir alt küme olan yeni bir sözlük oluşturmak yerine, belirli bir koşulu karşılamayan öğeleri kaldırmaktır.

Bu iyi bir çözüm mü? Daha zarif / verimli yollar var mı?


1
Çok ilginç cevaplarla ilgili bir soru: stackoverflow.com/questions/9023078/… .
maksimum

Biri kolayca deneyebilirdi. Başarısız olursa, meşru değildir.
Trilarion

26
@Trilarion Biri kolayca deneyebilirdi ... ve kolayca hiçbir şey öğrenemezdi. Başarılı olursa, mutlaka meşru değildir . Uç durumlar ve beklenmedik uyarılar boldur. Bu soru Pythonistalar için önemsiz bir ilgidir. "Biri kolayca denemiş olabilir!" yararsızdır ve yığın akışı sorgulamasının meraklı ruhuna aykırıdır.
Cecil Curry

Perusing sonra maksimum 's ilgili soru , ben hemfikir olmalıdır. Muhtemelen sadece rahatsız edici derinlemesine soruyu ve bunun iyi yazılmış cevaplarını incelemek istersiniz. Pythonic zihniniz patlayacak.
Cecil Curry

1
@CecilCurry Bir fikri burada sunmadan önce kendiniz test etmek, yanılmıyorsam yığın akışı ruhu içinde olur. Tüm anlatmak istediğim buydu. Bu yüzden herhangi bir rahatsızlık varsa üzgünüm. Ayrıca bence bu iyi bir soru ve bunu aşağılamadı. En çok Jochen Ritzel'in cevabını seviyorum . Ben ikinci bir adımda silmek çok hızlı bir şekilde silmek için tüm bu şeyleri yapmak gerektiğini sanmıyorum. Bence bu tercih edilen yol olmalı.
Trilarion

Yanıtlar:


305

DÜZENLE:

Bu cevap Python3 için çalışmayacak ve bir RuntimeError.

RuntimeError: yineleme sırasında sözlük boyutu değişti.

Bu olur çünkü mydict.keys() liste değil yineleyici döndürmesi ile . Yorumlarda belirtildiği gibi sadece mydict.keys()bir listeye dönüştürün list(mydict.keys())ve çalışması gerekir.


Konsoldaki basit bir test, bir sözlüğü tekrar ederken değiştiremeyeceğinizi gösterir:

>>> 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'ın cevabında belirtildiği gibi, yineleyici bir sonraki girişe geçmeye çalıştığında girişleri silmek sorunlara neden olur. Bunun yerine,keys() anahtarların bir listesini almak ve bununla çalışmak yöntemi kullanın:

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

Öğeler değerine göre silmeniz gerekiyorsa items()bunun yerine yöntemi kullanın:

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

53
Python 3'te dict.items () öğesinin bir yineleyici döndürdüğünü (ve dict.iteritems () gittiğini) unutmayın.
Tim Lesher

83
@TimLesher yorumunda ayrıntıya girmek için ... Bu Python 3'te ÇALIŞMAZ
maks.

99
@ Max'ın detaylandırma hakkında ayrıntılı bilgi için yukarıdaki kodu 2to3 ile dönüştürürseniz işe yarayacaktır. Varsayılan ayarlayıcılarında biri gibi döngü göz yapacaktır for k, v in list(mydict.items()):için aynı Python 3'te cezası çalışır keys()hale list(keys()).
Walter Mundt

8
Bu işe yaramıyor. Bir hata alıyorum:RuntimeError: dictionary changed size during iteration
Tomáš Zato - Monica'yı

15
@ TomášZato, Walter'ın işaret ettiği gibi, python3 için python3 olarak kullanmanız gerekir for k in list(mydict.keys()): , keys () yöntemini yineleyici yapar ve ayrıca yineleme sırasında dikte öğelerinin silinmesine izin vermez. Bir list () çağrısı ekleyerek, keys () yineleyicisini bir listeye dönüştürürsünüz. Yani for döngüsünün gövdesindeyken artık sözlüğün kendisini yinelemezsiniz.
Geoff Crompton

89

Bunu iki adımda da yapabilirsiniz:

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

En sevdiğim yaklaşım genellikle yeni bir diksiyon yapmaktır:

# 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: Aslında 2.7'den beri.
Jochen Ritzel

5
Diksiyon anlama yaklaşımı sözlüğün bir kopyasını oluşturur; Neyse ki değerler en azından kopyalanmıyor, sadece bağlantılı. Yine de çok fazla anahtarınız varsa, kötü olabilir. Bu nedenle removedöngü yaklaşımını daha çok seviyorum .
max

1
Ayrıca adımları birleştirebilirsiniz:for k in [k for k in mydict if k == val]: del mydict[k]
AXO

ilk çözüm, bu konudaki büyük dikte üzerinde tek etkili olanıdır - tam uzunlukta bir kopya yapmadığı için.
kxr

21

Bir koleksiyonu yinelerken değiştiremezsiniz. Bu şekilde delilik yatar - en önemlisi, geçerli öğeyi silmenize ve silmenize izin verildiyse, yineleyicinin (+1) ve sonraki çağrının nextsizi bunun ötesine götürmesi gerekir (+2), böylece Son olarak bir öğeyi (sildiğiniz öğenin hemen arkasındaki) atlar. İki seçeneğiniz var:

  • Tüm anahtarları (veya değerleri veya ihtiyacınız olana bağlı olarak her ikisini birden) kopyalayın, ardından bunları yineleyin. Bunun için .keys()et al kullanabilirsiniz (Python 3'te, sonuçtaki yineleyiciyilist ). Uzay bilge olsa son derece savurgan olabilir.
  • mydictAyrı bir koleksiyonda silmek için tuşları kaydederek her zamanki gibi yineleyin to_delete. Yinelemeyi mydicttamamladığınızda to_delete, içindeki tüm öğeleri silin mydict. İlk yaklaşımda (kaç anahtarın silindiğine ve kaç tane kaldığına bağlı olarak) yer tasarrufu sağlar, ancak birkaç satır daha gerektirir.

You can't modify a collection while iterating it.bu sadece dikte ve arkadaşlar için doğrudur, ancak yineleme sırasında listeleri değiştirebilirsiniz: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
@Nils İstisna atmıyor ama yine de yanlış. Gözlemleyin: codepad.org/Yz7rjDVT - açıklama için bkz. Örneğin stackoverflow.com/q/6260089/395760

Beni buraya getirdin. Hala can'tsadece dikte ve arkadaşlar için doğrudur, ancak shouldn'tlisteler için olmalıdır .
Nils Lindemann

21

Bunun yerine bir kopya üzerinden yineleyin items():

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

1
Bu çok mantıklı değil - o zaman del vdoğrudan yapamazsınız , böylece asla kullanmayacağınız her v'nin bir kopyasını yaptınız ve öğelere yine de anahtarla erişmeniz gerekiyor. dict.keys()daha iyi bir seçimdir.
jscs

2
@Josh: Her şey vsilme kriteri olarak ne kadar kullanmanız gerektiğine bağlı .
Ignacio Vazquez-Abrams

3
Python 3 altında, dict.items()kopya yerine yineleyici döndürür. İçin açıklamayı bakın Blair bireyin cevabı (ne yazık ki) ayrıca Python 2 semantiğini varsayar.
Cecil Curry

11

Kullanımı en temiz 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}

Bu listeler için paralel bir yapıya karşılık gelir:

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

Her ikisi de python2 ve python3'te çalışır.


Veri kümenizin büyük olması durumunda bu iyi değildir. Bu bellekteki tüm nesneleri kopyalıyor, değil mi?
AFP_555

1
@ AFP_555 Evet - buradaki amacım temiz, paralel, pythonic kodu için. Bellek verimliliğine ihtiyacınız varsa, bildiğim en iyi yaklaşım yinelenecek ve silinecek anahtarların listesini veya kaydedilecek yeni bir dikteyi oluşturmaktır. Python ile güzellik benim önceliğim; büyük veri kümeleri için Go veya Rust kullanıyorum.
rsanden

9

Bir sözlük kavrama kullanabilirsiniz.

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


Bu en Pythonic.
Yehosef

Ancak dyerinde değişiklik yapmak yerine yeni bir sözlük oluşturur .
Aristide

9

Python3 ile, dic.keys () üzerinde yineleme sözlük boyutu hatasını yükseltir. Bu alternatif yolu kullanabilirsiniz:

Python3 ile test edildi, iyi çalışıyor ve " yineleme sırasında sözlük boyutu değişti " hatası ortaya çıkmadı:

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

Önce silmek için bir anahtar listesi oluşturabilir ve daha sonra bu listeyi silerek tekrarlayabilirsiniz.

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]

Onun oldukça bir @ @ Ritzel'in 1 çözümü (tam kopya w / o büyük dikte üzerinde verimli). Rağmen "uzun okuma" w / o liste kavrama. Yine de muhtemelen daha hızlı mı?
kxr

3

Silmek istediğiniz öğeler her zaman diksiyon yinelemesinin "başlangıcında" ise uygun olabilecek bir yol vardır

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

"Başlangıç" ın yalnızca belirli Python sürümleri / uygulamaları için tutarlı olması garanti edilir. Örneğin Python 3.7'deki Yenilikler

dikte nesnelerinin ekleme emri koruma doğası Python dil spesifikasyonunun resmi bir parçası olarak ilan edilmiştir.

Bu şekilde, en azından Python 3'te, diğer cevapların birçoğunun önerdiği demetin bir kopyası önlenir.


1

Yukarıdaki çözümleri Python3'te denedim ama bir dikte içinde nesneleri saklarken benim için çalışan tek kişi bu gibi görünüyor. Temel olarak, dict'inizin () bir kopyasını oluşturur ve orijinal sözlüğünüzdeki girişleri silerken bunu tekrarlarsınız.

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
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.