سؤال

لدي بنية بيانات والتي هي في الأساس عبارة عن قاموس متداخل.لنفترض أن الأمر يبدو كالتالي:

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

الآن، يعد الحفاظ على هذا وإنشاءه أمرًا مؤلمًا جدًا؛في كل مرة يكون لدي ولاية/مقاطعة/مهنة جديدة، يجب علي إنشاء قواميس الطبقة السفلية عبر كتل المحاولة/الالتقاط البغيضة.علاوة على ذلك، لا بد لي من إنشاء تكرارات متداخلة مزعجة إذا كنت أرغب في مراجعة جميع القيم.

يمكنني أيضًا استخدام الصفوف كمفاتيح، مثل:

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

وهذا يجعل التكرار على القيم أمرًا بسيطًا وطبيعيًا للغاية، ولكنه أكثر إيلامًا من الناحية النحوية عند القيام بأشياء مثل التجميعات والنظر إلى مجموعات فرعية من القاموس (على سبيل المثال.إذا كنت أرغب فقط في الذهاب إلى كل ولاية على حدة).

في الأساس، أحيانًا أريد أن أفكر في القاموس المتداخل باعتباره قاموسًا مسطحًا، وأحيانًا أريد أن أفكر فيه بالفعل باعتباره تسلسلًا هرميًا معقدًا.يمكنني أن أختتم كل هذا في الفصل الدراسي، ولكن يبدو أن شخصًا ما ربما قام بذلك بالفعل.وبدلاً من ذلك، يبدو أنه قد تكون هناك بعض الإنشاءات النحوية الأنيقة للقيام بذلك.

كيف يمكنني أن أفعل هذا بشكل أفضل؟

إضافة:أنا على علم setdefault() لكنها لا تؤدي حقًا إلى بناء جملة نظيف.بالإضافة إلى ذلك، لا يزال كل قاموس فرعي تقوم بإنشائه بحاجة إلى الحصول عليه setdefault() تعيين يدويا.

هل كانت مفيدة؟

المحلول

ما هي أفضل طريقة لتنفيذ القواميس المتداخلة في بايثون؟

ينفذ __missing__ على dict فئة فرعية لتعيين وإرجاع مثيل جديد.

وقد أصبح هذا النهج متاحا (و موثقة) منذ Python 2.5، و(ذو قيمة خاصة بالنسبة لي) إنها تطبع بشكل جميل تمامًا مثل الإملاء العادي, ، بدلاً من الطباعة القبيحة للإملاء الافتراضي الذي تم تنشيطه تلقائيًا:

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

(ملحوظة self[key] على الجانب الأيسر من المهمة، لذا لا يوجد تكرار هنا.)

ونقول أن لديك بعض البيانات:

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}

إليك رمز الاستخدام الخاص بنا:

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

و الأن:

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

نقد

انتقاد هذا النوع من الحاويات هو أنه إذا أخطأ المستخدم في كتابة مفتاح، فقد يفشل الكود الخاص بنا بصمت:

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

وبالإضافة إلى ذلك، سيكون لدينا الآن مقاطعة بها خطأ إملائي في بياناتنا:

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

توضيح:

نحن نقدم فقط مثيلًا متداخلًا آخر لفصلنا Vividict كلما تم الوصول إلى المفتاح ولكنه مفقود.(يُعد إرجاع تعيين القيمة مفيدًا لأنه يتجنبنا بالإضافة إلى ذلك استدعاء المُحصل على الإملاء، ولسوء الحظ، لا يمكننا إعادته كما يتم تعيينه.)

لاحظ أن هذه هي نفس دلالات الإجابة الأكثر تأييدًا ولكن في نصف سطور التعليمات البرمجية - تنفيذ 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

مظاهرة الاستخدام

يوجد أدناه مجرد مثال لكيفية استخدام هذا الإملاء بسهولة لإنشاء بنية إملاء متداخلة بسرعة.يمكن أن يؤدي هذا إلى إنشاء بنية شجرة هرمية بسرعة بالعمق الذي قد ترغب فيه.

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)

ما هي المخرجات:

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

وكما يظهر السطر الأخير، فإنه يطبع بشكل جميل ومن أجل الفحص اليدوي.ولكن إذا كنت تريد فحص بياناتك بصريًا، فقم بالتنفيذ __missing__ يعد تعيين مثيل جديد لفئته على المفتاح وإعادته حلاً أفضل بكثير.

بدائل أخرى، على النقيض من ذلك:

dict.setdefault

ورغم أن السائل يعتقد أن هذا غير نظيف، إلا أنني أجده أفضل من Vividict نفسي.

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

و الأن:

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

من شأن الخطأ الإملائي أن يفشل بشكل صاخب، ولن يؤدي إلى ازدحام بياناتنا بمعلومات سيئة:

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

بالإضافة إلى ذلك، أعتقد أن setdefault يعمل بشكل رائع عند استخدامه في الحلقات ولا تعرف ما الذي ستحصل عليه مقابل المفاتيح، لكن الاستخدام المتكرر يصبح مرهقًا للغاية، ولا أعتقد أن أي شخص قد يرغب في متابعة ما يلي:

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

انتقاد آخر هو أن setdefault يتطلب مثيلًا جديدًا سواء تم استخدامه أم لا.ومع ذلك، فإن Python (أو CPython على الأقل) ذكية إلى حد ما في التعامل مع المثيلات الجديدة غير المستخدمة وغير المرجعية، على سبيل المثال، تعيد استخدام الموقع في الذاكرة:

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

الإفتراض الافتراضي الذي تم تنشيطه تلقائيًا

يعد هذا تنفيذًا أنيقًا، واستخدامه في برنامج نصي لا تقوم بفحص البيانات عليه سيكون مفيدًا مثل التنفيذ __missing__:

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

ولكن إذا كنت بحاجة إلى فحص بياناتك، فإن نتائج الإفتراض الافتراضي الذي تم تنشيطه تلقائيًا والمملوء بالبيانات بنفس الطريقة تبدو كما يلي:

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

هذا الإخراج غير أنيق تمامًا، والنتائج غير قابلة للقراءة تمامًا.الحل المقدم عادةً هو التحويل بشكل متكرر مرة أخرى إلى الإملاء للفحص اليدوي.يتم ترك هذا الحل غير التافه كتمرين للقارئ.

أداء

وأخيرا، دعونا ننظر إلى الأداء.أنا أطرح تكاليف إنشاء مثيل.

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

بناءً على الأداء، dict.setdefault يعمل بشكل أفضل.أوصي به بشدة لرمز الإنتاج، في الحالات التي تهتم فيها بسرعة التنفيذ.

إذا كنت بحاجة إلى هذا للاستخدام التفاعلي (ربما في دفتر ملاحظات IPython) فإن الأداء لا يهم حقًا - وفي هذه الحالة، سأختار Vividict لسهولة قراءة المخرجات.بالمقارنة مع كائن AutoVivification (الذي يستخدم __getitem__ بدلاً من __missing__, الذي صنع لهذا الغرض) فهو أفضل بكثير.

خاتمة

تنفيذ __missing__ على فئة فرعية dict يعد تعيين مثيل جديد وإعادته أصعب قليلاً من البدائل ولكن له فوائد

  • إنشاء مثيل سهل
  • سهولة تجميع البيانات
  • سهولة عرض البيانات

ولأنه أقل تعقيدًا وأكثر أداءً من التعديل __getitem__, ، فينبغي تفضيل تلك الطريقة.

ومع ذلك، فإن له عيوب:

  • سوف تفشل عمليات البحث السيئة بصمت.
  • سيبقى البحث السيئ في القاموس.

وهكذا أنا شخصياً أفضل ذلك setdefault إلى الحلول الأخرى، وفي كل موقف حيث كنت بحاجة إلى هذا النوع من السلوك.

نصائح أخرى

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

والاختبار:

a = AutoVivification()

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

print a

وإخراج:

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

وفقط لأنني لم أر واحدة هذا صغيرة، وهنا ديكت أن يحصل كما متداخلة كما تريد، لا عرق:

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

هل يمكن إنشاء ملف YAML وقراءته في استخدام PyYaml .

الخطوة 1: إنشاء ملف YAML "employment.yml":

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

الخطوة 2: قراءة في بيثون

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

والآن my_shnazzy_dictionary لديه كل القيم الخاصة بك. إذا كنت في حاجة للقيام بذلك على الطاير، يمكنك إنشاء YAML كسلسلة وتغذية ذلك في yaml.safe_load(...).

ومنذ لديك تصميم النجوم مخطط، قد ترغب في تنظيم ذلك أشبه الجدول العلائقية وأقل مثل القاموس.

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

وهذا النوع من شيء يمكن أن تقطع شوطا طويلا لخلق مثل مستودع بيانات التصميم دون النفقات العامة SQL.

وإذا كان عدد من المستويات التعشيش صغير، وأنا استخدم 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

وعن طريق defaultdict مثل هذا يتجنب الكثير من setdefault() الفوضى، get()، وما إلى ذلك.

وهذه هي وظيفة يقوم بإرجاع القاموس متداخلة من عمق التعسفي:

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

استخدم مثل هذا:

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"

وتكرار خلال كل شيء مع شيء من هذا القبيل:

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)

وهذا بطباعة:

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

وقد ترغب في نهاية المطاف لجعله بحيث لا يمكن إضافة عناصر جديدة إلى ديكت. فإنه من السهل لتحويل متكرر كل هذه defaultdicts إلى dicts وضعها الطبيعي.

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

وأجد setdefault مفيدة للغاية. فإنه يتحقق إذا كان المفتاح موجود ويضيف أنه إن لم يكن:

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

وsetdefault دوما بإرجاع مفتاح ذات الصلة، حتى انك فعلا تحديث قيم "d" في المكان.

وعندما يتعلق الأمر بالتكرار، وأنا متأكد من أنك يمكن أن يكتب مولد بسهولة بما فيه الكفاية إذا كان أحد غير موجود مسبقا في بيثون:

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)

وكما اقترح آخرون، يمكن أن تكون قاعدة بيانات علائقية أكثر فائدة لك. يمكنك استخدام SQLite3 قاعدة البيانات في الذاكرة كهيكل البيانات لإنشاء الجداول وثم الاستعلام منهم.

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

وهذا مجرد مثال بسيط. هل يمكن تحديد جداول منفصلة عن الولايات والمقاطعات والمسميات الوظيفية.

وcollections.defaultdict يمكن أن يكون شبه تصنف لجعل ديكت متداخلة. ثم إضافة أية أساليب التكرار مفيد لتلك الفئة.

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

وdefaultdict() هو صديقك!

لقاموس ثنائي الأبعاد يمكنك القيام به:

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

لمزيد من أبعاد يمكنك:

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

وأما بالنسبة "لبنات حاول / catch البغيضة":

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

وعوائد

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

ويمكنك استخدام هذا لتحويل من صيغة القاموس الشقة الخاصة بك إلى تنسيق منظم:

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

لبالتكرار سهلا على القاموس متداخلة، لماذا لا مجرد كتابة مولد بسيط؟

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
                }

وحتى ذلك الحين، إذا كان لديك قاموس متداخلة compilicated الخاص بك، بالتكرار عبر يصبح بسيط:

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

ومن الواضح أن المولدات الخاصة بك يمكن أن تسفر عن أي شكل من البيانات هو مفيد لك.

لماذا أنت باستخدام كتل الصيد محاولة لقراءة الشجرة؟ فإنه من السهل بما فيه الكفاية (وأكثر أمانا على الأرجح) للاستعلام إذا كان المفتاح موجود في ديكت قبل محاولة للعثور عليها. وظيفة باستخدام بنود الحرس قد تبدو هذه:

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

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

وأو طريقة ربما مطول بعض الشيء، هو استخدام طريقة الحصول على:

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

ولكن عن وسيلة إلى حد ما أكثر مقتضبة، قد ترغب في النظر في استخدام <لأ href = "http://docs.python.org/library/collections.html#collections.defaultdict" يختلط = "نوفولو noreferrer" > collections.defaultdict ، الذي هو جزء من المكتبة القياسية منذ الثعبان 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']

وأنا وضع افتراضات حول معنى بنية البيانات الخاصة بك هنا، ولكن يجب أن يكون من السهل ضبط ما تريد فعلا القيام به.

ويمكنك استخدام مدمن: 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}}}}}

وأنا أحب فكرة التفاف هذا في فئة وتنفيذ __getitem__ و__setitem__ بحيث أنها نفذت لغة الاستعلام بسيطة:

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

إذا أردت الحصول على الهوى يمكنك أيضا تنفيذ شيء من هذا القبيل:

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

ولكن في الغالب وأعتقد أن مثل هذا الشيء أن تكون ممتعة حقا لتنفيذ: D

وإذا لم بيانات الخاصة بك سوف تبقى صغيرة جدا، قد ترغب في النظر في استخدام قواعد البيانات العلائقية. انها لن تفعل بالضبط ما تريد: تجعل من السهل لإضافة تهمة، واختيار مجموعات فرعية من التهم، وحتى المبالغ المجمعة من قبل الدولة، والمحافظة، والاحتلال، أو أي مزيج من هذه

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

مثال:

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

>>>

يحرر: يتم الآن إرجاع القواميس عند الاستعلام باستخدام أحرف البدل (None)، والقيم المفردة خلاف ذلك.

ويمكنك استخدام العودية في lambdas وdefaultdict، لا حاجة لتعريف أسماء:

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

وهنا مثال:

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

اعتدت على استخدام هذه الوظيفة.إنها آمنة وسريعة ويمكن صيانتها بسهولة.

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)

مثال :

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

ولدي شيئا مماثلا يحدث. لدي الكثير من الحالات التي أفعل:

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

ولكن الذهاب العديد من المستويات العميقة. انها ". احصل على (البند، {})" هذا هو المفتاح لأنها سوف تجعل القاموس آخر إذا لم يكن هناك احد بالفعل. وفي الوقت نفسه، لقد تم التفكير في طرق للتعامل مع هذا افضل. الآن، هناك الكثير من

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

وبدلا من ذلك، أنا جعلت:

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

والذي لديه نفس التأثير إذا قمت بذلك:

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

وأفضل؟ أعتقد ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top