पायथन में नेस्टेड शब्दकोशों को लागू करने का सबसे अच्छा तरीका क्या है?
यह एक बुरा विचार है, यह मत करो। इसके बजाय, एक नियमित शब्दकोश का उपयोग करें और dict.setdefault
जहां एप्रोपोस का उपयोग करें , इसलिए जब सामान्य उपयोग के तहत चाबियाँ गायब हों, तो आप अपेक्षित हो KeyError
। यदि आप इस व्यवहार को प्राप्त करने पर जोर देते हैं, तो यहां बताया गया है कि पैर में खुद को कैसे गोली मारनी है:
एक नया उदाहरण सेट करने और वापस करने के लिए __missing__
एक dict
उपवर्ग पर लागू करें ।
पायथन 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
जब भी कोई कुंजी एक्सेस की जाती है लेकिन गायब होती है तो हैं। (मान असाइनमेंट वापस करना उपयोगी है, क्योंकि यह हमें अतिरिक्त रूप से डिक्टेटर पर कॉल करने से रोकता है, और दुर्भाग्य से, हम इसे वापस नहीं कर सकते क्योंकि यह सेट किया जा रहा है।)
ध्यान दें, ये एक ही शब्दार्थ हैं जो सबसे अधिक उत्तर दिए गए हैं लेकिन कोड की आधी लाइनों में हैं - नोस्कोलो का कार्यान्वयन:
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'
इसके अतिरिक्त, मुझे लगता है कि छोरों में इस्तेमाल होने पर सेटडेफॉल्ट बहुत अच्छा काम करता है और आपको नहीं पता कि आपको चाबियों के लिए क्या मिलेगा, लेकिन दोहराव का उपयोग काफी बोझिल हो जाता है, और मुझे नहीं लगता कि कोई भी निम्नलिखित को रखना चाहेगा:
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', {})
एक और आलोचना यह है कि सेटडेफॉल्ट को एक नए उदाहरण की आवश्यकता होती है चाहे इसका उपयोग किया जाए या नहीं। हालाँकि, पायथन (या कम से कम 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 नोटबुक में, शायद) तो प्रदर्शन वास्तव में मायने नहीं रखता है - किस मामले में, मैं आउटपुट की पठनीयता के लिए विविड के साथ जाऊंगा। AutoVivification ऑब्जेक्ट की तुलना में (जो __getitem__
इसके बजाय उपयोग करता है __missing__
, जो इस उद्देश्य के लिए बनाया गया था) यह कहीं बेहतर है।
निष्कर्ष
एक नया उदाहरण सेट करने और वापस करने के लिए __missing__
एक उपवर्ग पर लागू dict
करना विकल्पों की तुलना में थोड़ा अधिक कठिन है लेकिन इसके फायदे हैं
- आसान तात्कालिकता
- आसान डेटा जनसंख्या
- आसान डेटा देखना
और क्योंकि यह कम जटिल है और संशोधित करने की तुलना में अधिक प्रदर्शनकारी है __getitem__
, इसलिए इसे उस पद्धति को प्राथमिकता दी जानी चाहिए।
फिर भी, इसमें कमियां हैं:
- खराब लुकअप चुपचाप विफल हो जाएगा।
- ख़राब लुक डिक्शनरी में रहेगा।
इस प्रकार मैं व्यक्तिगत रूप setdefault
से अन्य समाधानों को पसंद करता हूं , और हर स्थिति में जहां मुझे इस तरह के व्यवहार की आवश्यकता है।
Vividict
? उदाहरण के लिए3
औरlist
ऐसे तानाशाहों के हुक्मरानों की एक बड़ी संख्या के साथ जो आबाद हो सकते हैंd['primary']['secondary']['tertiary'].append(element)
। मैं प्रत्येक गहराई के लिए 3 अलग-अलग वर्गों को परिभाषित कर सकता हूं, लेकिन मुझे एक क्लीनर समाधान खोजना अच्छा लगेगा।