अजगर जनरेटर का उपयोग करने के लिए एक अच्छा समय नहीं है?


83

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

तो जब यह एक जनरेटर, या एक जनरेटर अभिव्यक्ति, या एक समारोह का उपयोग करने के लिए एक अच्छा समय नहीं है itertools?

  • जब मैं पसंद किए जाने वाले zip()से अधिक itertools.izip(), या
  • range()पर xrange(), या
  • [x for x in foo]खत्म (x for x in foo)हो गया ?

जाहिर है, हमें अंततः एक जनरेटर को वास्तविक डेटा में "हल" करने की आवश्यकता है, आमतौर पर एक सूची बनाकर या गैर-जनरेटर लूप के साथ उस पर पुनरावृति। कभी-कभी हमें सिर्फ लंबाई जानने की जरूरत होती है। यह वह नहीं है जो मैं पूछ रहा हूं।

हम जनरेटर का उपयोग करते हैं ताकि हम अंतरिम डेटा के लिए नई सूचियों को मेमोरी में असाइन न करें। यह विशेष रूप से बड़े डेटासेट के लिए समझ में आता है। यह छोटे डेटासेट के लिए भी समझ में आता है? क्या ध्यान देने योग्य मेमोरी / सीपीयू ट्रेड-ऑफ है?

मुझे विशेष रूप से दिलचस्पी है अगर किसी ने सूची समझ प्रदर्शन बनाम मानचित्र () और फ़िल्टर () की आंख खोलने की चर्चा के प्रकाश में इस पर कुछ रूपरेखा तैयार की है । (पूरी लिंक )


2
मैंने यहां एक समान प्रश्न पेश किया और कुछ विशेष विश्लेषण किया कि मेरी विशेष उदाहरण सूचियों में लंबाई के पुनरावृत्तियों के लिए तेजी से है<5
अलेक्जेंडर मैकफर्लेन

क्या इससे आपके सवाल का जवाब मिलता है? जेनरेटर एक्सप्रेशंस बनाम लिस्ट कॉम्प्रिहेंशन
ggorlen

जवाबों:


57

जब एक जनरेटर के बजाय एक सूची का उपयोग करें:

1) आपको कई बार डेटा का उपयोग करने की आवश्यकता होती है (अर्थात उन्हें पुनः प्राप्त करने के बजाय परिणामों को कैश करें):

for i in outer:           # used once, okay to be a generator or return a list
    for j in inner:       # used multiple times, reusing a list is better
         ...

2) आपको रैंडम एक्सेस (या आगे के क्रम के अलावा किसी भी एक्सेस) की आवश्यकता है:

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) आपको स्ट्रिंग्स में शामिल होने की आवश्यकता है (जिसमें डेटा पर दो पास की आवश्यकता होती है):

s = ''.join(data)                # lists are faster than generators in this use case

4) आप PyPy का उपयोग कर रहे हैं जो कभी-कभी जेनरेटर कोड को ऑप्टिमाइज़ नहीं कर सकता है क्योंकि यह सामान्य फ़ंक्शन कॉल और सूची जोड़तोड़ के साथ हो सकता है।


# 3 के लिए, जुड़ाव ireduceको दोहराने के लिए दो पास से बचा नहीं जा सकता था ?
प्लैटिनम

धन्यवाद! मैं स्ट्रिंग में शामिल होने के व्यवहार से अवगत नहीं था। क्या आप इसे दो पास की आवश्यकता के स्पष्टीकरण के लिए प्रदान या लिंक कर सकते हैं?
डेविड आईक

5
@DavidEyk str.join सभी स्ट्रिंग अंशों की लंबाई जोड़ने के लिए एक पास बनाता है, इसलिए यह संयुक्त अंतिम परिणाम के लिए आवंटित करने के लिए बहुत स्मृति जानता है। दूसरा पास एक नया स्ट्रिंग बनाने के लिए नए बफ़र में स्ट्रिंग अंशों की प्रतिलिपि बनाता है। देखें hg.python.org/cpython/file/82fd95c2851b/Objects/stringlib/...
रेमंड Hettinger

1
दिलचस्प है, मैं स्प्रिंग्स में शामिल होने के लिए बहुत बार जनरेटर का उपयोग करता हूं। लेकिन, मुझे आश्चर्य है कि अगर इसे दो पास की जरूरत है तो यह कैसे काम करेगा? उदाहरण के लिए''.join('%s' % i for i in xrange(10))
bgusach

4
@ ikaros45 यदि इनपुट में शामिल होने की सूची नहीं है, तो दो पासों के लिए एक अस्थायी सूची बनाने के लिए अतिरिक्त काम करना होगा। मोटे तौर पर यह `` डेटा = डेटा अगर आइंस्टीन (डेटा, सूची) और सूची (डेटा) है; n = योग (नक्शा (लेन, डेटा)); बफ़र = bytearray (n); ... <बफर में कॉपी टुकड़े> `` `।
रेमंड हेटिंगर

40

सामान्य तौर पर, जब आपको सूची संचालन की आवश्यकता होती है, तो जनरेटर का उपयोग न करें, जैसे कि लेन (), उल्टा (), और इसी तरह।

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


25
इसके अलावा, सभी गणना को आगे करने से यह सुनिश्चित होता है कि यदि सूची तत्वों की गणना एक अपवाद फेंकता है, तो इसे उस बिंदु पर फेंक दिया जाएगा जहां सूची बनाई गई है , न कि उस लूप में जो बाद में इसके माध्यम से पुनरावृत्त होता है। यदि आपको जारी रखने से पहले पूरी सूची के त्रुटि-मुक्त प्रसंस्करण को सुनिश्चित करने की आवश्यकता है, तो जनरेटर अच्छे नहीं हैं।
रयान सी। थॉम्पसन

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

26

प्रोफाइल, प्रोफाइल, प्रोफाइल।

अपने कोड को प्रोफाइल करना यह जानने का एकमात्र तरीका है कि आप जो कर रहे हैं उसका कोई प्रभाव नहीं है।

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

प्रोफाइल, प्रोफाइल, प्रोफाइल।


1
प्रोफाइल, वास्तव में। इन दिनों में से एक, मैं कोशिश करूँगा और एक अनुभवजन्य तुलना करूँगा। तब तक, मैं बस किसी और से पहले से ही उम्मीद कर रहा था। :)
डेविड आइक

प्रोफाइल, प्रोफाइल, प्रोफाइल। मैं पूरी तरह से सहमत। प्रोफाइल, प्रोफाइल, प्रोफाइल।
Jeppe

17

आपको जेनरेटर कॉम्प्रिहेंशन पर कभी zipओवर izip, rangeओवर xrangeया लिस्ट कॉम्प्रिहेंशन का पक्ष नहीं लेना चाहिए । अजगर 3.0 में rangeहै xrangeकी तरह अर्थ विज्ञान और zipहै izipकी तरह अर्थ विज्ञान।

सूची की समझ वास्तव में list(frob(x) for x in foo)उन लोगों के लिए जैसे आप एक वास्तविक सूची की जरूरत के लिए स्पष्ट हैं ।


3
@ सही मैं असहमत नहीं हूं, लेकिन मैं सोच रहा हूं कि आपके जवाब के पीछे क्या तर्क है। ज़िप, रेंज, और लिस्ट कॉम्प्रिहेंशन को कभी भी "आलसी" संस्करण के अनुकूल क्यों नहीं होना चाहिए ??
मावके

क्योंकि, जैसा कि उन्होंने कहा, जिप और रेंज का पुराना व्यवहार जल्द ही दूर हो जाएगा।

@ सीन: अच्छा अंक। मैं 3.0 में इन परिवर्तनों के बारे में भूल गया था, जिसका अर्थ है कि कोई व्यक्ति अपनी सामान्य श्रेष्ठता के बारे में आश्वस्त है। पुन: सूची समझ, वे अक्सर स्पष्ट होते हैं (और विस्तारित forछोरों की तुलना में तेज़ !), लेकिन कोई आसानी से समझ से बाहर की सूची लिख सकता है।
डेविड आईक

9
मैं देख रहा हूं कि आपका क्या मतलब है, लेकिन मुझे []फॉर्म वर्णनात्मक पर्याप्त (और अधिक संक्षिप्त, और कम बरबाद, आम तौर पर) लगता है। लेकिन यह सिर्फ स्वाद की बात है।
डेविड आईक

4
सूची संचालन छोटे डेटा आकारों के लिए तेज़ हैं, लेकिन डेटा आकार छोटा होने पर सब कुछ तेज़ है, इसलिए आपको हमेशा जनरेटर पसंद करना चाहिए जब तक कि आपके पास सूचियों का उपयोग करने का कोई विशिष्ट कारण न हो (ऐसे कारणों के लिए, रायन जिंस्ट्रोम का उत्तर देखें)।
रयान सी। थॉम्पसन

7

जैसा कि आप उल्लेख करते हैं, "यह विशेष रूप से बड़े डेटासेट के लिए समझ में आता है", मुझे लगता है कि यह आपके प्रश्न का उत्तर देता है।

यदि आपकी कोई दीवार, प्रदर्शन के हिसाब से नहीं है, तो भी आप सूची और मानक कार्यों से चिपके रह सकते हैं। फिर जब आप प्रदर्शन के साथ समस्याओं में भाग लेते हैं तो स्विच बनाते हैं।

जैसा कि टिप्पणियों में @ u0b34a0f6ae द्वारा उल्लेख किया गया है, हालांकि, शुरुआत में जनरेटर का उपयोग करना आपके लिए बड़े डेटासेट को स्केल करना आसान बना सकता है।


5
+1 जेनरेटर आपके कोड को बड़े डेटासेट के लिए तैयार करता है, इसके बिना आपको इसका अनुमान लगाना होगा।
u0b34a0f6ae

6

प्रदर्शन के बारे में: यदि मानस का उपयोग करते हैं, तो सूची जनरेटर की तुलना में काफी तेज हो सकती है। नीचे दिए गए उदाहरण में, psyco.full () का उपयोग करते समय सूचियां लगभग 50% तेज हैं

import psyco
import time
import cStringIO

def time_func(func):
    """The amount of time it requires func to run"""
    start = time.clock()
    func()
    return time.clock() - start

def fizzbuzz(num):
    """That algorithm we all know and love"""
    if not num % 3 and not num % 5:
        return "%d fizz buzz" % num
    elif not num % 3:
        return "%d fizz" % num
    elif not num % 5:
        return "%d buzz" % num
    return None

def with_list(num):
    """Try getting fizzbuzz with a list comprehension and range"""
    out = cStringIO.StringIO()
    for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
        print >> out, fibby
    return out.getvalue()

def with_genx(num):
    """Try getting fizzbuzz with generator expression and xrange"""
    out = cStringIO.StringIO()
    for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
        print >> out, fibby
    return out.getvalue()

def main():
    """
    Test speed of generator expressions versus list comprehensions,
    with and without psyco.
    """

    #our variables
    nums = [10000, 100000]
    funcs = [with_list, with_genx]

    #  try without psyco 1st
    print "without psyco"
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

    #  now with psyco
    print "with psyco"
    psyco.full()
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

if __name__ == "__main__":
    main()

परिणाम:

without psyco
  number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds

  number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds

with psyco
  number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds

  number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds

1
ऐसा इसलिए है क्योंकि साइको जनरेटर को गति नहीं देता है, इसलिए यह जेनरेटर की तुलना में साइको की कमी है। अच्छा जवाब, हालांकि।
स्टीवन हुआविग

4
इसके अलावा, साइको अब बहुत ज्यादा अचंभित है। सभी डेवलपर्स PyPy के JIT पर समय बिता रहे हैं जो मेरे ज्ञान का सबसे अच्छा काम करता है जनरेटर को अनुकूलित करता है।
नौफाल इब्राहिम

3

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


all(True for _ in range(10 ** 8))all([True for _ in range(10 ** 8)])पायथन 3.8 की तुलना में धीमी है । मैं यहाँ एक जनरेटर की सूची पसंद करूँगा
gorglen

3

मैंने कभी भी ऐसी स्थिति नहीं पाई है जहां जनरेटर आप क्या करने की कोशिश कर रहे हैं, इसमें बाधा होगी। हालाँकि, ऐसे बहुत सारे उदाहरण हैं जहाँ जनरेटर का उपयोग करने से आपको उनका उपयोग न करने की तुलना में अधिक मदद मिलेगी।

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

sorted(xrange(5))

किसी भी सुधार की पेशकश नहीं करता है:

sorted(range(5))

4
न तो उन में से कोई भी सुधार प्रदान करता है range(5), क्योंकि परिणामी सूची पहले से ही क्रमबद्ध है।
dan04

3

यदि आपको बाद में किसी और चीज़ के लिए मान रखने की आवश्यकता है, तो आपको सूची समझ को प्राथमिकता देना चाहिए और आपके सेट का आकार बहुत बड़ा नहीं है।

उदाहरण के लिए: आप एक सूची बना रहे हैं जिसे आप अपने कार्यक्रम में बाद में कई बार लूप करेंगे।

कुछ हद तक आप जनरेटरों को पुनरावृत्ति (लूप्स) के लिए प्रतिस्थापन के रूप में सोच सकते हैं। एक प्रकार की डेटा संरचना आरंभीकरण के रूप में सूची की समझ। यदि आप डेटा संरचना रखना चाहते हैं तो सूची समझ का उपयोग करें।


यदि आपको स्ट्रीम पर केवल सीमित लुक-फॉरवर्ड / लुक-बैक की आवश्यकता है, तो शायद itertools.tee()आपकी मदद कर सकता है। लेकिन आम तौर पर, यदि आप एक से अधिक पास चाहते हैं, या कुछ मध्यवर्ती डेटा तक यादृच्छिक पहुंच बनाते हैं, तो इसकी एक सूची / सेट / तानाशाही बनाएं।
बेनी चेर्नियाव्स्की-पास्किन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.