पायथन में जनरेटर ऑब्जेक्ट को रीसेट करना


153

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

y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)

बेशक, मैं सामग्री को सरल सूची में कॉपी करने का ध्यान रख रहा हूं। क्या मेरा जनरेटर रीसेट करने का कोई तरीका है?

जवाबों:


119

एक अन्य विकल्प itertools.tee()अपने जनरेटर का दूसरा संस्करण बनाने के लिए फ़ंक्शन का उपयोग करना है :

y = FunctionWithYield()
y, y_backup = tee(y)
for x in y:
    print(x)
for x in y_backup:
    print(x)

यह मेमोरी उपयोग के दृष्टिकोण से फायदेमंद हो सकता है यदि मूल पुनरावृत्ति सभी वस्तुओं को संसाधित नहीं कर सकता है।


33
यदि आप सोच रहे हैं कि इस मामले में यह क्या करेगा, तो यह अनिवार्य रूप से सूची में तत्वों को कैशिंग कर रहा है। तो आप y = list(y)अपने शेष कोड के साथ अपरिवर्तित उपयोग कर सकते हैं।
एलिया एन।

5
टी () डेटा को संग्रहीत करने के लिए आंतरिक रूप से एक सूची बनाएगा, इसलिए जैसा मैंने अपने उत्तर में किया था।
nosklo

6
निहितार्थ को देखें ( docs.python.org/library/itertools.html#itertools.tee ) - इसमें आलसी लोड रणनीति का उपयोग किया जाता है, इसलिए केवल मांग पर कॉपी की गई वस्तुओं को सूचीबद्ध करें
Dewfy

11
@Dewfy: जो धीमी होगी क्योंकि सभी वस्तुओं को वैसे भी कॉपी करना होगा।
nosklo

8
हां, सूची () इस मामले में बेहतर है। टी केवल तभी उपयोगी है जब आप पूरी सूची का उपभोग नहीं कर रहे हों
गुरुत्वाकर्षण

148

जनरेटर फिर से जारी नहीं किए जा सकते। आपके पास निम्नलिखित विकल्प हैं:

  1. जेनरेटर फ़ंक्शन को फिर से चलाएँ, पीढ़ी को पुनरारंभ करना:

    y = FunctionWithYield()
    for x in y: print(x)
    y = FunctionWithYield()
    for x in y: print(x)
    
  2. मेमोरी या डिस्क पर एक डेटा संरचना में जनरेटर परिणामों को स्टोर करें जिसे आप फिर से पुन: व्यवस्थित कर सकते हैं:

    y = list(FunctionWithYield())
    for x in y: print(x)
    # can iterate again:
    for x in y: print(x)
    

विकल्प 1 का नकारात्मक पक्ष यह है कि यह फिर से मूल्यों की गणना करता है। यदि वह सीपीयू-गहन है तो आप दो बार गणना करते हैं। दूसरी ओर, 2 का नकारात्मक हिस्सा भंडारण है। मूल्यों की पूरी सूची स्मृति पर संग्रहीत की जाएगी। यदि बहुत अधिक मूल्य हैं, तो यह अव्यावहारिक हो सकता है।

तो आपके पास क्लासिक मेमोरी बनाम प्रोसेसिंग ट्रेडऑफ है । मैं मानों को संचय किए बिना या उन्हें फिर से गणना किए बिना जनरेटर को रिवाइंड करने के तरीके की कल्पना नहीं कर सकता।


फ़ंक्शन कॉल के हस्ताक्षर को सहेजने का तरीका मौजूद हो सकता है? FunctionWithYield, param1, param2 ...
Dewfy

3
@ नई: सुनिश्चित करें: कॉल call_my_func (): रिटर्न फंक्शनविथिल्ड (param1, param2)
nosklo

@Dewfy "फंक्शन सिग्नेचर सेव सेव" से आपका क्या अभिप्राय है? क्या आप समझा सकते हैं? क्या आप जनरेटर को पारित मापदंडों को बचाने का मतलब है?
Андрей Беньковский

2
(1) का एक और नकारात्मक पहलू यह भी है कि FunctionWithYield () न केवल महंगा हो सकता है, बल्कि असंभव भी हो सकता है , उदाहरण के लिए अगर यह स्टड से पढ़ रहा हो।
मैक्स

2
यह बताने के लिए कि @Max ने क्या कहा, यदि फ़ंक्शन का आउटपुट कॉल के बीच बदल सकता है (या होगा), (1) अप्रत्याशित और / या अवांछनीय परिणाम दे सकता है।
सैम_Butler 13:19

36
>>> def gen():
...     def init():
...         return 0
...     i = init()
...     while True:
...         val = (yield i)
...         if val=='restart':
...             i = init()
...         else:
...             i += 1

>>> g = gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
>>> g.send('restart')
0
>>> g.next()
1
>>> g.next()
2

29

संभवतः सबसे सरल उपाय यह है कि किसी वस्तु में महंगे हिस्से को लपेटकर जनरेटर को पास किया जाए:

data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass

इस तरह, आप महंगी गणनाओं को कैश कर सकते हैं।

यदि आप एक ही समय में सभी परिणाम रैम में रख सकते हैं, तो list()एक सादे सूची में जनरेटर के परिणामों को उत्प्रेरित करने के लिए उपयोग करें और उसी के साथ काम करें।


23

मैं एक पुरानी समस्या का एक अलग समाधान पेश करना चाहता हूं

class IterableAdapter:
    def __init__(self, iterator_factory):
        self.iterator_factory = iterator_factory

    def __iter__(self):
        return self.iterator_factory()

squares = IterableAdapter(lambda: (x * x for x in range(5)))

for x in squares: print(x)
for x in squares: print(x)

इसका लाभ जब किसी चीज की तुलना में यह list(iterator)है कि यह O(1)अंतरिक्ष जटिलता है और list(iterator)है O(n)। नुकसान यह है कि, यदि आपके पास केवल इट्रेटर तक पहुंच है, लेकिन फ़ंक्शन जो इट्रेटर का उत्पादन नहीं करता है, तो आप इस पद्धति का उपयोग नहीं कर सकते। उदाहरण के लिए, ऐसा करना उचित प्रतीत हो सकता है, लेकिन यह काम नहीं करेगा।

g = (x * x for x in range(5))

squares = IterableAdapter(lambda: g)

for x in squares: print(x)
for x in squares: print(x)

@Dewfy पहले स्निपेट में, जनरेटर "स्क्वायर = ..." लाइन पर है। जेनरेटर एक्सप्रेशंस उसी तरह का व्यवहार करता है जैसे किसी फंक्शन को कॉल करना, जो पैदावार का उपयोग करता है, और मैंने केवल एक का उपयोग किया है क्योंकि यह इतने कम उदाहरण के लिए यील्ड के साथ फंक्शन लिखने की तुलना में कम क्रिया है। दूसरे स्निपेट में, मैंने जनरेटर_फैक्ट्री के रूप में फ़ंक्शनविथिल्ड का उपयोग किया है, इसलिए इसे जब भी इसेर कहा जाता है, जो कि जब भी मैं "वाई में एक्स के लिए" लिखता हूं।
michaelsnowden

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

5

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

अद्यतन: भी देखें itertools.tee()। इसमें ऊपर वर्णित कुछ मेमोरी बनाम प्रोसेसिंग ट्रेडऑफ शामिल हैं, लेकिन यह जनरेटर मेमोरी को ए में संग्रहीत करने से अधिक कुछ मेमोरी को बचा सकता है list; यह इस बात पर निर्भर करता है कि आप जनरेटर का उपयोग कैसे कर रहे हैं।


5

यदि आपका जनरेटर इस मायने में शुद्ध है कि इसका आउटपुट केवल उत्तीर्ण तर्कों और चरण संख्या पर निर्भर करता है, और आप चाहते हैं कि परिणामी जनरेटर पुनः आरंभ करने योग्य हो, तो यहां एक प्रकार का स्निपेट हो सकता है जो उपयोगी हो सकता है:

import copy

def generator(i):
    yield from range(i)

g = generator(10)
print(list(g))
print(list(g))

class GeneratorRestartHandler(object):
    def __init__(self, gen_func, argv, kwargv):
        self.gen_func = gen_func
        self.argv = copy.copy(argv)
        self.kwargv = copy.copy(kwargv)
        self.local_copy = iter(self)

    def __iter__(self):
        return self.gen_func(*self.argv, **self.kwargv)

    def __next__(self):
        return next(self.local_copy)

def restartable(g_func: callable) -> callable:
    def tmp(*argv, **kwargv):
        return GeneratorRestartHandler(g_func, argv, kwargv)

    return tmp

@restartable
def generator2(i):
    yield from range(i)

g = generator2(10)
print(next(g))
print(list(g))
print(list(g))
print(next(g))

आउटपुट:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
0
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1

3

से टी के आधिकारिक दस्तावेज :

सामान्य तौर पर, यदि एक पुनरावृत्तिकर्ता किसी अन्य पुनरावृत्ति शुरू होने से पहले डेटा का सबसे या सभी का उपयोग करता है, तो यह टी () के बजाय सूची () का उपयोग करने के लिए तेज़ है।

इसलिए list(iterable)आपके मामले में इसके बजाय इसका उपयोग करना सबसे अच्छा है ।


6
अनंत जनरेटर के बारे में क्या?
डेफी

1
गति केवल विचार नहीं है; list()पूरे पुनरावृत्ति को स्मृति में रखता है
क्रिस_रंड

@Chris_Rands तो tee()अगर एक पुनरावृत्तिकर्ता सभी मानों का उपभोग करता है - तो यह है कि कैसे teeकाम करता है।
एसीफैम्पियन

2
@ नई: अनंत जनरेटर के लिए, आरोन डिगुल्ला के समाधान (कीमती डेटा के लिए महंगासेटअप फ़ंक्शन) का उपयोग करें।
जेफ लीमैन

3

संभाल करने के लिए एक आवरण समारोह का उपयोग करना StopIteration

आप अपने जेनरेटर-जनरेट करने वाले फ़ंक्शन को एक साधारण आवरण फ़ंक्शन लिख सकते हैं जो जनरेटर के समाप्त होने पर ट्रैक करता है। StopIterationजब यह पुनरावृत्ति के अंत तक पहुँच जाता है तो यह एक जनरेटर थ्रो का उपयोग करके ऐसा करेगा ।

import types

def generator_wrapper(function=None, **kwargs):
    assert function is not None, "Please supply a function"
    def inner_func(function=function, **kwargs):
        generator = function(**kwargs)
        assert isinstance(generator, types.GeneratorType), "Invalid function"
        try:
            yield next(generator)
        except StopIteration:
            generator = function(**kwargs)
            yield next(generator)
    return inner_func

जैसा कि आप ऊपर रख सकते हैं, जब हमारा आवरण फ़ंक्शन StopIterationअपवाद को पकड़ता है, तो यह केवल जनरेटर ऑब्जेक्ट को फिर से इनिशियलाइज़ करता है (फ़ंक्शन कॉल के किसी अन्य उदाहरण का उपयोग करके)।

और फिर, मान लें कि आप अपने जनरेटर-सप्लाई फ़ंक्शन को नीचे के रूप में परिभाषित करते हैं, तो आप इसे निहित करने के लिए पायथन फ़ंक्शन डेकोरेटर सिंटैक्स का उपयोग कर सकते हैं:

@generator_wrapper
def generator_generating_function(**kwargs):
    for item in ["a value", "another value"]
        yield item

2

आप एक फ़ंक्शन को परिभाषित कर सकते हैं जो आपके जनरेटर को लौटाता है

def f():
  def FunctionWithYield(generator_args):
    code here...

  return FunctionWithYield

अब आप जितनी बार चाहें उतनी बार कर सकते हैं:

for x in f()(generator_args): print(x)
for x in f()(generator_args): print(x)

1
उत्तर के लिए धन्यवाद, लेकिन सवाल का मुख्य बिंदु निर्माण से बचना था , आंतरिक कार्य को लागू करने से निर्माण छिप जाता है - आप इसे दो बार बनाते हैं
डेफी

1

मुझे यकीन नहीं है कि महंगी तैयारी से आपका क्या मतलब है, लेकिन मुझे लगता है कि आपके पास वास्तव में है

data = ... # Expensive computation
y = FunctionWithYield(data)
for x in y: print(x)
#here must be something to reset 'y'
# this is expensive - data = ... # Expensive computation
# y = FunctionWithYield(data)
for x in y: print(x)

यदि ऐसा है, तो पुन: उपयोग क्यों नहीं किया जाता है data?


1

पुनरावृत्तियों को रीसेट करने का कोई विकल्प नहीं है। Iterator आमतौर पर जब यह next()कार्य के माध्यम से पुनरावृत्ति करता है तो बाहर निकलता है। केवल तरीका यह है कि पुनरावृति ऑब्जेक्ट पर पुनरावृति से पहले बैकअप लेना है। नीचे देखें।

आइटम 0 से 9 के साथ पुनरावृत्त वस्तु बनाना

i=iter(range(10))

अगले () फ़ंक्शन के माध्यम से Iterating जो बाहर पॉप जाएगा

print(next(i))

सूची में पुनरावृत्त वस्तु को परिवर्तित करना

L=list(i)
print(L)
output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

इसलिए आइटम 0 पहले से ही पॉप आउट है। साथ ही सभी आइटम पॉप-अप हो गए हैं क्योंकि हमने सूची में पुनरावृत्ति को परिवर्तित कर दिया है।

next(L) 

Traceback (most recent call last):
  File "<pyshell#129>", line 1, in <module>
    next(L)
StopIteration

तो आपको पुनरावृति शुरू करने से पहले बैकअप के लिए पुनरावृत्तियों को सूचियों में बदलना होगा। सूची को पुनरावृत्त में परिवर्तित किया जा सकता हैiter(<list-object>)


1

अब आप more_itertools.seekable(तृतीय-पक्ष उपकरण) का उपयोग कर सकते हैं जो पुनरावृत्तियों को रीसेट करने में सक्षम बनाता है।

के माध्यम से स्थापित करें > pip install more_itertools

import more_itertools as mit


y = mit.seekable(FunctionWithYield())
for x in y:
    print(x)

y.seek(0)                                              # reset iterator
for x in y:
    print(x)

ध्यान दें: मेमोरी की खपत इट्रेटर को आगे बढ़ाते हुए बढ़ती है, इसलिए बड़े पुनरावृत्तियों से सावधान रहें।


1

आप ऐसा कर सकते हैं कि itertools.cycle () का उपयोग करके आप इस विधि के साथ एक इटरेटर बना सकते हैं और फिर इटरेटर पर एक लूप के लिए निष्पादित कर सकते हैं जो इसके मूल्यों पर लूप करेगा।

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

def generator():
for j in cycle([i for i in range(5)]):
    yield j

gen = generator()
for i in range(20):
    print(next(gen))

बार-बार 20 नंबर, 0 से 4 उत्पन्न करेगा।

डॉक्स से एक नोट:

Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).

+1 क्योंकि यह काम करता है, लेकिन मुझे वहां 2 मुद्दे दिखाई देते हैं 1) बड़े स्मृति पदचिह्न क्योंकि प्रलेखन में कहा गया है "एक कॉपी बनाएं" 2) अनंत लूप निश्चित रूप से वह नहीं है जो मैं चाहता हूं
डेवफी

0

ठीक है, आप कहते हैं कि आप एक जनरेटर को कई बार कॉल करना चाहते हैं, लेकिन आरंभीकरण महंगा है ... ऐसा कुछ क्या है?

class InitializedFunctionWithYield(object):
    def __init__(self):
        # do expensive initialization
        self.start = 5

    def __call__(self, *args, **kwargs):
        # do cheap iteration
        for i in xrange(5):
            yield self.start + i

y = InitializedFunctionWithYield()

for x in y():
    print x

for x in y():
    print x

वैकल्पिक रूप से, आप बस अपना स्वयं का वर्ग बना सकते हैं जो इट्रेटर प्रोटोकॉल का अनुसरण करता है और कुछ प्रकार के 'रीसेट' फ़ंक्शन को परिभाषित करता है।

class MyIterator(object):
    def __init__(self):
        self.reset()

    def reset(self):
        self.i = 5

    def __iter__(self):
        return self

    def next(self):
        i = self.i
        if i > 0:
            self.i -= 1
            return i
        else:
            raise StopIteration()

my_iterator = MyIterator()

for x in my_iterator:
    print x

print 'resetting...'
my_iterator.reset()

for x in my_iterator:
    print x

https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-ults-book/iterators.html


आप सिर्फ रैपर की समस्या को देखते हैं। मान लें कि महंगा आरंभीकरण जनरेटर बनाता है। मेरा सवाल यह था कि आपके अंदर कैसे रीसेट करना है__call__
डेवफी

अपनी टिप्पणी के जवाब में एक दूसरा उदाहरण जोड़ा। यह अनिवार्य रूप से एक रीसेट विधि के साथ एक कस्टम जनरेटर है।
tvt173

0

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

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

class GeneratorSplitter:
'''
Split a generator object into multiple generators which will be sincronised. Each call to each of the sub generators will cause only one call in the input generator. This way multiple methods on threads can iterate the input generator , and the generator will cycled only once.
'''

def __init__(self, gen):
    self.gen = gen
    self.consumers: List[GeneratorSplitter.InnerGen] = []
    self.thread: threading.Thread = None
    self.value = None
    self.finished = False
    self.exception = None

def GetConsumer(self):
    # Returns a generator object. 
    cons = self.InnerGen(self)
    self.consumers.append(cons)
    return cons

def _Work(self):
    try:
        for d in self.gen:
            for cons in self.consumers:
                cons.consumed.wait()
                cons.consumed.clear()

            self.value = d

            for cons in self.consumers:
                cons.readyToRead.set()

        for cons in self.consumers:
            cons.consumed.wait()

        self.finished = True

        for cons in self.consumers:
            cons.readyToRead.set()
    except Exception as ex:
        self.exception = ex
        for cons in self.consumers:
            cons.readyToRead.set()

def Start(self):
    self.thread = threading.Thread(target=self._Work)
    self.thread.start()

class InnerGen:
    def __init__(self, parent: "GeneratorSplitter"):
        self.parent: "GeneratorSplitter" = parent
        self.readyToRead: threading.Event = threading.Event()
        self.consumed: threading.Event = threading.Event()
        self.consumed.set()

    def __iter__(self):
        return self

    def __next__(self):
        self.readyToRead.wait()
        self.readyToRead.clear()
        if self.parent.finished:
            raise StopIteration()
        if self.parent.exception:
            raise self.parent.exception
        val = self.parent.value
        self.consumed.set()
        return val

Ussage:

genSplitter = GeneratorSplitter(expensiveGenerator)

metrics={}
executor = ThreadPoolExecutor(max_workers=3)
f1 = executor.submit(mean,genSplitter.GetConsumer())
f2 = executor.submit(max,genSplitter.GetConsumer())
f3 = executor.submit(someFancyMetric,genSplitter.GetConsumer())
genSplitter.Start()

metrics.update(f1.result())
metrics.update(f2.result())
metrics.update(f3.result())

आप बस पुनर्निवेश करें itertools.isliceया async के लिए aiostream.stream.take, और यह पोस्ट आपको asyn /
wait

-3

यह कोड ऑब्जेक्ट द्वारा किया जा सकता है। यहाँ उदाहरण है।

code_str="y=(a for a in [1,2,3,4])"
code1=compile(code_str,'<string>','single')
exec(code1)
for i in y: print i

1 2 3 4

for i in y: print i


exec(code1)
for i in y: print i

1 2 3 4


4
आरंभिक कोड के दो बार क्रियान्वयन से बचने के लिए वास्तव में, जनरेटर को रीसेट करना आवश्यक था। आपका दृष्टिकोण (1) वैसे भी दो बार आरंभीकरण को कार्यान्वित करता है, (2) इसमें शामिल है execकि इस तरह के सरल मामले के लिए थोड़ा गैर-अनुशंसित।
डेविए
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.