python - आप पाइथन जनरेटर फ़ंक्शंस का उपयोग कैसे कर सकते हैं?




generator (11)

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


असली दुनिया उदाहरण

मान लें कि आपके पास MySQL तालिका में 100 मिलियन डोमेन हैं और आप प्रत्येक डोमेन के लिए एलेक्स रैंक अपडेट करना चाहते हैं।

सबसे पहले आपको डेटाबेस से अपने डोमेन नाम चुनना है।

आइए कहें कि आपका टेबल नाम domains और कॉलम नाम domain

यदि आप SELECT domain FROM domains उपयोग करते हैं तो यह 100 मिलियन पंक्तियों को वापस करने जा रहा है जो बहुत मेमोरी का उपभोग करने जा रहा है। तो आपका सर्वर क्रैश हो सकता है

तो आपने कार्यक्रम को बैचों में चलाने का फैसला किया। मान लें कि हमारा बैच आकार 1000 है।

हमारे पहले बैच में हम पहले 1000 पंक्तियों से पूछेंगे, प्रत्येक डोमेन के लिए एलेक्स रैंक देखें और डेटाबेस पंक्ति अपडेट करें।

हमारे दूसरे बैच में हम अगले 1000 पंक्तियों पर काम करेंगे। हमारे तीसरे बैच में यह 2001 से 3000 तक और इसी तरह होगा।

अब हमें एक जेनरेटर फ़ंक्शन चाहिए जो हमारे बैचों को उत्पन्न करता है।

यहां हमारे जनरेटर फ़ंक्शन हैं

def ResultGenerator(cursor, batchsize=1000):
    while True:
        results = cursor.fetchmany(batchsize)
        if not results:
            break
        for result in results:
            yield result

जैसा कि आप देख सकते हैं कि हमारा कार्य परिणाम प्राप्त करता है। यदि आपने yield बजाय कीवर्ड return उपयोग किया है, तो यह पूरा होने पर पूरा फ़ंक्शन समाप्त हो जाएगा

return - returns only once
yield - returns multiple times

यदि कोई फ़ंक्शन कीवर्ड yield का उपयोग करता yield तो यह एक जनरेटर होता है।

अब आप इस तरह पुन: सक्रिय कर सकते हैं

db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
    doSomethingWith(result)
db.close()

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

class Rect():

    def __init__(self, x, y, width, height):
        self.l_top  = (x, y)
        self.r_top  = (x+width, y)
        self.r_bot  = (x+width, y+height)
        self.l_bot  = (x, y+height)

    def __iter__(self):
        yield self.l_top
        yield self.r_top
        yield self.r_bot
        yield self.l_bot

अब मैं अपने कोनों पर एक आयताकार और लूप बना सकता हूं:

myrect=Rect(50, 50, 100, 100)
for corner in myrect:
    print(corner)

__iter__ बजाय आपके पास iter_corners एक विधि हो सकती है और for corner in myrect.iter_corners() कॉल करें। यह __iter__ का उपयोग करने के लिए और अधिक सुरुचिपूर्ण है, तब से हम सीधे अभिव्यक्ति के for क्लास इंस्टेंस नाम का उपयोग कर सकते हैं।


जनरेटर का उपयोग करने के कारणों में से एक है समाधान को किसी प्रकार के समाधान के लिए स्पष्ट बनाना।

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

यदि आपके पास इस तरह का एक फाइबोनैकी-अप-टू-एन फ़ंक्शन है:

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in xrange(n):
        result.append(a)
        a, b = b, a + b
    return result

आप इस तरह के फ़ंक्शन को आसानी से लिख सकते हैं:

# generator version
def fibon(n):
    a = b = 1
    for i in xrange(n):
        yield a
        a, b = b, a + b

समारोह स्पष्ट है। और यदि आप इस तरह के फ़ंक्शन का उपयोग करते हैं:

for x in fibon(1000000):
    print x,

इस उदाहरण में, जनरेटर संस्करण का उपयोग करते हुए, पूरी 1000000 आइटम सूची बिल्कुल नहीं बनाई जाएगी, एक समय में केवल एक मान। सूची संस्करण का उपयोग करते समय यह मामला नहीं होगा, जहां एक सूची पहले बनाई जाएगी।


जेनरेटर आपको आलसी मूल्यांकन देते हैं। आप उन्हें उन पर फिर से उपयोग करके, या तो स्पष्ट रूप से 'के लिए' या अंतर्निहित रूप से किसी भी फ़ंक्शन या इसे पुन: निर्माण करने के द्वारा उपयोग करके उपयोग करते हैं। आप जेनरेटर के बारे में सोच सकते हैं कि कई आइटम लौट रहे हैं, जैसे कि वे एक सूची वापस करते हैं, लेकिन उन्हें एक बार में वापस लौटने की बजाय वे उन्हें एक-एक करके वापस कर देते हैं, और जेनरेटर फ़ंक्शन को तब तक रोका जाता है जब तक कि अगले आइटम का अनुरोध नहीं किया जाता है।

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

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

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

यदि आप बाद के दो दृष्टिकोणों का एक उदाहरण देखना चाहते हैं, तो os.path.walk () (कॉलबैक के साथ पुरानी फाइल सिस्टम-चलने वाली फ़ंक्शन) और os.walk () (नई फाइल सिस्टम-चलने वाले जनरेटर) देखें। बेशक, यदि आप वास्तव में एक सूची में सभी परिणामों को इकट्ठा करना चाहते थे, जनरेटर दृष्टिकोण बड़े सूची दृष्टिकोण में परिवर्तित करने के लिए तुच्छ है:

big_list = list(the_generator)

बफरिंग। जब यह बड़े हिस्सों में डेटा लाने के लिए सक्षम होता है, लेकिन इसे छोटे हिस्सों में संसाधित करता है, तो जनरेटर सहायता कर सकता है:

def bufferedFetch():
  while True:
     buffer = getBigChunkOfData()
     # insert some code to break on 'end of data'
     for i in buffer:    
          yield i

उपरोक्त आप आसानी से प्रसंस्करण से बफरिंग को अलग करने देता है। उपभोक्ता फ़ंक्शन अब बफरिंग के बारे में चिंता किए बिना मूल्यों को एक-एक करके प्राप्त कर सकता है।


मुझे यह स्पष्टीकरण मिलता है जो मेरा संदेह साफ करता है। क्योंकि ऐसी संभावना है कि Generators नहीं पता वह व्यक्ति yield बारे में भी नहीं जानता yield

वापसी

रिटर्न स्टेटमेंट वह जगह है जहां सभी स्थानीय चर नष्ट हो जाते हैं और परिणामस्वरूप मूल्य कॉलर को वापस (लौटाया जाता है) दिया जाता है। क्या एक ही समारोह को कुछ समय बाद बुलाया जाना चाहिए, फ़ंक्शन को चर के नए नए सेट मिलेंगे।

प्राप्ति

लेकिन क्या होगा यदि हम किसी फ़ंक्शन से बाहर निकलने पर स्थानीय चर को फेंक दिया नहीं जाता है? इसका तात्पर्य है कि हम resume the function को resume the function कर सकते हैं जहां हमने छोड़ा था। यह वह जगह है जहां generators की अवधारणा पेश की जाती है और yield बयान फिर से शुरू होता है जहां function छोड़ दिया जाता है।

  def generate_integers(N):
    for i in xrange(N):
    yield i 
  In [1]: gen = generate_integers(3)
    In [2]: gen
    <generator object at 0x8117f90>
    In [3]: gen.next()
    0
    In [4]: gen.next()
    1
    In [5]: gen.next()

तो पाइथन में वापसी और उपज बयान के बीच यह अंतर है।

यील्ड कथन वह है जो फ़ंक्शन को जनरेटर फ़ंक्शन बनाता है।

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


मेरे पसंदीदा उपयोग "फ़िल्टर" और "कम" ऑपरेशन हैं।

मान लीजिए कि हम एक फाइल पढ़ रहे हैं, और केवल उन पंक्तियों को चाहते हैं जो "##" से शुरू हों।

def filter2sharps( aSequence ):
    for l in aSequence:
        if l.startswith("##"):
            yield l

हम जनरेटर फ़ंक्शन को उचित लूप में उपयोग कर सकते हैं

source= file( ... )
for line in filter2sharps( source.readlines() ):
    print line
source.close()

उदाहरण कम है समान है। मान लें कि हमारे पास एक फ़ाइल है जहां हमें <Location>...</Location> लाइनों के ब्लॉक का पता लगाने की आवश्यकता है। [एचटीएमएल टैग नहीं, लेकिन लाइन-जैसी दिखने वाली रेखाएं।]

def reduceLocation( aSequence ):
    keep= False
    block= None
    for line in aSequence:
        if line.startswith("</Location"):
            block.append( line )
            yield block
            block= None
            keep= False
        elif line.startsWith("<Location"):
            block= [ line ]
            keep= True
        elif keep:
            block.append( line )
        else:
            pass
    if block is not None:
        yield block # A partial block, icky

दोबारा, हम इस जेनरेटर को लूप के लिए उचित रूप से उपयोग कर सकते हैं।

source = file( ... )
for b in reduceLocation( source.readlines() ):
    print b
source.close()

विचार यह है कि एक जनरेटर फ़ंक्शन हमें अनुक्रम को फ़िल्टर या कम करने की अनुमति देता है, एक समय में एक और अनुक्रम एक मूल्य का उत्पादन करता है।


मैं जेनरेटर का उपयोग करता हूं जब हमारा वेब सर्वर प्रॉक्सी के रूप में कार्य कर रहा है:

  1. ग्राहक सर्वर से एक प्रॉक्सी यूआरएल अनुरोध करता है
  2. सर्वर लक्ष्य यूआरएल लोड करना शुरू करता है
  3. सर्वर परिणाम को क्लाइंट को जितनी जल्दी हो सके परिणाम लौटने के लिए उपज देता है

सरल स्पष्टीकरण: बयान के for विचार करें

for item in iterable:
   do_stuff()

बहुत समय, iterable में सभी वस्तुओं को शुरुआत से वहां होने की आवश्यकता नहीं है, लेकिन उन्हें आवश्यकतानुसार फ्लाई पर उत्पन्न किया जा सकता है। यह दोनों में बहुत अधिक कुशल हो सकता है

  • अंतरिक्ष (आपको कभी भी सभी वस्तुओं को एक साथ स्टोर करने की आवश्यकता नहीं है) और
  • समय (सभी वस्तुओं की आवश्यकता होने से पहले पुनरावृत्ति खत्म हो सकती है)।

अन्य बार, आप समय से पहले सभी वस्तुओं को भी नहीं जानते हैं। उदाहरण के लिए:

for command in user_input():
   do_stuff_with(command)

आपके पास पहले से सभी उपयोगकर्ता के आदेशों को जानने का कोई तरीका नहीं है, लेकिन यदि आप जनरेटर को आदेश देते हैं तो आप इस तरह के एक अच्छा लूप का उपयोग कर सकते हैं:

def user_input():
    while True:
        wait_for_command()
        cmd = get_command()
        yield cmd

जेनरेटर के साथ आप असीमित अनुक्रमों पर पुनरावृत्ति भी कर सकते हैं, जो कंटेनर पर फिर से शुरू होने पर संभव नहीं है।


सामान की ढेर जब भी आप वस्तुओं का अनुक्रम उत्पन्न करना चाहते हैं, लेकिन उन्हें एक ही सूची में सभी को 'भौतिक' बनाना नहीं चाहते हैं। उदाहरण के लिए, आपके पास एक साधारण जनरेटर हो सकता है जो प्राइम नंबर देता है:

def primes():
    primes_found = set()
    primes_found.add(2)
    yield 2
    for i in itertools.count(1):
        candidate = i * 2 + 1
        if not all(candidate % prime for prime in primes_found):
            primes_found.add(candidate)
            yield candidate

इसके बाद आप बाद के प्राइम्स के उत्पादों को उत्पन्न करने के लिए इसका उपयोग कर सकते हैं:

def prime_products():
    primeiter = primes()
    prev = primeiter.next()
    for prime in primeiter:
        yield prime * prev
        prev = prime

ये काफी मामूली उदाहरण हैं, लेकिन आप देख सकते हैं कि बड़े पैमाने पर (संभावित रूप से अनंत!) डेटासेट को अग्रिम में उत्पन्न किए बिना यह कैसे उपयोगी हो सकता है, जो केवल अधिक स्पष्ट उपयोगों में से एक है।


पीईपी 255 में "प्रेरणा" अनुभाग देखें।

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





generator