जेनरेटर एक्सप्रेशंस बनाम सूची समझ


411

आपको कब जनरेटर भाव का उपयोग करना चाहिए और कब पायथन में सूची बोध का उपयोग करना चाहिए?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

27
सकता है [exp for x in iter]बस के लिए चीनी हो list((exp for x in iter))? या वहाँ एक निष्पादन अंतर है?
b0fh

1
यह लगता है कि मेरे पास एक प्रासंगिक सवाल था, इसलिए उपज का उपयोग करते समय क्या हम किसी फ़ंक्शन से सिर्फ जनरेटर अभिव्यक्ति का उपयोग कर सकते हैं या हमें जनरेटर ऑब्जेक्ट को वापस करने के लिए फ़ंक्शन के लिए उपज का उपयोग करना होगा?

28
@ b0fh आपकी टिप्पणी का बहुत देर से जवाब: पायथन 2 में एक छोटा सा अंतर है, लूप वेरिएबल एक सूची समझ से बाहर लीक हो जाएगा, जबकि एक जनरेटर अभिव्यक्ति लीक नहीं होगी। के X = [x**2 for x in range(5)]; print xसाथ तुलना करें Y = list(y**2 for y in range(5)); print y, दूसरा एक त्रुटि देगा। Python3 में, एक सूची की समझ वास्तव list()में आप की उम्मीद के रूप में खिलाया जनरेटर अभिव्यक्ति के लिए वाक्यविन्यास चीनी है, इसलिए लूप चर अब लीक नहीं होगा ।
बास स्विंकल्स

12
मैं PEP 0289 पढ़ने का सुझाव देता हूं । द्वारा अभिव्यक्त किया "यह पीईपी प्रस्तुत किया जाने जनरेटर एक उच्च प्रदर्शन के रूप में भाव, सूची comprehensions और जनरेटर की स्मृति कुशल सामान्यीकरण" । इसका उपयोग कब करना है, इसके उपयोगी उदाहरण भी हैं।
icc97

5
@ icc97 मैं पार्टी में आठ साल की देरी से आया हूं, और पीईपी लिंक एकदम सही था। उस आसान को खोजने के लिए धन्यवाद!
1

जवाबों:


283

जॉन का जवाब अच्छा है (जब आप कई बार किसी चीज़ पर पुनरावृति करना चाहते हैं तो सूची की समझ बेहतर है)। हालाँकि, यह भी ध्यान देने योग्य है कि यदि आप सूची के किसी भी तरीके का उपयोग करना चाहते हैं, तो आपको एक सूची का उपयोग करना चाहिए। उदाहरण के लिए, निम्न कोड काम नहीं करेगा:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

मूल रूप से, एक जनरेटर अभिव्यक्ति का उपयोग करें यदि आप कर रहे हैं एक बार पुनरावृत्ति। यदि आप जनरेट किए गए परिणामों को संग्रहीत और उपयोग करना चाहते हैं, तो संभवतः आप सूची बोध के साथ बेहतर हैं।

चूंकि प्रदर्शन एक दूसरे को चुनने का सबसे आम कारण है, इसलिए मेरी सलाह है कि इसके बारे में चिंता न करें और सिर्फ एक को चुनें; यदि आप पाते हैं कि आपका कार्यक्रम बहुत धीरे-धीरे चल रहा है, तो और उसके बाद ही आपको वापस जाना चाहिए और अपने कोड को ट्यून करने की चिंता करनी चाहिए।


70
कभी-कभी आपको जनरेटर का उपयोग करना पड़ता है - उदाहरण के लिए, यदि आप पैदावार का उपयोग करते हुए सहकारी समय-निर्धारण के साथ कॉरटाइन लिख रहे हैं। लेकिन अगर आप ऐसा कर रहे हैं, तो शायद आप यह सवाल नहीं पूछ रहे हैं;)
2

12
मुझे पता है कि यह पुराना है, लेकिन मुझे लगता है कि यह ध्यान देने योग्य है कि जनरेटर (और किसी भी चलने योग्य) को विस्तार के साथ सूचियों में जोड़ा जा सकता है: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- अब एक [1, 2, 3, 4, 5, 6] होगा। (क्या आप टिप्पणियों में नई सूची जोड़ सकते हैं ??)
jarvisteve

12
@jarvisteve आपका उदाहरण उन शब्दों को मानता है जो आप कह रहे हैं। यहाँ एक अच्छा बिंदु भी है। जनरेटर के साथ सूचियों को बढ़ाया जा सकता है, लेकिन तब इसे जनरेटर बनाने का कोई मतलब नहीं था। जेनरेटरों को सूचियों के साथ नहीं बढ़ाया जा सकता है, और जनरेटर काफी पुनरावृत्त नहीं हैं। a = (x for x in range(0,10)), b = [1,2,3]उदाहरण के लिए। a.extend(b)एक अपवाद फेंकता है। b.extend(a)सभी का मूल्यांकन करेगा, जिस स्थिति में पहली बार में इसे जनरेटर बनाने का कोई मतलब नहीं है।
स्लाटर विक्टोरॉफ

4
@SlaterTyranus आप 100% सही हैं, और मैंने आपको सटीकता के लिए उकेरा है। फिर भी, मुझे लगता है कि उनकी टिप्पणी ओपी के प्रश्न का एक उपयोगी गैर-उत्तर है क्योंकि यह उन लोगों की मदद करेगा जो खुद को यहां खोजते हैं क्योंकि उन्होंने एक खोज इंजन में 'कॉम्बिनेशन जनरेटर विद लिस्ट कॉम्प्रिहेंशन' जैसा कुछ टाइप किया था।
आरबीपी

1
एक बार के माध्यम से एक जनरेटर का उपयोग करने का कारण नहीं होगा (जैसे कि स्मृति की कमी के बारे में मेरी चिंता "एक समय में एक मान" प्राप्त करने के बारे में मेरी चिंता को दूर करती है ) शायद तब भी लागू होती है जब कई बार पुनरावृति होती है? मैं कहूंगा कि यह एक सूची को और अधिक उपयोगी बना सकता है, लेकिन क्या यह स्मृति की चिंताओं को दूर करने के लिए पर्याप्त है।
रोब ग्रांट

181

अधिक पुनरावृत्ति जनरेटर अभिव्यक्ति या सूची समझ ही बात करेंगे। हालाँकि, सूची की समझ पूरी सूची को पहले मेमोरी में बनाएगी जबकि जेनरेटर एक्सप्रेशन मक्खी पर आइटम बनाएगी, इसलिए आप इसका उपयोग बहुत बड़े (और अनंत!) अनुक्रमों के लिए भी कर सकते हैं।


39
अनंत के लिए +1। आप एक सूची के साथ ऐसा नहीं कर सकते, भले ही आप प्रदर्शन के बारे में कितना कम ध्यान दें।
पॉल ड्रेपर

क्या आप समझ बनाने की विधि का उपयोग करके अनंत जनरेटर बना सकते हैं?
अन्नपूर्णे

5
@ अर्नन केवल तभी जब आपके पास पहले से ही एक और अनंत जनरेटर का उपयोग हो। उदाहरण के लिए, itertools.count(n)एन से शुरू होने वाले पूर्णांकों का एक अनंत क्रम है, इसलिए शुरू होने (2 ** item for item in itertools.count(n))की शक्तियों का एक अनंत क्रम होगा । 22 ** n
केविन

2
एक जनरेटर अपने पुनरावृत्त होने के बाद मेमोरी से आइटम हटाता है। तो इसका उपवास यदि आपके पास बड़ा डेटा है तो आप इसे प्रदर्शित करना चाहते हैं, उदाहरण के लिए। यह एक स्मृति हॉग नहीं है। जेनरेटरों के साथ आइटम को 'आवश्यकतानुसार' संसाधित किया जाता है। यदि आप सूची को लटकाना चाहते हैं या उस पर पुन: चलना चाहते हैं (इसलिए आइटम संग्रहीत करें) तो सूची समझ का उपयोग करें।
j2emanue

102

जब परिणाम कई बार या जहां गति सर्वोपरि हो, तब सूची की समझ का उपयोग करें। जेनरेटर के भावों का उपयोग करें जहाँ रेंज बड़ी या अनंत है।

जेनरेटर के भाव देखें और अधिक जानकारी के लिए सूची समझें


2
यह संभवत: थोड़ा ऑफ-टॉपिक होगा, लेकिन दुर्भाग्य से "अन-गोग्लाबल" ... इस संदर्भ में "सर्वोपरि" का क्या अर्थ होगा? मैं एक देशी अंग्रेजी वक्ता नहीं हूं ... :)
गिलर्मो एरेस

6
@GillilloAres यह सर्वोपरि के अर्थ के लिए "googling" का प्रत्यक्ष परिणाम है: किसी भी चीज़ से अधिक महत्वपूर्ण; सर्वोच्च।
S --n atш Sаӽ

1
तो अभिव्यक्ति की listsतुलना में तेज हैं generator? डीएफ के उत्तर को पढ़ने से, यह पता चला कि यह चारों ओर का दूसरा रास्ता था।
हसन बेग

1
शायद यह कहना बेहतर होगा कि सीमा छोटा होने पर सूची की समझ तेज होती है, लेकिन जैसे-जैसे पैमाना बढ़ता है, यह मक्खी पर मूल्यों की गणना करने के लिए अधिक मूल्यवान हो जाता है - बस उनके उपयोग के लिए समय में। यही एक जनरेटर अभिव्यक्ति करता है।
काइल

59

महत्वपूर्ण बिंदु यह है कि सूची की समझ एक नई सूची बनाती है। जनरेटर एक चलने वाली वस्तु बनाता है जो बिट्स का उपभोग करने के साथ-साथ-साथ स्रोत सामग्री को "फ़िल्टर" करेगा।

कल्पना करें कि आपके पास एक 2TB लॉग फ़ाइल है, जिसे "vastfile.txt" कहा जाता है, और आप "ENTRY" शब्द से शुरू होने वाली सभी लाइनों के लिए सामग्री और लंबाई चाहते हैं।

तो आप एक सूची समझ लिखकर शुरू करने का प्रयास करें:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

यह पूरी फ़ाइल को स्लैप करता है, प्रत्येक पंक्ति को संसाधित करता है, और आपके सरणी में मिलान लाइनों को संग्रहीत करता है। इसलिए इस सरणी में 2TB तक की सामग्री हो सकती है। यह बहुत सारे RAM है, और शायद आपके उद्देश्यों के लिए व्यावहारिक नहीं है।

इसलिए इसके बजाय हम अपनी सामग्री के लिए "फ़िल्टर" लागू करने के लिए एक जनरेटर का उपयोग कर सकते हैं। कोई भी डेटा वास्तव में पढ़ा नहीं जाता है जब तक कि हम परिणाम पर चलना शुरू नहीं करते हैं।

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

हमारी फाइल से अभी तक एक भी लाइन नहीं पढ़ी गई है। वास्तव में, मान लें कि हम अपने परिणाम को आगे भी फ़िल्टर करना चाहते हैं:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

अभी भी कुछ नहीं पढ़ा गया है, लेकिन हमने अब दो जनरेटर निर्दिष्ट किए हैं जो हमारे डेटा पर काम करेंगे जैसे हम चाहते हैं।

हमारी फ़िल्टर की गई पंक्तियों को किसी अन्य फ़ाइल में लिखें:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

अब हम इनपुट फाइल को पढ़ते हैं। जैसा कि हमारा forलूप अतिरिक्त लाइनों का अनुरोध करना जारी रखता है, long_entriesजनरेटर जनरेटर से लाइनों की मांग करता entry_linesहै, केवल उन्हीं को लौटाता है जिनकी लंबाई 80 वर्णों से अधिक होती है। और बदले में, entry_linesजनरेटर पुनरावृत् तियों (संकेत के अनुसार फ़िल्टर्ड) से अनुरोध करता logfileहै, जो बदले में फ़ाइल को पढ़ता है।

इसलिए पूरी तरह से आबादी वाली सूची के रूप में अपने आउटपुट फ़ंक्शन के लिए डेटा को "पुश" करने के बजाय, आप आउटपुट फ़ंक्शन को केवल तब डेटा "पुल" करने का एक तरीका दे रहे हैं जब इसकी आवश्यकता होती है। यह हमारे मामले में बहुत अधिक कुशल है, लेकिन उतना लचीला नहीं है। जनरेटर एक रास्ता है, एक पास; लॉग फ़ाइल से डेटा जो हमने पढ़ा है वह तुरंत खारिज हो जाता है, इसलिए हम पिछली पंक्ति में वापस नहीं जा सकते। दूसरी ओर, जब भी हम इसके साथ काम करते हैं, तो हमें डेटा रखने की चिंता नहीं करनी चाहिए।


46

एक जनरेटर अभिव्यक्ति का लाभ यह है कि यह कम मेमोरी का उपयोग करता है क्योंकि यह एक बार में पूरी सूची का निर्माण नहीं करता है। सूची के मध्यस्थ होने पर जेनरेटर के भावों का सबसे अच्छा उपयोग किया जाता है, जैसे कि परिणामों को समेटना, या परिणामों का एक तानाशाही बनाना।

उदाहरण के लिए:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

वहाँ लाभ यह है कि सूची पूरी तरह से उत्पन्न नहीं हुई है, और इस तरह से छोटी मेमोरी का उपयोग किया जाता है (और यह तेज़ भी होना चाहिए)

हालाँकि, जब अंतिम उत्पाद एक सूची है, तो आपको सूची बोध का उपयोग करना चाहिए। आप जेनरेटर एक्सप्रेशंस का उपयोग करके किसी भी मेमरी को बचाने नहीं जा रहे हैं, क्योंकि आप जेनरेट की गई सूची चाहते हैं। आपको किसी भी तरह के सूची कार्यों का उपयोग करने में सक्षम होने का लाभ मिलता है, जैसे कि क्रमबद्ध या उलट।

उदाहरण के लिए:

reversed( [x*2 for x in xrange(256)] )

9
आपके लिए भाषा में एक संकेत दिया गया है कि जनरेटर के भाव का उपयोग उस तरह से किया जाना है। कोष्ठक खोना! sum(x*2 for x in xrange(256))
u0b34a0f6ae

8
sortedऔर reversedकिसी भी चलने योग्य, जनरेटर के भावों पर काम करना शामिल है।
marr75

1
यदि आप 2.7 और इसके बाद के संस्करण का उपयोग कर सकते हैं, तो उस तानाशाह () का उदाहरण एक तानाशाह की समझ के रूप में बेहतर लगेगा (इसके लिए पीईपी तब जनरेटर के भाव पीईपी से अधिक पुराना है, लेकिन जमीन पर अधिक समय तक लगा रहा है)
जुर्गन ए। एरहार्ड

14

उत्परिवर्तित वस्तु (जैसे एक सूची) से एक जनरेटर बनाते समय यह ध्यान रखें कि जनरेटर का उपयोग जनरेटर के निर्माण के समय नहीं, बल्कि सूची का उपयोग करने के समय सूची की स्थिति पर किया जाएगा:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

यदि आपकी सूची के संशोधित होने (या उस सूची के अंदर एक परिवर्तनशील वस्तु) के होने की कोई संभावना है, लेकिन आपको जनरेटर की स्थिति में राज्य की आवश्यकता है जिसके बजाय आपको सूची समझ का उपयोग करने की आवश्यकता है।


1
और यह स्वीकृत उत्तर होना चाहिए। यदि आपका डेटा उपलब्ध मेमोरी से बड़ा है, तो आपको हमेशा जनरेटर का उपयोग करना चाहिए, हालांकि मेमोरी में लिस्ट ओवर करना अधिक तेज़ हो सकता है (लेकिन आपके पास ऐसा करने के लिए पर्याप्त मेमोरी नहीं है)।
मर्कज़ मरकज़


4

मैं Hadoop Mincemeat मॉड्यूल का उपयोग कर रहा हूं । मुझे लगता है कि यह एक महान उदाहरण है:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

यहां जनरेटर एक टेक्स्ट फ़ाइल (15GB जितना बड़ा) से नंबर प्राप्त करता है और Hadoop के मानचित्र-कम का उपयोग करके उन संख्याओं पर सरल गणित लागू करता है। यदि मैंने पैदावार फ़ंक्शन का उपयोग नहीं किया था, लेकिन एक सूची की समझ के बजाय, यह रकम और औसत (अंतरिक्ष जटिलता का उल्लेख नहीं करने के लिए) की गणना में बहुत लंबा समय लेती थी।

जेनरेटरों के सभी फायदों का उपयोग करने के लिए हडोप एक बेहतरीन उदाहरण है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.