Yuvalanmış sözlükleri uygulamanın en iyi yolu nedir?


201

Temelde iç içe bir sözlük anlamına gelen bir veri yapısı var. Diyelim ki şöyle görünüyor:

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

Şimdi, bunu korumak ve yaratmak oldukça acı verici; yeni bir eyalet / ilçe / mesleğe sahip olduğum her seferinde iğrenç deneme / yakalama blokları aracılığıyla alt katman sözlükleri oluşturmak zorundayım. Ayrıca, tüm değerleri gözden geçirmek istiyorsam can sıkıcı iç içe yineleyiciler oluşturmak zorundayım.

Ayrıca tuples tuşları gibi kullanabilirsiniz, gibi:

{('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}

Bu, değerler üzerinde yinelemeyi çok basit ve doğal hale getirir, ancak toplama ve sözlüğün alt kümelerine bakmak gibi şeyleri yapmak için sözdizimsel olarak daha acı vericidir (örneğin, sadece eyalet bazında gitmek istiyorum).

Temel olarak, bazen iç içe bir sözlüğü düz sözlük olarak düşünmek istiyorum, bazen de gerçekten karmaşık bir hiyerarşi olarak düşünmek istiyorum. Tüm bunları bir sınıfa satabilirim, ama birisi bunu zaten yapmış olabilir gibi görünüyor. Alternatif olarak, bunu yapmak için gerçekten zarif sözdizimsel yapılar olabilir gibi görünüyor.

Bunu nasıl daha iyi yapabilirim?

Zeyilname: Biliyorum setdefault()ama gerçekten temiz bir sözdizimi oluşturmuyor. Ayrıca, oluşturduğunuz her alt sözlüğün yine de setdefault()manuel olarak ayarlanması gerekir.

Yanıtlar:


180

İç 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.setdefaultve 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 dictsetine 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:

VividictBir 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, Vividictkendime 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.setdefaulten 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 dictalternatiflerden 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 setdefaultdiğer çözümlere tercih ediyorum ve bu tür davranışlara ihtiyaç duyduğum her durumda.


Mükemmel cevap! A için sonlu derinlik ve yaprak türü belirtmenin bir yolu var mı Vividict? Örnegin 3ve listiçinde listeye girilebilecek listelerin diktinin diktesi için d['primary']['secondary']['tertiary'].append(element). Her derinlik için 3 farklı sınıf tanımlayabilirim ama daha temiz bir çözüm bulmak isterim.
Eric Duminil

@EricDuminil d['primary']['secondary'].setdefault('tertiary', []).append('element')- ?? İltifat için teşekkürler, ama dürüst olalım - asla kullanmıyorum __missing__- her zaman kullanırım setdefault. Muhtemelen sonucumu / girişimi güncellemeliyim ...
Aaron Hall

@AaronHall Doğru davranış kodu gerekirse bir diksiyon oluşturmak gerekir. Bu durumda önceki atanmış değeri geçersiz kılarak.
nehem

@AaronHall Ayrıca The bad lookup will remain in the dictionary.bu çözümü kullanmayı düşündüğümde ne anlama geldiğini anlamama yardımcı olabilir misiniz ? Çok takdir etmek. Thx
nehem

@AaronHall İkiden setdefaultfazla derinlik içerdiğinde sorun başarısız olur. Görünüşe göre Python'daki hiçbir yapı açıklandığı gibi gerçek canlılık sunamaz. Ben dikte ve iç içe öznitelikler listesi için bir referans kabul biri için get_nestedve biri için iki belirleme yöntemleri için yerleşmek zorunda kaldı set_nested.
nehem

188
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

Test yapmak:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

Çıktı:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

Python 3.x taşındıklarında bu sorunu olan var mı? stackoverflow.com/questions/54622935/…
jason

@jason picklepython sürümleri arasında korkunç. Saklamak istediğiniz verileri depolamak için kullanmaktan kaçının. Sadece istediği zaman döküp yeniden oluşturabileceğiniz önbellek ve şeyler için kullanın. Uzun süreli depolama veya serileştirme yöntemi olarak değil.
nosklo

Bu nesneleri saklamak için ne kullanıyorsunuz? Otomatik doğrulama nesnem yalnızca panda veri çerçeveleri ve dize içeriyor.
Jason

@jason Verilere bağlı olarak, sqlitesaklamak için JSON, csv dosyaları ve hatta bir veritabanı kullanmayı seviyorum .
nosklo

30

Bu kadar küçük birini görmediğim için, burada istediğiniz kadar iç içe olan bir ter, ter yok:

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)

2
@wberry: Aslında tek ihtiyacınız olan şey yodict = lambda: defaultdict(yodict).
martineau

1
Kabul edilen sürüm bir alt sınıftır dict, bu yüzden tamamen eşdeğer olmak x = Vdict(a=1, b=2)için çalışmamız gerekir .
wberry

@wberry: Kabul edilen cevapta ne olduğuna bakılmaksızın, bir alt sınıf olmak dictOP tarafından belirtilen ve sadece bunları uygulamak için "en iyi yolu" isteyen bir zorunluluk değildi - ayrıca, yapmıyor / yapmamalı Python'da bu kadar önemli.
martineau

24

Bir YAML dosyası oluşturabilir ve PyYaml kullanarak okuyabilirsiniz .

Adım 1: "employed.yml" adlı bir YAML dosyası oluşturun:

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

Adım 2: Python'da okuyun

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

ve şimdi my_shnazzy_dictionarytüm değerlerinize sahip. Bunu anında yapmanız gerekiyorsa, YAML'yi bir dize olarak oluşturabilir ve içine besleyebilirsiniz yaml.safe_load(...).


4
YAML kesinlikle çok derinden iç içe veri (ve yapılandırma dosyaları, databaes modelleri, vb.) Girmek için benim seçimim. Eğer OP fazladan dosya istemiyorsa, bazı dosyalarda normal bir Python dizesi kullanın ve bunu YAML ile ayrıştırın.
kmelvn

YAML dizeleri oluşturmak için iyi bir nokta: Bu, "tempfile" modülünü tekrar tekrar kullanmaktan çok daha temiz bir yaklaşım olacaktır.
Pete

18

Bir yıldız şeması tasarımınız olduğundan, onu daha çok ilişkisel bir tablo gibi ve daha az sözlük gibi yapılandırmak isteyebilirsiniz.

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

Bu tür şeyler, SQL ek yükleri olmadan veri ambarı benzeri bir tasarım oluşturmak için uzun bir yol kat edebilir.


14

İç içe yerleştirme düzeyi sayısı azsa, bunun için kullanırım collections.defaultdict:

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

Kullanılması defaultdictDağınık bir sürü böyle kaçınır setdefault(), get()vb


+1: defaultdict, python'a tüm zamanların en sevdiğim eklemelerinden biridir. Artık .setdefault ()!
John Fouhy

8

Bu, iç içe bir keyfi derinlik sözlüğü döndüren bir işlevdir:

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

Şöyle kullanın:

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

Her şeyi böyle bir şeyle yineleyin:

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

Bu çıktı:

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

Sonunda, sözlüğe yeni öğelerin eklenememesi için bunu yapmak isteyebilirsiniz. Tüm bunları tekrar defaultdicttekrar normal dicts'ye dönüştürmek kolaydır .

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)

7

setdefaultOldukça kullanışlı buluyorum ; Bir anahtarın mevcut olup olmadığını kontrol eder ve yoksa ekler:

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefault her zaman ilgili anahtarı döndürür, böylece aslında 'd ' .

Tekrarlama söz konusu olduğunda, bir Python'da zaten yoksa bir jeneratörü kolayca yazabileceğinize eminim:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)

Bu çözümü seviyorum ama denediğimde: count.setdefault (a, {}). Setdefault (b, {}). Setdefault (c, 0) + = 1 "Artırılmış atama için yasadışı ifade"
alıyorum

6

Diğerlerinin önerdiği gibi, ilişkisel bir veritabanı sizin için daha yararlı olabilir. Tablolar oluşturmak ve bunları sorgulamak için bir veri yapısı olarak bir bellek içi sqlite3 veritabanı kullanabilirsiniz.

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

Bu sadece basit bir örnek. Eyaletler, ilçeler ve iş unvanları için ayrı tablolar tanımlayabilirsiniz.


5

collections.defaultdictiç içe bir diksiyon yapmak için alt sınıflara ayrılabilir. Ardından bu sınıfa yararlı yineleme yöntemleri ekleyin.

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)

1
Aradığım şeye en yakın cevap bu. Ancak ideal olarak, walk_keys () veya benzeri her türlü yardımcı fonksiyon olacaktır. Standart kütüphanelerde bunu yapacak bir şey olmadığına şaşırdım.
YGA

4

"İğrenç denemek / yakalamak blokları" gelince:

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

verim

{'key': {'inner key': {'inner inner key': 'value'}}}

Düz sözlük biçiminizden yapılandırılmış biçime dönüştürmek için bunu kullanabilirsiniz:

fd = {('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}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v

4

Addict'i kullanabilirsiniz: https://github.com/mewwts/addict

>>> from addict import Dict
>>> my_new_shiny_dict = Dict()
>>> my_new_shiny_dict.a.b.c.d.e = 2
>>> my_new_shiny_dict
{'a': {'b': {'c': {'d': {'e': 2}}}}}

4

defaultdict() senin arkadaşın!

İki boyutlu bir sözlük için şunları yapabilirsiniz:

d = defaultdict(defaultdict)
d[1][2] = 3

Daha fazla boyut için şunları yapabilirsiniz:

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4

Bu cevap en iyi ihtimalle sadece üç seviye için geçerlidir. Keyfi seviyeler için bu cevabı düşünün .
Acumenus

3

Yuvalanmış sözlüğünüzü kolayca yinelemek için neden sadece basit bir jeneratör yazmıyorsunuz?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

Böylece, derlenmiş iç içe sözlüğünüz varsa, yineleme basitleşir:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

Açıkçası, jeneratörünüz sizin için yararlı olan herhangi bir veri formatını verebilir.

Ağacı okumak için neden try catch blokları kullanıyorsunuz? Bir anahtarı geri almaya çalışmadan önce bir dikte var olup olmadığını sorgulamak yeterince kolaydır (ve muhtemelen daha güvenlidir). Koruma cümleleri kullanan bir işlev şöyle görünebilir:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

Ya da belki biraz ayrıntılı bir yöntem get yöntemini kullanmaktır:

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

Ama biraz daha özlü bir şekilde, python 2.5'ten beri standart kütüphanenin bir parçası olan bir collections.defaultdict kullanmaya bakmak isteyebilirsiniz .

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

Burada veri yapınızın anlamı hakkında varsayımlar yapıyorum, ama aslında ne yapmak istediğinize göre ayar yapmak kolay olmalı.


2

Ben bir sınıfta bu sarma ve uygulama __getitem__ve __setitem__basit bir sorgu dili uygulandığı şekilde seviyorum :

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

Eğer fantezi almak istiyorsanız, aşağıdakileri de uygulayabilirsiniz:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

ama çoğunlukla böyle bir şeyin uygulanması gerçekten eğlenceli olacağını düşünüyorum: D


Bence bu kötü bir fikir - anahtarların sözdizimini asla tahmin edemezsiniz. Yine de getitem ve setitem'i geçersiz kılarsınız , ancak tuples almasını istersiniz .
YGA

3
@YGA Muhtemelen haklısınız, ancak bunun gibi mini diller uygulamayı düşünmek eğlencelidir.
Aaron Maenpaa

1

Veri kümeniz oldukça küçük kalmayacaksa, ilişkisel bir veritabanı kullanmayı düşünebilirsiniz. Tam olarak ne istediğinizi yapacak: sayım eklemeyi, sayım alt kümelerini seçmeyi ve hatta eyalet, ilçe, meslek veya bunların herhangi bir kombinasyonuna göre toplam sayımları kolaylaştırır.


1
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

Misal:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

Düzenleme: Artık joker karakterlerle ( None) ve tek değerlerle sorgulama yaparken sözlükler döndürüyor .


Listeleri neden iade etmeliyim? Bir sözlük (her sayının neyi temsil ettiğini biliyorsunuz) veya bir toplam (listeyle gerçekten yapabileceğiniz tek şey bu olduğundan) döndürmesi gerekir.
Ben Blank

0

Bende benzer bir şey var. Yaptığım birçok durum var:

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

Ama çok derinlere iniyor. ".Get (item, {})" anahtardır, çünkü henüz bir sözlük yoksa başka bir sözlük yapar. Bu arada, bununla daha iyi başa çıkmanın yollarını düşünüyorum. Şu anda, çok fazla

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

Bunun yerine, yaptım:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

Aşağıdakileri yaparsanız aynı etkiye sahiptir:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

Daha iyi? Sanırım.


0

Yinelemeyi lambdas ve defaultdict içinde kullanabilirsiniz, ad tanımlamanıza gerek yoktur:

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

İşte bir örnek:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})

0

Bu işlevi kullanırdım. güvenli, hızlı, kolay bakım.

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

Misal :

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
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.