İç içe sözlükleri Python'da uygulamanın en iyi yolu nedir?
Bu kötü bir fikir, yapma. Bunun yerine, normal bir sözlük kullanın dict.setdefault
ve aproposların olduğu yerlerde kullanın , böylece normal kullanımda tuşlar eksik olduğunda beklenen sonucu alırsınız KeyError
. Bu davranışı almakta ısrar ediyorsanız, kendinizi ayağınızdan nasıl çekeceğiniz aşağıda açıklanmıştır:
Uygulamak __missing__
bir üzerinde dict
setine alt sınıf ve yeni bir örneğini döndürür.
Bu yaklaşım, Python 2.5'ten beri mevcuttur (ve belgelenmiştir) ve (özellikle benim için değerli) , otomatikleştirilmiş bir varsayılan kararın çirkin yazdırılması yerine, normal bir dikte gibi güzel bir şekilde yazdırılıyor :
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)() # retain local pointer to value
return value # faster to return than dict lookup
(Not self[key]
ödevin sol tarafındadır, dolayısıyla burada yineleme yoktur.)
ve verileriniz olduğunu varsayalım:
data = {('new jersey', 'mercer county', 'plumbers'): 3,
('new jersey', 'mercer county', 'programmers'): 81,
('new jersey', 'middlesex county', 'programmers'): 81,
('new jersey', 'middlesex county', 'salesmen'): 62,
('new york', 'queens county', 'plumbers'): 9,
('new york', 'queens county', 'salesmen'): 36}
İşte kullanım kodumuz:
vividict = Vividict()
for (state, county, occupation), number in data.items():
vividict[state][county][occupation] = number
Ve şimdi:
>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
eleştiri
Bu tür bir kapsayıcıya yönelik bir eleştiri, kullanıcı bir anahtarı yanlış yazarsa, kodumuzun sessizce başarısız olabileceğidir:
>>> vividict['new york']['queens counyt']
{}
Ayrıca, verilerimizde yanlış yazılmış bir ilçemiz olacaktı:
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36},
'queens counyt': {}}}
Açıklama:
Vividict
Bir anahtara erişilip eksik olduğunda sadece sınıfımızın başka bir iç içe örneğini sağlıyoruz . (Değer atamasını döndürmek yararlıdır, çünkü ek olarak alıcıyı dikte üzerine çağırmamızı engeller ve maalesef ayarlandığı gibi iade edemeyiz.)
Notlar, bunlar en çok oylanan cevapla aynı semantiktir, ancak kod satırlarının yarısında - nosklo'nun uygulanması:
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Kullanım Gösterisi
Aşağıda, bu diktenin anında iç içe bir diksiyon yapısı oluşturmak için nasıl kolayca kullanılabileceğinin bir örneği verilmiştir. Bu, hızlı bir şekilde, gitmek istediğiniz kadar derin bir hiyerarşik ağaç yapısı oluşturabilir.
import pprint
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
d = Vividict()
d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)
Hangi çıktılar:
{'fizz': {'buzz': {}},
'foo': {'bar': {}, 'baz': {}},
'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}
Ve son satırın gösterdiği gibi, güzelce ve manuel inceleme için güzel yazdırıyor. Ancak verilerinizi görsel olarak incelemek istiyorsanız __missing__
, sınıfının yeni bir örneğini anahtara ayarlamak ve geri vermek çok daha iyi bir çözümdür.
Kontrast için diğer alternatifler:
dict.setdefault
Asker bunun temiz olmadığını düşünmesine rağmen, Vividict
kendime tercih edilebilir buluyorum .
d = {} # or dict()
for (state, county, occupation), number in data.items():
d.setdefault(state, {}).setdefault(county, {})[occupation] = number
ve şimdi:
>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
Bir yanlış yazım gürültüyle başarısız olur ve verilerimizi kötü bilgilerle karıştırmaz:
>>> d['new york']['queens counyt']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'
Ayrıca, setdefault'un döngülerde kullanıldığında harika çalıştığını düşünüyorum ve anahtarlar için ne alacağınızı bilmiyorsunuz, ancak tekrarlayan kullanım oldukça külfetli oluyor ve kimsenin aşağıdakileri takip etmek isteyeceğini düşünmüyorum:
d = dict()
d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})
Başka bir eleştiri, setdefault'un kullanılsa da kullanılmasa da yeni bir örnek gerektirmesidir. Bununla birlikte, Python (veya en azından CPython) kullanılmayan ve referanslandırılmamış yeni örneklerin işlenmesi konusunda oldukça zekidir, örneğin bellekteki konumu yeniden kullanır:
>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)
Otomatik olarak canlandırılan bir varsayılan karar
Bu düzgün görünen bir uygulamadır ve verileri incelediğiniz bir komut dosyasında kullanmak, uygulama yapmak kadar yararlı olacaktır __missing__
:
from collections import defaultdict
def vivdict():
return defaultdict(vivdict)
Ancak verilerinizi incelemeniz gerekiyorsa, verilerle aynı şekilde doldurulmuş otomatik olarak canlandırılan varsayılan bir kararın sonuçları şöyle görünür:
>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint;
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar':
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>,
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})
Bu çıktı oldukça yetersiz ve sonuçlar oldukça okunamıyor. Tipik olarak verilen çözüm tekrar tekrar manuel inceleme için bir dikteye dönüştürmektir. Bu önemsiz olmayan çözüm okuyucu için bir alıştırma olarak bırakılmıştır.
Verim
Son olarak, performansa bakalım. Anlamanın maliyetlerini çıkarıyorum.
>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747
Performansa dayanarak dict.setdefault
en iyi sonucu verir. Yürütme hızına önem verdiğiniz durumlarda, üretim kodu için kesinlikle tavsiye ederim.
Etkileşimli kullanım için buna ihtiyacınız varsa (belki bir IPython dizüstü bilgisayarda), performans gerçekten önemli değildir - bu durumda, çıkışın okunabilirliği için Vividict ile giderdim. AutoVivification nesnesiyle karşılaştırıldığında (bunun __getitem__
yerine __missing__
bu amaç için yapılmış olan) çok daha üstündür.
Sonuç
Yeni bir örnek oluşturmak ve geri döndürmek __missing__
için alt sınıflara uygulamak dict
alternatiflerden biraz daha zordur, ancak
- kolay örnekleme
- kolay veri popülasyonu
- kolay veri görüntüleme
ve modifikasyondan daha az karmaşık ve daha performanslı olduğu __getitem__
için bu yönteme tercih edilmelidir.
Bununla birlikte, dezavantajları vardır:
- Kötü aramalar sessizce başarısız olur.
- Kötü arama sözlükte kalacaktır.
Bu yüzden şahsen setdefault
diğer çözümlere tercih ediyorum ve bu tür davranışlara ihtiyaç duyduğum her durumda.
Vividict
? Örnegin3
velist
içinde listeye girilebilecek listelerin diktinin diktesi içind['primary']['secondary']['tertiary'].append(element)
. Her derinlik için 3 farklı sınıf tanımlayabilirim ama daha temiz bir çözüm bulmak isterim.