Cách tốt nhất để thực hiện từ điển lồng nhau là gì?


201

Tôi có một cấu trúc dữ liệu mà về cơ bản là một từ điển lồng nhau. Hãy nói nó trông như thế này:

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

Bây giờ, duy trì và tạo ra điều này là khá đau đớn; mỗi khi tôi có một tiểu bang / quận / nghề nghiệp mới, tôi phải tạo ra các từ điển lớp thấp hơn thông qua các khối thử / bắt đáng ghét. Hơn nữa, tôi phải tạo các vòng lặp lồng nhau gây phiền nhiễu nếu tôi muốn đi qua tất cả các giá trị.

Tôi cũng có thể sử dụng bộ dữ liệu làm khóa, như vậy:

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

Điều này làm cho việc lặp đi lặp lại các giá trị rất đơn giản và tự nhiên, nhưng sẽ khó khăn hơn về mặt cú pháp khi thực hiện những việc như tập hợp và xem các tập con của từ điển (ví dụ: nếu tôi chỉ muốn đi theo trạng thái).

Về cơ bản, đôi khi tôi muốn nghĩ về một từ điển lồng nhau như một từ điển phẳng, và đôi khi tôi muốn nghĩ về nó thực sự là một hệ thống phân cấp phức tạp. Tôi có thể gói tất cả những thứ này trong một lớp, nhưng có vẻ như ai đó có thể đã làm điều này rồi. Ngoài ra, có vẻ như có thể có một số cấu trúc cú pháp thực sự thanh lịch để làm điều này.

Làm thế nào tôi có thể làm điều này tốt hơn?

Phụ lục: Tôi biết setdefault()nhưng nó không thực sự tạo ra cú pháp rõ ràng. Ngoài ra, mỗi từ điển phụ bạn tạo vẫn cần phải setdefault()được đặt thủ công.

Câu trả lời:


179

Cách tốt nhất để thực hiện từ điển lồng nhau trong Python là gì?

Đây là một ý tưởng tồi, đừng làm điều đó. Thay vào đó, hãy sử dụng một từ điển thông thường và sử dụng dict.setdefaultnơi apropos, vì vậy khi thiếu các khóa trong sử dụng bình thường, bạn sẽ nhận được mong đợi KeyError. Nếu bạn khăng khăng nhận hành vi này, đây là cách tự bắn vào chân mình:

Thực hiện __missing__trên một dictlớp con để thiết lập và trả về một thể hiện mới.

Cách tiếp cận này đã có sẵn (và được ghi lại) kể từ Python 2.5 và (đặc biệt có giá trị đối với tôi), nó in đẹp như một bản chính tả , thay vì in ấn xấu xí của một bản mặc định tự động:

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

(Lưu ý self[key]ở phía bên trái của bài tập, vì vậy không có đệ quy ở đây.)

và nói rằng bạn có một số dữ liệu:

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}

Đây là mã sử dụng của chúng tôi:

vividict = Vividict()
for (state, county, occupation), number in data.items():
    vividict[state][county][occupation] = number

Và bây giờ:

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

Sự chỉ trích

Một lời chỉ trích về loại container này là nếu người dùng viết sai một khóa, mã của chúng tôi có thể thất bại trong âm thầm:

>>> vividict['new york']['queens counyt']
{}

Và ngoài ra, bây giờ chúng tôi có một quận sai chính tả trong dữ liệu của chúng tôi:

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

Giải trình:

Chúng tôi chỉ cung cấp một thể hiện lồng nhau khác của lớp chúng tôi Vividictbất cứ khi nào khóa được truy cập nhưng bị thiếu. (Trả lại việc gán giá trị là hữu ích vì nó tránh cho chúng tôi gọi thêm getter trên dict, và thật không may, chúng tôi không thể trả lại nó khi nó đang được đặt.)

Lưu ý, đây là những ngữ nghĩa giống như câu trả lời được đánh giá cao nhất nhưng trong một nửa dòng mã - triển khai của nosklo:

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

Trình diễn sử dụng

Dưới đây chỉ là một ví dụ về cách có thể dễ dàng sử dụng dict này để tạo ra một cấu trúc chính tả lồng nhau khi đang bay. Điều này có thể nhanh chóng tạo ra một cấu trúc cây phân cấp sâu như bạn muốn đi.

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)

Đầu ra nào:

{'fizz': {'buzz': {}},
 'foo': {'bar': {}, 'baz': {}},
 'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}

Và như dòng cuối cùng cho thấy, nó in khá đẹp và để kiểm tra thủ công. Nhưng nếu bạn muốn kiểm tra trực quan dữ liệu của mình, triển khai __missing__để đặt một thể hiện mới của lớp của nó thành khóa và trả lại thì đó là một giải pháp tốt hơn nhiều.

Các lựa chọn thay thế khác, tương phản:

dict.setdefault

Mặc dù người hỏi nghĩ rằng điều này không sạch, nhưng tôi thấy nó tốt hơn cho Vividictbản thân mình.

d = {} # or dict()
for (state, county, occupation), number in data.items():
    d.setdefault(state, {}).setdefault(county, {})[occupation] = number

và bây giờ:

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

Một lỗi chính tả sẽ thất bại một cách ồn ào và không làm lộn xộn dữ liệu của chúng tôi với thông tin xấu:

>>> d['new york']['queens counyt']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'

Ngoài ra, tôi nghĩ setdefault hoạt động rất tốt khi được sử dụng trong các vòng lặp và bạn không biết bạn sẽ lấy gì cho khóa, nhưng việc sử dụng lặp đi lặp lại trở nên khá nặng nề và tôi không nghĩ ai sẽ muốn theo kịp:

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', {})

Một chỉ trích khác là setdefault yêu cầu một thể hiện mới cho dù nó được sử dụng hay không. Tuy nhiên, Python (hoặc ít nhất là CPython) khá thông minh trong việc xử lý các trường hợp mới không được sử dụng và không được kiểm tra, ví dụ, nó sử dụng lại vị trí trong bộ nhớ:

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

Một defaultdict tự động sinh động

Đây là một triển khai tìm kiếm gọn gàng và việc sử dụng trong một tập lệnh mà bạn không kiểm tra dữ liệu trên sẽ hữu ích như triển khai __missing__:

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

Nhưng nếu bạn cần kiểm tra dữ liệu của mình, kết quả của một defaultdict được tự động hóa được điền với dữ liệu theo cách tương tự như sau:

>>> 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>, {})})})

Đầu ra này là không phù hợp, và kết quả là không thể đọc được. Giải pháp thường được đưa ra là chuyển đổi đệ quy trở lại thành một lệnh để kiểm tra thủ công. Giải pháp không tầm thường này được để lại như một bài tập cho người đọc.

Hiệu suất

Cuối cùng, hãy nhìn vào hiệu suất. Tôi đang trừ chi phí khởi tạo.

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

Dựa trên hiệu suất, dict.setdefaulthoạt động tốt nhất. Tôi rất muốn giới thiệu nó cho mã sản xuất, trong trường hợp bạn quan tâm đến tốc độ thực hiện.

Nếu bạn cần điều này để sử dụng tương tác (có thể là trong một máy tính xách tay IPython) thì hiệu suất không thực sự quan trọng - trong trường hợp đó, tôi sẽ đi với Vividict để dễ đọc đầu ra. So với đối tượng AutoVivification (sử dụng __getitem__thay vì __missing__, được tạo ra cho mục đích này), nó vượt trội hơn nhiều.

Phần kết luận

Việc triển khai __missing__trên một lớp con dictđể thiết lập và trả về một thể hiện mới khó hơn một chút so với các lựa chọn thay thế nhưng có lợi ích của

  • khởi tạo dễ dàng
  • dân số dữ liệu dễ dàng
  • xem dữ liệu dễ dàng

và bởi vì nó ít phức tạp hơn và hiệu quả hơn so với sửa đổi __getitem__, nên nó được ưa thích hơn phương pháp đó.

Tuy nhiên, nó có nhược điểm:

  • Tra cứu xấu sẽ thất bại âm thầm.
  • Tra cứu xấu sẽ vẫn còn trong từ điển.

Vì vậy, cá nhân tôi thích setdefaultcác giải pháp khác, và trong mọi tình huống mà tôi cần loại hành vi này.


Câu trả lời tuyệt vời! Có cách nào để xác định độ sâu hữu hạn và loại lá cho a Vividictkhông? Ví dụ 3listcho một dict of dict of dict của danh sách có thể được điền với d['primary']['secondary']['tertiary'].append(element). Tôi có thể định nghĩa 3 lớp khác nhau cho mỗi độ sâu nhưng tôi muốn tìm một giải pháp sạch hơn.
Eric Duminil

@EricDuminil d['primary']['secondary'].setdefault('tertiary', []).append('element')- ?? Cảm ơn lời khen, nhưng hãy trung thực - tôi không bao giờ thực sự sử dụng __missing__- tôi luôn sử dụng setdefault. Tôi có lẽ nên cập nhật kết luận / giới thiệu của mình ...
Aaron Hall

@AaronHall Hành vi đúng là mã sẽ tạo ra một lệnh nếu cần. Trong trường hợp này bằng cách ghi đè giá trị được gán trước đó.
nehem

@AaronHall Ngoài ra, bạn có thể giúp tôi hiểu ý nghĩa của The bad lookup will remain in the dictionary.việc sử dụng giải pháp này không? Nhiều đánh giá cao. Thx
nehem

@AaronHall Vấn đề với nó sẽ thất bại setdefaultkhi nó lồng nhiều hơn hai cấp độ sâu. Có vẻ như không có cấu trúc nào trong Python có thể mang lại sự sinh động thực sự như được mô tả. Tôi đã phải giải quyết hai phương thức nêu một get_nestedvà cho một phương thức set_nestedchấp nhận tham chiếu cho dict và danh sách các thuộc tính lồng nhau.
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

Kiểm tra:

a = AutoVivification()

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

print a

Đầu ra:

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

Bất cứ ai cũng có vấn đề này khi họ chuyển sang python 3.x? stackoverflow.com/questions/54622935/ từ
jason

@jason picklelà khủng khiếp giữa các phiên bản python. Tránh sử dụng nó để lưu trữ dữ liệu bạn muốn giữ. Chỉ sử dụng nó cho bộ nhớ cache và những thứ bạn có thể đổ và tái tạo theo ý muốn. Không phải là một phương pháp lưu trữ hoặc tuần tự hóa dài hạn.
nosklo

Bạn dùng gì để lưu trữ những đồ vật này? Đối tượng tự động hóa của tôi chỉ chứa các chuỗi dữ liệu và chuỗi gấu trúc.
jason

@jason Tùy thuộc vào dữ liệu, tôi thích sử dụng các tệp JSON, csv hoặc thậm chí là sqlitecơ sở dữ liệu để lưu trữ.
nosklo

30

Chỉ vì tôi chưa thấy cái này nhỏ, đây là một câu lệnh được lồng như bạn muốn, không có mồ hôi:

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

2
@wberry: Thật ra tất cả những gì bạn cần là yodict = lambda: defaultdict(yodict).
martineau

1
Phiên bản được chấp nhận là một lớp con của dict, vì vậy để tương đương hoàn toàn, chúng ta sẽ cần x = Vdict(a=1, b=2)phải làm việc.
dâu

@wberry: Không phân biệt câu trả lời được chấp nhận, là một lớp con dictkhông phải là yêu cầu của OP, người chỉ yêu cầu "cách tốt nhất" để thực hiện chúng - và bên cạnh đó, không nên / không nên vấn đề đó rất nhiều trong Python.
martineau

24

Bạn có thể tạo một tệp YAML và đọc nó bằng PyYaml .

Bước 1: Tạo tệp YAML, "jobs.yml":

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

Bước 2: Đọc nó trong Python

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

và bây giờ my_shnazzy_dictionarycó tất cả các giá trị của bạn. Nếu bạn cần thực hiện việc này một cách nhanh chóng, bạn có thể tạo YAML dưới dạng chuỗi và đưa dữ liệu đó vào yaml.safe_load(...).


4
YAML chắc chắn là lựa chọn của tôi để nhập nhiều dữ liệu được lồng sâu (và các tệp cấu hình, mockup databaes, v.v ...). Nếu OP không muốn có thêm các tệp nằm xung quanh, chỉ cần sử dụng chuỗi Python thông thường trong một số tệp và phân tích cú pháp đó với YAML.
kmelvn

Điểm hay trong việc tạo chuỗi YAML: Đây sẽ là một cách tiếp cận sạch hơn nhiều so với việc sử dụng mô-đun "tempfile" nhiều lần.
Pete

18

Vì bạn có một thiết kế lược đồ sao, bạn có thể muốn cấu trúc nó giống như một bảng quan hệ và ít giống như một từ điển hơn.

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

Điều đó có thể đi một chặng đường dài để tạo ra một thiết kế giống như kho dữ liệu mà không cần chi phí SQL.


14

Nếu số cấp độ lồng là nhỏ, tôi sử dụng collections.defaultdictcho điều này:

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

Sử dụng defaultdictnhư thế này tránh rất nhiều lộn xộn setdefault(), get()vv


+1: defaultdict là một trong những bổ sung yêu thích mọi thời đại của tôi cho python. Không còn .setdefault ()!
John Fouhy

8

Đây là một hàm trả về một từ điển lồng nhau có độ sâu tùy ý:

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

Sử dụng nó như thế này:

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"

Lặp lại tất cả mọi thứ với một cái gì đó như thế này:

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)

Điều này in ra:

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

Cuối cùng, bạn có thể muốn làm cho nó để các mục mới không thể được thêm vào dict. Thật dễ dàng để chuyển đổi đệ quy tất cả các defaultdicts này sang dicts bình thường .

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

7

Tôi thấy setdefaultkhá hữu ích; Nó kiểm tra nếu một khóa có mặt và thêm nó nếu không:

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

setdefaultluôn trả về khóa có liên quan, vì vậy bạn thực sự đang cập nhật các giá trị của ' d' tại chỗ.

Khi nói đến việc lặp lại, tôi chắc chắn rằng bạn có thể viết một trình tạo đủ dễ dàng nếu nó không tồn tại trong Python:

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)

Tôi thích giải pháp này nhưng khi tôi thử: Count.setdefault (a, {}). Setdefault (b, {}). Setdefault (c, 0) + = 1 Tôi nhận được "biểu thức bất hợp pháp cho phép gán tăng"
dfrankow

6

Như những người khác đã đề xuất, một cơ sở dữ liệu quan hệ có thể hữu ích hơn cho bạn. Bạn có thể sử dụng cơ sở dữ liệu sqlite3 trong bộ nhớ làm cấu trúc dữ liệu để tạo các bảng và sau đó truy vấn chúng.

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

Đây chỉ là một ví dụ đơn giản. Bạn có thể xác định các bảng riêng biệt cho các tiểu bang, quận và chức danh công việc.


5

collections.defaultdictcó thể được phân lớp để tạo ra một dict lồng nhau. Sau đó thêm bất kỳ phương thức lặp hữu ích nào vào lớp đó.

>>> 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
Đây là câu trả lời gần nhất với những gì tôi đang tìm kiếm. Nhưng lý tưởng là sẽ có tất cả các loại hàm trợ giúp, ví dụ walk_keys () hoặc như vậy. Tôi ngạc nhiên không có gì trong các thư viện tiêu chuẩn để làm điều này.
YGA

4

Còn đối với "khối thử / bắt đáng ghét":

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

sản lượng

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

Bạn có thể sử dụng điều này để chuyển đổi từ định dạng từ điển phẳng sang định dạng có cấu trúc:

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

Bạn có thể sử dụng Nghiện: 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() là bạn của bạn!

Đối với một từ điển hai chiều, bạn có thể làm:

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

Để biết thêm kích thước, bạn có thể:

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

Câu trả lời này chỉ hoạt động cho ba cấp độ tốt nhất. Đối với mức độ tùy ý, xem xét câu trả lời này .
Acumenus

3

Để dễ dàng lặp lại từ điển lồng nhau của bạn, tại sao không chỉ viết một trình tạo đơn giản?

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
                }

Vì vậy, nếu bạn có từ điển lồng ghép tổng hợp của mình, việc lặp lại nó sẽ trở nên đơn giản:

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

Rõ ràng trình tạo của bạn có thể mang lại bất kỳ định dạng dữ liệu nào hữu ích cho bạn.

Tại sao bạn sử dụng thử khối bắt để đọc cây? Nó đủ dễ dàng (và có thể an toàn hơn) để truy vấn liệu một khóa có tồn tại trong một lệnh hay không trước khi thử truy xuất nó. Một hàm sử dụng mệnh đề bảo vệ có thể trông như thế này:

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

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

Hoặc, một phương pháp có lẽ hơi dài dòng, là sử dụng phương thức get:

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

Nhưng đối với một cách ngắn gọn hơn, bạn có thể muốn xem xét bằng cách sử dụng bộ sưu tập.defaultdict , một phần của thư viện chuẩn kể từ python 2.5.

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']

Tôi đang đưa ra các giả định về ý nghĩa của cấu trúc dữ liệu của bạn ở đây, nhưng nó sẽ dễ dàng điều chỉnh cho những gì bạn thực sự muốn làm.


2

Tôi thích ý tưởng gói cái này trong một lớp và thực hiện __getitem____setitem__để chúng thực hiện một ngôn ngữ truy vấn đơn giản:

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

Nếu bạn muốn có được sự ưa thích, bạn cũng có thể thực hiện một cái gì đó như:

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

nhưng chủ yếu tôi nghĩ một điều như vậy sẽ rất thú vị khi thực hiện: D


Tôi nghĩ rằng đây là một ý tưởng tồi - bạn không bao giờ có thể dự đoán cú pháp của các khóa. Bạn vẫn sẽ ghi đè getitemsetitem nhưng để chúng mất các bộ dữ liệu.
YGA

3
@YGA Có lẽ bạn đúng, nhưng thật vui khi nghĩ đến việc triển khai các ngôn ngữ nhỏ như thế này.
Aaron Maenpaa

1

Trừ khi dữ liệu của bạn sẽ ở mức khá nhỏ, bạn có thể muốn xem xét sử dụng cơ sở dữ liệu quan hệ. Nó sẽ thực hiện chính xác những gì bạn muốn: giúp dễ dàng thêm số lượng, chọn tập hợp con của số đếm và thậm chí tổng số theo tiểu bang, hạt, nghề nghiệp hoặc bất kỳ sự kết hợp nào của chúng.


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

Thí dụ:

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

>>>

Chỉnh sửa: Bây giờ trả lại từ điển khi truy vấn bằng thẻ đại diện ( None) và các giá trị đơn lẻ khác.


Tại sao trả lại danh sách? Có vẻ như nó sẽ trả về một từ điển (để bạn biết mỗi số đại diện cho cái gì) hoặc một tổng (vì đó là tất cả những gì bạn thực sự có thể làm với danh sách).
Ben Trống

0

Tôi có một điều tương tự đi. Tôi có rất nhiều trường hợp tôi làm:

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

Nhưng đi sâu nhiều cấp. Đó là ".get (item, {})" đó là chìa khóa vì nó sẽ tạo một từ điển khác nếu chưa có từ điển. Trong khi đó, tôi đã nghĩ cách để giải quyết vấn đề này tốt hơn. Ngay bây giờ, có rất nhiều

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

Vì vậy, thay vào đó, tôi đã thực hiện:

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

Điều này có tác dụng tương tự nếu bạn làm:

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

Tốt hơn? Tôi nghĩ vậy.


0

Bạn có thể sử dụng đệ quy trong lambdas và defaultdict, không cần xác định tên:

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

Đây là một ví dụ:

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

Tôi đã từng sử dụng chức năng này. nó an toàn, nhanh chóng, dễ bảo trì.

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)

Thí dụ :

>>> 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
>>>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.