Sözlükler Python 3.6 ve sonraki sürümlerinde sıralanıyor mu?


470

Sözlükler, önceki enkarnasyonların aksine Python 3.6'da (en azından CPython uygulaması altında) sıralanır. Bu önemli bir değişiklik gibi görünüyor, ancak dokümantasyonda sadece kısa bir paragraf var . Bir dil özelliği yerine bir CPython uygulama detayı olarak tanımlanır, ancak bunun gelecekte standart hale gelebileceğini de ima eder.

Öğe sırasını korurken yeni sözlük uygulaması eskisinden daha iyi nasıl çalışır?

Belgelerdeki metin:

dict()artık PyPy'nin öncülüğünü yaptığı “kompakt” bir temsili kullanıyor . Yeni diktenin () bellek kullanımı Python 3.5'e kıyasla% 20 ila% 25 arasında daha küçük. PEP 468 (Bir fonksiyondaki ** kwargların sırasının korunması.) Bunun tarafından uygulanır. Bu yeni uygulamanın siparişi koruyan yönü bir uygulama detayı olarak kabul edilir ve buna güvenilmemelidir (bu gelecekte değişebilir, ancak dil spesifikasyonunu değiştirmeden önce bu yeni dict uygulamasının birkaç sürüm için dilde olması arzu edilir. mevcut ve gelecekteki tüm Python uygulamaları için siparişi koruyan semantikleri zorunlu kılmak için; bu aynı zamanda rastgele yineleme sırasının hala geçerli olduğu dilin eski sürümleriyle geriye dönük uyumluluğu korumaya yardımcı olur, örneğin Python 3.5). (INADA Naoki tarafındanSayı 27350 . İlk olarak Raymond Hettinger tarafından önerilen fikir .)

Güncelleme Aralık 2017: dicts tutma ekleme talimatı olduğunu garanti Python 3.7 için


2
Bu konuyu Python-Dev posta listesinde bulabilirsiniz: mail.python.org/pipermail/python-dev/2016-September/146327.html Görmediyseniz ; temelde bu konular üzerine bir tartışma.
mgc

1
Eğer kwarglar şimdi sipariş edilmesi gerekiyorsa (ki bu güzel bir fikirdir) ve kwarglar OrderedDict değil, diktüyse, o zaman belgelerin aksini söylese de, dict tuşlarının Python'un gelecekteki versiyonunda sıralı kalacağını tahmin edebilirim.
Dmitriy Sintsov

4
@DmitriySintsov Hayır, bu varsayımı yapma. Bu, PEP'in yazılması sırasında ortaya çıkan ve sipariş koruma özelliğini tanımlayan bir konuydu **kwargsve kullanılan ifadeler diplomatiktir: **kwargsbir işlev imzasında artık bir ekleme siparişi koruma eşlemesi olması garanti edilmektedir . Haritalama terimini , başka bir uygulamayı zorunlu kılmak (ve OrderedDictdahili olarak kullanmak ) için zorlamak ve bunun siparişin verilmemiş olmasına bağlı olmaması gerektiğini belirtmek için kullandılar dict.
Dimitris Fasarakis Hilliard

7
Raymond Hettinger'dan iyi bir video açıklaması
Alex

1
@wazoox, hashmap'ın düzeni ve karmaşıklığı değişmedi. Değişiklik, daha az yer harcayarak hashmap'ı daha küçük hale getirir ve kaydedilen alan yardımcı diziden (genellikle?) Daha fazladır. Daha hızlı, daha küçük, sıralı - tüm 3'ü seçersiniz
John La Rooy

Yanıtlar:


514

Sözlükler Python 3.6 ve sonraki sürümlerinde sıralanıyor mu?

Bunlar olan ekleme sipariş [1] . Python 3.6'dan itibaren, Python'un CPython uygulaması için sözlükler eklenen öğelerin sırasını hatırlar . Bu Python 3.6'da bir uygulama detayı olarak kabul edilir ; diğer Python uygulamalarında (ve diğer sıralı davranışlarda [1] ) garanti edilenOrderedDict ekleme siparişi vermek istiyorsanız kullanmanız gerekir .

Python 3.7'den itibaren , bu artık bir uygulama detayı değildir ve bunun yerine bir dil özelliği haline gelir. GvR'nin bir python-dev mesajından :

Öyleyse yap. "Dict ekleme emrini koruyor" kararıdır. Teşekkürler!

Bu basitçe ona güvenebileceğiniz anlamına gelir . Python'un diğer uygulamaları da, Python 3.7'nin uygun bir uygulaması olmak istiyorlarsa, kampanya siparişi verilmiş bir sözlük sunmalıdır.


Python 3.6sözlük uygulaması , öğe sırasını koruyarak eskisinden daha iyi nasıl çalışır [2] ?

Esasen, iki dizi tutarak .

  • İlk dizi, sözlüğün dk_entriesgirişlerini ( türündePyDictKeyEntry ) eklendikleri sırayla tutar. Koruma sırası, sonunda yalnızca yeni öğelerin her zaman sonuna eklendiği bir ekleme dizisi (ekleme sırası) ile gerçekleştirilir.

  • İkincisi,, dizinin dk_indicesindekslerini tutar dk_entries(yani, ilgili girişin konumunu gösteren değerler dk_entries). Bu dizi karma tablosu olarak işlev görür. Bir anahtar karma olduğunda, depolanan endekslerden birine yol açar dk_indicesve karşılık gelen giriş indeksleme ile getirilir dk_entries. Yalnızca dizinler tutulduğundan, bu dizinin türü sözlüğün toplam boyutuna bağlıdır (tür int8_t( 1bayt) int32_t/ / bit derlemelerinde / int64_t( 4/ 8bayt) arasında değişir )3264

Önceki uygulamada, seyrek bir tür PyDictKeyEntryve boyut dizisi ayrılmak dk_sizezorundaydı; maalesef, bu dizinin performans nedenleriyle2/3 * dk_size dolu olmasından daha fazla olmasına izin verilmediğinden, çok fazla boş alanla sonuçlandı . (ve boş alanın büyüklüğü hala vardı !).PyDictKeyEntry

Sadece gerekli girdiler (eklenmiş olanlar) depolandığından ve intX_t( Xboyut boyutuna bağlı olarak) seyrek bir tür dizisinin 2/3 * dk_sizedolu tutulduğu için bu durum böyle değildir. Boş alan türden PyDictKeyEntryolarak değiştirildi intX_t.

Açıkçası, seyrek bir tür dizisi oluşturmak s PyDictKeyEntrydepolamak için seyrek bir diziden çok daha fazla bellek gerektirir int.

İlgileniyorsanız, bu özellikle ilgili Python-Dev ile ilgili tüm konuşmayı görebilirsiniz, iyi bir okuma.


Raymond Hettinger tarafından yapılan orijinal teklifte , kullanılan veri yapılarının, fikrin özünü yakalayan bir görselleştirmesi görülebilir.

Örneğin, sözlük:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

şu anda [keyhash, key, value] olarak depolanıyor:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

Bunun yerine, veriler aşağıdaki gibi organize edilmelidir:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

Gördüğünüz gibi, orijinal teklifte, çarpışmaları azaltmak ve aramaları daha hızlı yapmak için çok fazla alan boş. Yeni yaklaşımla, seyrekliği gerçekten gerekli olduğu yerlerde, endekslerde taşıyarak gereken belleği azaltırsınız.


[1]: I "ekleme sipariş" olup OrderedDict varlığı ile, "düzenli", çünkü bu daha davranışını göstermektedir "sıralı" demek dictnesne sağlamaz . SıralıDikkatler tersine çevrilebilir, siparişe duyarlı yöntemler sağlar ve esas olarak siparişe duyarlı bir eşitlik testi sağlar ( ==, !=). dictşu anda bu davranışlardan / yöntemlerden hiçbirini sunmuyor.


[2]: Yeni sözlük uygulamaları daha kompakt bir şekilde tasarlanarak daha iyi bellek performansı sağlar ; buradaki ana fayda bu. Hız açısından, fark çok sert değil, yeni diktinin hafif gerilemeler ( örneğin anahtar aramaları ) getirebileceği yerler varken, diğerlerinde (yineleme ve yeniden boyutlandırma akla geliyor) bir performans artışı olmalıdır.

Genel olarak, sözlüğün performansı, özellikle gerçek yaşamdaki durumlarda, sunulan kompaktlık nedeniyle iyileşir.


15
Öyleyse, bir öğe kaldırıldığında ne olur? olduğu entriesliste resized? veya boş bir alan mı tutulur? ya da zaman zaman sıkıştırılıyor mu?
njzk2

18
@ njzk2 Bir öğe kaldırıldığında, karşılık gelen dizin DKIX_DUMMYbir değerle değiştirilir-2 ve entrydizideki giriş ile değiştirilirNULL , ekleme yapılırken yeni değerler girişler dizisine eklenir, henüz fark edemedim, ancak endekslerin 2/3eşik boyutunu aştığında yeniden boyutlandırma yapılır. Bu, çok sayıda DUMMYgiriş olması durumunda büyümek yerine küçülmeye yol açabilir .
Dimitris Fasarakis Hilliard

3
@Chris_Rands Hayır, gördüğüm tek gerçek regresyon Victor'un mesajında izleyicide . Bu mikrobenchmark dışında, gerçek hayattaki iş yüklerinde ciddi bir hız farkını gösteren başka bir sorun / mesaj görmedim. Yeni diktinin hafif gerilemeler (örneğin anahtar aramaları) getirebileceği yerler vardır, diğerlerinde (yineleme ve yeniden boyutlandırma akla geliyor) bir performans artışı olacaktır.
Dimitris Fasarakis Hilliard

3
Yeniden boyutlandırma kısmındaki düzeltme : Sözlükler öğeleri sildiğinizde yeniden boyutlandırılmaz, yeniden eklediğinizde yeniden hesaplar. Dolayısıyla, bir diksiyon oluşturulmuşsa d = {i:i for i in range(100)}ve .popeklemeden tüm öğeleriniz varsa , boyut değişmez. Tekrar eklediğinizde d[1] = 1, uygun boyut hesaplanır ve diksiyon yeniden boyutlandırılır.
Dimitris Fasarakis Hilliard

6
@Chris_Rands Eminim kalıyor. Mesele şu ki, ' dictsipariş edilme' hakkındaki battaniye ifadeleri kaldırmamın cevabını değiştirmemin sebebi dicts olduğu gibi sıralanmıyor OrderedDict. Dikkate değer mesele eşitliktir. dicts sıralamaya duyarlı olması ==, OrderedDicts sipariş duyarlı olanları var. Dampingler OrderedDictve dictsşimdi siparişe göre karşılaştırmalar yapmak eski kodda çok fazla kırılmaya yol açabilir. Sanırım OrderedDicts ile ilgili değişebilecek tek şey onun uygulaması.
Dimitris Fasarakis Hilliard

67

Aşağıda orijinal ilk soruya cevap verilmektedir:

Ben kullanmalı mıyım dictya OrderedDictPython 3.6?

Belgelerdeki bu cümlenin aslında sorunuzu cevaplamak için yeterli olduğunu düşünüyorum

Bu yeni uygulamanın siparişi koruyan yönü bir uygulama detayı olarak kabul edilir ve buna güvenilmemelidir

dictaçık bir şekilde sıralı bir koleksiyon olması anlamına gelmez, bu nedenle tutarlı kalmak ve yeni uygulamanın yan etkisine güvenmemek istiyorsanız, bağlı kalmalısınız OrderedDict.

Kodunuzu geleceğe kanıtlayın :)

Burada bununla ilgili bir tartışma var .

EDIT: Python 3.7 bunu bir özellik olarak tutacak görmek


1
Görünüşe göre gerçek bir özellik değil, sadece bir uygulama detayı olsaydı, o zaman belgelere bile koymamalılar.
xji

3
Düzenleme uyarınızdan emin değilim; garanti sadece Python 3.7 için geçerli olduğundan, Python 3.6 için tavsiyenin değişmediğini varsayıyorum, yani
dytts CPython'da

25

Güncelleme: Guido van Rossum , posta listesinde Python 3.7'den itibaren dicttüm Python uygulamalarındaki ekleme talimatını koruması gerektiğini duyurdu .


2
Şimdi anahtar sipariş resmi standarttır, OrderedDict'in amacı nedir? Yoksa artık gereksiz mi?
Jonny Waffles

2
Sanırım OrderedDict gereksizdir çünkü move_to_endyöntemi vardır ve eşitliği sıraya duyarlıdır: docs.python.org/3/library/… . Jim Fasarakis Hilliard'ın cevabı hakkındaki nota bakınız.
fjsj

@JonnyWaffles Jim'in cevabını ve bu soru-cevap grubunu gör stackoverflow.com/questions/50872498/…
Chris_Rands

3
Kodunuzun 2.7 ve 3.6 / 3.7 + 'da aynı şekilde çalışmasını istiyorsanız, OrderedDict
boatcoder

3
Muhtemelen güvenlik nedenleriyle
dikte

9

Yukarıdaki tartışmaya eklemek istedim ama yorum yapacak üne sahip değilim.

Python 3.8 henüz piyasaya sürülmedi, ancak reversed()sözlüklerdeki işlevi de içerecek (başka bir farkı kaldıracak OrderedDict.

Dict ve dictviews artık reversed () kullanarak tersine çevrilmiş kampanya siparişinde yinelenebilir. (BPO-33462 yılında Rémi Lapeyre katkılarıyla.) Piton 3.8 yenilikleri görün

Eşitlik operatörü veya diğer özelliklerinden hiç bahsetmiyorum, bu OrderedDictyüzden hala tamamen aynı değiller.

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.