अजगर जनरेटर "भेजें" समारोह उद्देश्य?


164

क्या कोई मुझे इसका उदाहरण दे सकता है कि पायथन जनरेटर फ़ंक्शन से जुड़े "भेजें" फ़ंक्शन क्यों मौजूद हैं? मैं उपज फ़ंक्शन को पूरी तरह से समझता हूं। हालाँकि, भेजें फ़ंक्शन मुझे भ्रमित कर रहा है। इस पद्धति का प्रलेखन जटिल है:

generator.send(value)

निष्पादन को फिर से शुरू करता है और जनरेटर फ़ंक्शन में "मूल्य" भेजता है। मूल्य तर्क वर्तमान उपज अभिव्यक्ति का परिणाम बन जाता है। भेजें () विधि जनरेटर द्वारा उत्पादित अगले मूल्य को वापस कर देती है, या यदि जनरेटर दूसरे मूल्य के बिना जनरेटर से बाहर निकलता है तो StopIteration उठाता है।

इसका क्या मतलब है? मुझे लगा कि फंक्शन का मूल्य क्या है? वाक्यांश "प्रेषक () विधि जनरेटर द्वारा प्राप्त अगले मूल्य को लौटाता है" उपज फ़ंक्शन का सटीक उद्देश्य भी लगता है; उपज जनरेटर द्वारा प्राप्त अगले मूल्य देता है ...

क्या कोई मुझे एक जनरेटर का उपयोग करने का उदाहरण दे सकता है जो कुछ उपज प्राप्त नहीं कर सकता है?



3
एक और वास्तविक जीवन उदाहरण (FTP से पढ़ना) जोड़ा गया जब कॉलबैक को जनरेटर से अंदर से चालू किया जाता है
Jan Vlcinsky

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

जवाबों:


147

इसका उपयोग एक जनरेटर में मान भेजने के लिए किया जाता है जो सिर्फ उपज देता है। यहाँ एक कृत्रिम (गैर-उपयोगी) व्याख्यात्मक उदाहरण दिया गया है:

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

आप ऐसा नहीं कर सकते yield

क्यों यह उपयोगी है, मैंने देखा है सबसे अच्छे उपयोग मामलों में से एक ट्विस्टेड है @defer.inlineCallbacks। अनिवार्य रूप से यह आपको एक फ़ंक्शन लिखने की अनुमति देता है:

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

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

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

यह बहुत अधिक जटिल और अविवेकी है।


2
क्या आप बता सकते हैं कि इसका उद्देश्य क्या है? यह दोहरे_पुष्पों (शुरुआती) और उपज के साथ फिर से क्यों नहीं बनाया जा सकता है?
टॉमी

@ टॉमी: ओह, क्योंकि आपको जो मान मिला है उसका पिछले एक से कोई लेना-देना नहीं है। मुझे उदाहरण बदल
Claudiu

फिर आप एक साधारण फ़ंक्शन पर इसका उपयोग क्यों करेंगे जो इसके इनपुट को दोगुना करता है ??
टॉमी

4
@ टॉमी: आप नहीं करेंगे। पहला उदाहरण यह समझाने के लिए है कि यह क्या करता है। दूसरा उदाहरण वास्तव में उपयोगी उपयोग के मामले के लिए है।
Claudiu

1
@ टॉमी: मैं कहूंगा कि अगर आप वास्तव में इस प्रस्तुति को देखना चाहते हैं और इसके माध्यम से काम करना चाहते हैं । एक छोटा सा उत्तर पर्याप्त नहीं होगा क्योंकि तब आप कहेंगे "लेकिन क्या मैं ऐसा नहीं कर सकता?" आदि
क्लाउडीउ

96

यह फ़ंक्शन कोरटाइन लिखना है

def coroutine():
    for i in range(1, 10):
        print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print("From user {}".format(c.send(1)))
except StopIteration: pass

प्रिंट

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

देखें कि नियंत्रण कैसे आगे और पीछे पारित किया जा रहा है? वे कोरटाइन हैं। वे asynch IO और इसी तरह की सभी प्रकार की ठंडी चीजों के लिए उपयोग किया जा सकता है।

इसे इस तरह से समझें, जैसे कोई जनरेटर और कोई भेज नहीं, यह एक तरह से सड़क है

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

लेकिन भेजने के साथ, यह दो तरह से सड़क बन जाता है

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

जो उपयोगकर्ता जनरेटर व्यवहार को अनुकूलित करने के लिए दरवाजा खुल जाता है मक्खी पर और जनरेटर उपयोगकर्ता का जवाब।


3
लेकिन एक जनरेटर फ़ंक्शन पैरामीटर ले सकता है। जनरेटर को पैरामीटर भेजने से परे "भेजें" कैसे जाता है?
टॉमी

13
@ टॉमी क्योंकि आप पैरामीटर को जनरेटर में नहीं बदल सकते क्योंकि यह चलता है। आप इसे पैरामीटर देते हैं, यह चलता है, किया जाता है। भेजने के साथ, आप इसे पैरामीटर देते हैं, यह एक बिट के लिए चलता है, आप इसे एक मूल्य भेजते हैं और यह कुछ अलग करता है, दोहराता है
डैनियल ग्रैज़र


5
क्या आप हर चीज से पहले कोई नहीं भेजने का उद्देश्य स्पष्ट कर सकते हैं?
शुभम अग्रवाल

2
@ शुभमअगरवाल यह जनरेटर शुरू करने के लिए किया जाता है। यह सिर्फ कुछ ऐसा है जिसे करने की जरूरत है। यह कुछ समझ में आता है जब आप इसके बारे में सोचते हैं क्योंकि पहली बार जब आप send()जनरेटर को कॉल करते हैं तो वह कीवर्ड yieldतक नहीं पहुंचता है।
माइकल

50

इससे किसी की मदद हो सकती है। यहाँ एक जनरेटर है जो भेजने के कार्य से अप्रभावित है। यह तात्कालिकता पर संख्या पैरामीटर में है और भेजने से अप्रभावित है:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

अब यहां बताया गया है कि आप एक ही प्रकार के फ़ंक्शन का उपयोग किस प्रकार करेंगे, इसलिए प्रत्येक पुनरावृत्ति पर आप संख्या का मान बदल सकते हैं:

def double_number(number):
    while True:
        number *= 2
        number = yield number

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

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

आप इसे इस तरह से लूप में रख सकते हैं:

for x in range(10):
    n = c.send(n)
    print n

अधिक मदद के लिए इस महान ट्यूटोरियल की जाँच करें ।


12
एक फ़ंक्शन के बीच यह तुलना एक (जो वास्तव में मदद करता है के साथ भेजने से प्रभावित नहीं करता है)। धन्यवाद!
मानस बजाज

यह किस उद्देश्य के लिए एक उदाहरण हो सकता है send? एक साधारण एक lambda x: x * 2ही चीज़ को बहुत कम जटिल तरीके से करता है।
user209974

क्या यह भेजने का उपयोग करता है? जाओ और अपना उत्तर जोड़ो।
राडटेक

17

कुछ जनरेटर और उपयोग करने के लिए मामलों का उपयोग करते हैं send()

send()अनुमति के साथ जनरेटर :

  • निष्पादन की आंतरिक स्थिति को याद रखना
    • हम किस कदम पर हैं
    • हमारे डेटा की वर्तमान स्थिति क्या है
  • मूल्यों का अनुक्रम लौटाना
  • इनपुट का अनुक्रम प्राप्त करना

यहाँ कुछ उपयोग मामले हैं:

एक नुस्खा का पालन करने का प्रयास देखा

हमारे पास एक नुस्खा है, जो कुछ क्रम में इनपुट के पूर्वनिर्धारित सेट की अपेक्षा करता है।

हम कर सकते हैं:

  • watched_attemptनुस्खा से एक उदाहरण बनाएँ
  • कुछ इनपुट मिलते हैं
  • पॉट में वर्तमान में क्या है, इसके बारे में प्रत्येक इनपुट रिटर्न जानकारी के साथ
  • प्रत्येक इनपुट जांच के साथ, कि इनपुट अपेक्षित है (और यदि यह नहीं है तो विफल)

    def recipe():
        pot = []
        action = yield pot
        assert action == ("add", "water")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("add", "salt")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("boil", "water")
    
        action = yield pot
        assert action == ("add", "pasta")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("decant", "water")
        pot.remove("water")
    
        action = yield pot
        assert action == ("serve")
        pot = []
        yield pot

इसका उपयोग करने के लिए, पहले watched_attemptउदाहरण बनाएं :

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

.next()जनरेटर का निष्पादन शुरू करने के लिए कॉल आवश्यक है।

लौटाया गया मान दिखाता है, वर्तमान में हमारा बर्तन खाली है।

अब कुछ कार्यों के बाद नुस्खा क्या होता है:

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

जैसा कि हम देखते हैं, बर्तन आखिरकार खाली है।

मामले में, कोई भी नुस्खा का पालन नहीं करेगा, यह विफल हो जाएगा (कुछ पकाने के लिए देखे गए प्रयास का वांछित परिणाम क्या हो सकता है - बस सीखने के निर्देश दिए जाने पर हमने पर्याप्त ध्यान नहीं दिया।

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

नोटिस जो:

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

कुल योग चल रहा है

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

किसी भी समय हम एक संख्या जोड़ते हैं, इनपुट की गिनती और कुल योग लौटाया जाता है (उस समय पिछले इनपुट के लिए मान्य था)।

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

आउटपुट जैसा दिखेगा:

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)

3
मैं आपके उदाहरण को चलाता हूं और अजगर 3 में ऐसा लगता है कि watched_attempt.next () को अगले (watched_attempt) द्वारा प्रतिस्थापित किया जाना है।
thanos.a 15

15

send()विधि नियंत्रण क्या उपज अभिव्यक्ति के बाईं ओर रकम होनी चाहिए।

यह समझने के लिए कि उपज कैसे भिन्न होती है और इसका क्या मूल्य होता है, सबसे पहले ऑर्डर पाइथन कोड का मूल्यांकन जल्दी से करने देता है।

धारा 6.15 मूल्यांकन आदेश

पायथन बाएं से दाएं की ओर भावों का मूल्यांकन करता है। ध्यान दें कि असाइनमेंट का मूल्यांकन करते समय, दाएं हाथ का मूल्यांकन बाएं हाथ की तरफ से पहले किया जाता है।

तो एक अभिव्यक्ति a = bदाहिने हाथ की ओर पहले मूल्यांकन किया जाता है।

जैसा कि निम्नलिखित दर्शाता है कि a[p('left')] = p('right')पहले दाहिने हाथ की ओर का मूल्यांकन किया जाता है।

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

उपज क्या करता है ?, उपज, फ़ंक्शन के निष्पादन को निलंबित कर देता है और कॉलर को लौटता है, और उसी स्थान पर निष्पादन को फिर से शुरू करता है, जिसे निलंबित करने से पहले छोड़ दिया गया था।

जहां वास्तव में निष्पादन निलंबित है? आपने पहले ही अनुमान लगा लिया होगा ... उपज अभिव्यक्ति के दाईं और बाईं ओर के बीच निष्पादन निलंबित है। इसलिए new_val = yield old_valनिष्पादन को =संकेत पर रोक दिया जाता है , और दाईं ओर मूल्य (जो निलंबित होने से पहले है, और कॉलर को वापस लौटाया गया मूल्य भी है) कुछ भिन्न हो सकता है तो बाईं ओर का मूल्य (जिसे फिर से शुरू करने के बाद निर्दिष्ट किया जा रहा मान है) निष्पादन)।

yield 2 मान देता है, एक दाईं ओर और दूसरा बाईं ओर।

आप उपज अभिव्यक्ति के बाएं हाथ के मूल्य को कैसे नियंत्रित करते हैं? .send()विधि के माध्यम से ।

6.2.9। उपज के भाव

फिर से शुरू करने के बाद उपज अभिव्यक्ति का मूल्य उस विधि पर निर्भर करता है जिसने निष्पादन को फिर से शुरू किया। यदि __next__()उपयोग किया जाता है (आमतौर पर या तो एक या next()बिलिन के माध्यम से ) तो परिणाम कोई नहीं है। अन्यथा, यदि send()उपयोग किया जाता है, तो परिणाम उस पद्धति में पारित मूल्य होगा।


13

sendविधि लागू coroutines

यदि आपने कोराटाइन्स का सामना नहीं किया है, तो वे आपके सिर को चारों ओर लपेटने के लिए मुश्किल हैं क्योंकि वे प्रोग्राम प्रवाह के तरीके को बदलते हैं। आप अधिक विवरण के लिए एक अच्छा ट्यूटोरियल पढ़ सकते हैं ।


6

"उपज" शब्द के दो अर्थ हैं: कुछ उत्पन्न करना (जैसे, मकई उपजाना), और किसी को / दूसरी चीज़ को जारी रखने के लिए रोकना (जैसे, कार पैदल चलने वालों को)। दोनों परिभाषाएं पायथन के yieldकीवर्ड पर लागू होती हैं ; जेनरेटर फ़ंक्शंस को क्या खास बनाता है कि नियमित फ़ंक्शंस के विपरीत, मूल्यों को कॉल करने वाले को "वापस" किया जा सकता है, जबकि केवल रुकने का नहीं, एक जनरेटर फ़ंक्शन।

"बाएं" छोर और "दाएं" छोर के साथ एक द्विदिश पाइप के एक छोर के रूप में एक जनरेटर की कल्पना करना सबसे आसान है; यह पाइप वह माध्यम है जिस पर जनरेटर और जनरेटर फ़ंक्शन के बीच मूल्यों को भेजा जाता है। पाइप के प्रत्येक छोर में दो ऑपरेशन होते हैं: pushजो एक मूल्य भेजता है और तब तक ब्लॉक करता है जब तक पाइप के दूसरे छोर से मूल्य खींच नहीं लेता, और कुछ भी नहीं लौटाता; तथाpull, जो पाइप के दूसरे छोर तक एक मूल्य को धक्का देता है, और धक्का दिए गए मान को लौटाता है। रनटाइम के दौरान, निष्पादन पाइप के दोनों ओर संदर्भों के बीच आगे और पीछे उछलता है - प्रत्येक पक्ष तब तक चलता है जब तक कि यह दूसरी तरफ एक मूल्य नहीं भेजता है, जिस बिंदु पर यह रुकता है, दूसरे पक्ष को चलाने देता है, और मूल्य का इंतजार करता है वापसी, जिस तरफ दूसरी तरफ रुकती है और फिर से शुरू होती है। दूसरे शब्दों में, पाइप का प्रत्येक छोर उस क्षण से चलता है जब वह उस मूल्य को प्राप्त करता है जिस क्षण वह मूल्य भेजता है।

पाइप कार्यात्मक सममित है, लेकिन - सम्मेलन मैं इस सवाल का जवाब में परिभाषित करने कर रहा हूँ से - बाईं अंत जनरेटर समारोह के शरीर के अंदर ही उपलब्ध है और के माध्यम से पहुँचा जा सकता है yield, जबकि सही अंत, कीवर्ड है जनरेटर और के माध्यम से पहुँचा जा सकता है जनरेटर का sendकार्य। के रूप में एकवचन पाइप के अपने संबंधित छोरों के लिए इंटरफेस, yieldऔर sendडबल ड्यूटी करते हैं: वे प्रत्येक दोनों को धक्का देते हैं और पाइप के अपने सिरों से / के लिए मान को yieldधक्का देते हैं , दाएं को धक्का देते हैं और sendइसके विपरीत बाईं ओर खींचते हैं । यह दोहरा कर्तव्य बयानों के शब्दार्थ को लेकर भ्रम की स्थिति है x = yield y। दो स्पष्ट पुश / पुल चरणों में टूटने yieldऔर sendनीचे आने से उनके शब्दार्थ अधिक स्पष्ट होंगे:

  1. मान लीजिए gजनरेटर है। g.sendपाइप के दाईं ओर के माध्यम से बाईं ओर एक मूल्य को धक्का देता है।
  2. gठहराव के संदर्भ में निष्पादन , जनरेटर फ़ंक्शन को चलाने की अनुमति देता है।
  3. द्वारा धक्का दिया मूल्य पाइप के बाईं g.sendओर से yieldप्राप्त किया और छोड़ दिया है। में x = yield y, xखींचे गए मान को सौंपा गया है।
  4. जनरेटर फ़ंक्शन के शरीर के भीतर निष्पादन तब तक जारी रहता है जब तक कि अगली पंक्ति युक्त नहीं हो yieldजाता है।
  5. yieldपाइप के बाएं छोर के माध्यम से एक मूल्य को दाईं ओर धकेलता है, बैक अप लेता है g.send। में x = yield y, yपाइप के माध्यम से दाईं ओर धकेल दिया जाता है।
  6. जनरेटर फ़ंक्शन के शरीर के भीतर निष्पादन रुक जाता है, जिससे बाहरी गुंजाइश बनी रहती है, जहां इसे छोड़ दिया जाता है।
  7. g.send फिर से शुरू और मान खींचता है और इसे उपयोगकर्ता को लौटाता है।
  8. जब g.sendअगला कहा जाता है, तो चरण 1 पर वापस जाएं।

चक्रीय होते समय, इस प्रक्रिया की एक शुरुआत होती है: जब g.send(None)- जो है उसके next(g)लिए कम है - पहले कहा जाता है ( Noneपहली sendकॉल के अलावा कुछ और पास करना अवैध है )। और इसका एक अंत हो सकता है: जब yieldजनरेटर फ़ंक्शन के शरीर में पहुंचने के लिए अधिक विवरण नहीं होते हैं ।

क्या आप देखते हैं कि क्या yieldबयान (या अधिक सटीक, जनरेटर) इतना खास बनाता है ? औसत दर्जे के returnकीवर्ड के विपरीत , yieldइसके कॉलर को मान देने में सक्षम है और इसके कॉलर से उन सभी मूल्यों को प्राप्त कर सकता है, जो इस फ़ंक्शन को समाप्त किए बिना! (बेशक, यदि आप किसी फ़ंक्शन को समाप्त करना चाहते हैं - या एक जनरेटर - यह returnकीवर्ड के रूप में अच्छी तरह से काम करने के लिए आसान है ।) जब एक yieldबयान का सामना किया जाता है, तो जनरेटर फ़ंक्शन केवल रुकता है, और फिर वापस ऊपर उठाता है जहां यह छोड़ दिया है एक और मूल्य भेजा जा रहा है। और sendयह बाहर से एक जनरेटर फ़ंक्शन के अंदर के साथ संवाद करने के लिए सिर्फ इंटरफ़ेस है।

अगर हम वास्तव में इस पुश / पुल / पाइप सादृश्य को तोड़ना चाहते हैं, तो हम निम्नलिखित pseudocode के साथ समाप्त होते हैं, जो वास्तव में घर को ड्राइव करता है, जो 1-5 कदम से अलग है, yieldऔर sendएक ही सिक्के के पाइप के दो पहलू हैं :

  1. right_end.push(None) # the first half of g.send; sending None is what starts a generator
  2. right_end.pause()
  3. left_end.start()
  4. initial_value = left_end.pull()
  5. if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
  6. left_end.do_stuff()
  7. left_end.push(y) # the first half of yield
  8. left_end.pause()
  9. right_end.resume()
  10. value1 = right_end.pull() # the second half of g.send
  11. right_end.do_stuff()
  12. right_end.push(value2) # the first half of g.send (again, but with a different value)
  13. right_end.pause()
  14. left_end.resume()
  15. x = left_end.pull() # the second half of yield
  16. goto 6

मुख्य परिवर्तन यह है कि हम विभाजित हैं x = yield yऔर value1 = g.send(value2)प्रत्येक दो बयानों में: left_end.push(y)और x = left_end.pull(); और value1 = right_end.pull()और right_end.push(value2)yieldकीवर्ड के दो विशेष मामले हैं : x = yieldऔर yield y। ये सिंटैक्टिक शुगर हैं, क्रमशः, के लिए x = yield Noneऔर _ = yield y # discarding value

सटीक क्रम के संबंध में विशिष्ट विवरण के लिए जिसमें मान पाइप के माध्यम से भेजे जाते हैं, नीचे देखें।


इसके बाद के संस्करण के एक लंबे समय तक ठोस मॉडल है। सबसे पहले, यह पहले ध्यान दिया जाना चाहिए कि किसी भी जनरेटर के लिए g, next(g)इसके बिल्कुल बराबर है g.send(None)। इसे ध्यान में रखते हुए हम sendकेवल इस बात पर ध्यान केंद्रित कर सकते हैं कि कैसे काम करता है और केवल जनरेटर को आगे बढ़ाने के बारे में बात करता है send

मान लीजिए हमारे पास है

def f(y):  # This is the "generator function" referenced above
    while True:
        x = yield y
        y = x
g = f(1)
g.send(None)  # yields 1
g.send(2)     # yields 2

अब, fनिम्नलिखित साधारण (गैर-जनरेटर) फ़ंक्शन के मोटे तौर पर desugars की परिभाषा :

def f(y):
    bidirectional_pipe = BidirectionalPipe()
    left_end = bidirectional_pipe.left_end
    right_end = bidirectional_pipe.right_end

    def impl():
        initial_value = left_end.pull()
        if initial_value is not None:
            raise TypeError(
                "can't send non-None value to a just-started generator"
            )

        while True:
            left_end.push(y)
            x = left_end.pull()
            y = x

    def send(value):
        right_end.push(value)
        return right_end.pull()

    right_end.send = send

    # This isn't real Python; normally, returning exits the function. But
    # pretend that it's possible to return a value from a function and then
    # continue execution -- this is exactly the problem that generators were
    # designed to solve!
    return right_end
    impl()

निम्नलिखित इस परिवर्तन में हुआ है f:

  1. हमने कार्यान्वयन को एक नेस्टेड फ़ंक्शन में स्थानांतरित कर दिया है।
  2. हमने एक द्विदिश पाइप बनाया left_endहै जिसे नेस्टेड फ़ंक्शन द्वारा एक्सेस किया right_endजाएगा और जिसे वापस लौटाया जाएगा और बाहरी दायरे द्वारा एक्सेस किया जाएगा - right_endजिसे हम जेनरेटर ऑब्जेक्ट के रूप में जानते हैं।
  3. नेस्टेड फ़ंक्शन के भीतर, हम जो सबसे पहला काम करते हैं, वह यह left_end.pull()है कि Noneइस प्रक्रिया में एक धक्का मूल्य का उपभोग करना है।
  4. नेस्टेड फ़ंक्शन के भीतर, स्टेटमेंट x = yield yको दो लाइनों द्वारा प्रतिस्थापित किया गया है: left_end.push(y)और x = left_end.pull()
  5. हमने sendफ़ंक्शन को परिभाषित किया है right_end, जो दो पंक्तियों का प्रतिरूप है जिसे हमने x = yield yपिछले चरण में बयान से बदल दिया था ।

इस फंतासी दुनिया में जहां लौटने के बाद कार्य जारी रह सकते हैं, gसौंपा जाता है right_endऔर फिर impl()कहा जाता है तो ऊपर हमारे उदाहरण में, क्या हम लाइन द्वारा निष्पादन लाइन का पालन करते हैं, क्या होगा यह मोटे तौर पर निम्नलिखित है:

left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end

y = 1  # from g = f(1)

# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks

# Receive the pushed value, None
initial_value = left_end.pull()

if initial_value is not None:  # ok, `g` sent None
    raise TypeError(
        "can't send non-None value to a just-started generator"
    )

left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off

# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()

# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes

# Receive the pushed value, 2
x = left_end.pull()
y = x  # y == x == 2

left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off

# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()

x = left_end.pull()
# blocks until the next call to g.send

यह ऊपर दिए गए 16-चरण वाले स्यूडोकोड के समान है।

कुछ अन्य विवरण हैं, जैसे कि त्रुटियों का प्रचार कैसे किया जाता है और जनरेटर के अंत तक पहुंचने पर क्या होता है (पाइप बंद है), लेकिन यह स्पष्ट करना चाहिए कि मूल नियंत्रण प्रवाह कैसे काम करता है जब sendइसका उपयोग किया जाता है।

इन समान नियमों का उपयोग करते हुए, आइए दो विशेष मामलों को देखें:

def f1(x):
    while True:
        x = yield x

def f2():  # No parameter
    while True:
        x = yield x

अधिकांश भाग के लिए वे उसी तरह से उतरते हैं जैसे कि f, केवल अंतर यह है कि yieldबयान कैसे रूपांतरित होते हैं:

def f1(x):
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end


def f2():
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end

पहले में, पास किया गया मान f1शुरू में धकेल दिया जाता है (उपज दिया जाता है), और फिर खींचे गए (भेजे गए) सभी मानों को पीछे धकेल दिया जाता है। दूसरे में, xइसका कोई मूल्य (अभी तक) नहीं है जब यह पहली बार आता है push, तो एक UnboundLocalErrorउठाया जाता है।


"तर्क 1 में g = f (1) को सामान्य रूप से पकड़ लिया गया है और f के शरीर के भीतर y को सौंपा गया है, लेकिन जबकि ट्रू अभी तक शुरू नहीं हुआ है।" क्यों नहीं? पायथन इस कोड को चलाने की कोशिश क्यों नहीं करेगा जब तक कि उसका सामना न हो yield?
जोश

@Josh कर्सर पहली कॉल तक उन्नत नहीं है send; send(None)कर्सर को पहले yieldस्टेटमेंट में ले जाने के लिए एक कॉल लगता है , और उसके बाद ही sendकॉल बाद में वास्तव में "वास्तविक" मान भेजते हैं yield
बॉलपॉइंटबैन

धन्यवाद - यह दिलचस्प है, इसलिए दुभाषिया जानता है कि फ़ंक्शन किसी बिंदु पर f होगा yield , और इस तरह जब तक यह sendकॉलर से नहीं मिलता है तब तक प्रतीक्षा करें ? एक सामान्य समारोह के साथ दुभाषिया सिर्फ fसही दूर सही निष्पादित करना शुरू करेगा ? आखिरकार, पायथन में किसी भी प्रकार का कोई एओटी संकलन नहीं है। क्या आपको यकीन है कि ऐसा है? (यह सवाल नहीं कि आप क्या कह रहे हैं, मैं वास्तव में आपके द्वारा लिखी गई बातों से हैरान हूँ)। मैं इस बारे में और अधिक पढ़ सकता हूं कि पायथन को कैसे पता है कि इसे बाकी फ़ंक्शन को निष्पादित करने से पहले इंतजार करना होगा?
जोश

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

धन्यवाद BallpointBen। बहुत दिलचस्प है, मैंने एक सवाल यहां छोड़ दिया कि यह मामला क्यों है।
जोश

2

इनने मुझे भी भ्रमित किया। यहाँ एक उदाहरण दिया गया है जब मैंने एक जनरेटर स्थापित करने की कोशिश की है जो वैकल्पिक क्रम में पैदावार और संकेतों को स्वीकार करता है (उपज, स्वीकार, उपज, स्वीकार) ...

def echo_sound():

    thing_to_say = '<Sound of wind on cliffs>'
    while True:
        thing_to_say = (yield thing_to_say)
        thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
        yield None  # This is the return value of send.

gen = echo_sound()

print 'You are lost in the wilderness, calling for help.'

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

आउटपुट है:

You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.