2012-08-06 11 views
6

निम्नलिखित फ़ंक्शन का उपयोग सजावटी के रूप में किया जाना है जो पहले से गणना किए गए मानों के परिणाम संग्रहीत करता है। (गलती से) कि cache समारोह वस्तु की एक विशेषता होने की जरूरत नहीं है मुझे एहसास हुआसजाए गए फ़ंक्शन की कई कॉलों में डेटा लगातार कैसे रह सकता है?

def cached(f): 
    f.cache = {} 
    def _cachedf(*args): 
     if args not in f.cache: 
      f.cache[args] = f(*args) 

     return f.cache[args] 

    return _cachedf 

: तर्क पहले से ही से पहले गणना की गई है, तो समारोह cache डिक्शनरी में संग्रहीत किए मान प्रदान करेंगे। तथ्यों की बात के रूप में, निम्न कोड के रूप में अच्छी तरह से काम करता है:

def cached(f): 
    cache = {} # <---- not an attribute this time! 
    def _cachedf(*args): 
     if args not in cache: 
      cache[args] = f(*args) 

     return cache[args] 
    return _cachedf 

मैं यह समझने कैसे cache वस्तु एकाधिक कॉल के दौरान जारी रहता हो सकता है कर रहा हूँ। मैंने कई कैश किए गए कार्यों को कई बार कॉल करने का प्रयास किया और कोई संघर्ष या समस्या नहीं मिली।

क्या कोई मुझे यह समझने में मदद कर सकता है कि परिवर्तनीय अभी भी _cachedf फ़ंक्शन लौटाए जाने के बाद भी मौजूद है?

उत्तर

11

आप यहां closure बना रहे हैं: फ़ंक्शन _cachedf() संलग्न गुंजाइश से cache पर बंद हो जाता है। जब तक फ़ंक्शन ऑब्जेक्ट रहता है तब तक cache जीवित रहता है।

संपादित करें: शायद मुझे कुछ और विवरण जोड़ना चाहिए कि यह पायथन में कैसे काम करता है और कैसे सीपीथन इसे लागू करता है। एक सरल उदाहरण पर

आइए नज़र: इंटरैक्टिव दुभाषिया

>>> h = f() 
>>> h() 
1 
>>> h() 
2 
>>> h() 
3 

में

def f(): 
    a = [] 
    def g(): 
     a.append(1) 
     return len(a) 
    return g 

उदाहरण उपयोग समारोह f() युक्त मॉड्यूल के संकलन के दौरान, संकलक देखता समारोह है कि g() संदर्भ a से गुंजाइश संलग्न है और कोड ऑब्जेक्ट कोर में इस बाहरी संदर्भ को याद करता है फंक्शन f() का जवाब देना (विशेष रूप से, यह नाम af.__code__.co_cellvars) जोड़ता है।

तो क्या होता है जब फ़ंक्शन f() कहा जाता है? पहली पंक्ति एक नई सूची वस्तु बनाएं और इसे a नाम से बांध दें। अगली पंक्ति एक नया फ़ंक्शन ऑब्जेक्ट बनाता है (मॉड्यूल के संकलन के दौरान बनाए गए कोड ऑब्जेक्ट का उपयोग करके) और इसे g नाम से बांधता है। g() इस बिंदु पर निष्पादित नहीं किया गया है, और अंततः funciton ऑब्जेक्ट वापस कर दिया गया है।

के बाद से f() का कोड वस्तु एक नोट है कि नाम a स्थानीय कार्यों के द्वारा संदर्भित है, इस नाम के लिए एक "सेल" जब f() दर्ज किया गया है बनाया है। इस सेल में वास्तविक सूची ऑब्जेक्ट a का संदर्भ है, और फ़ंक्शन g() को इस सेल का संदर्भ मिलता है। इस तरह, सूची ऑब्जेक्ट और सेल को भी जीवित रखा जाता है जब funciton f() निकलता है।

+0

स्पष्टीकरण के लिए बहुत बहुत धन्यवाद, आपका संपादन चीजों को बहुत स्पष्ट बनाता है। मुझे आश्चर्य है, (सी) पायथन के आंतरिक सीखने के लिए क्या निरीक्षण या कुछ इसी तरह के माध्यम से आपके पिछले पैराग्राफ में "सेल" का उल्लेख करना संभव है? – rahmu

+0

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

3

क्या कोई मुझे यह समझने में सहायता कर सकता है कि acheachedf फ़ंक्शन वापस आने के बाद कैश वैरिएबल अभी भी मौजूद है?

इसे पाइथन के संदर्भ कचरा कलेक्टर की गणना करने के साथ करना है। cache वैरिएबल संरक्षित और सुलभ होगा क्योंकि फ़ंक्शन _cachedf का संदर्भ है, और cached पर कॉलर का संदर्भ है। जब आप फ़ंक्शन को दोबारा कॉल करते हैं, तो आप अभी भी उसी फ़ंक्शन ऑब्जेक्ट का उपयोग कर रहे हैं जो मूल रूप से बनाया गया था, इसलिए आपके पास अभी भी कैश तक पहुंच है।

आप तब तक कैश नहीं खो देंगे जब तक कि इसके सभी संदर्भ नष्ट नहीं हो जाते। ऐसा करने के लिए आप del ऑपरेटर का उपयोग कर सकते हैं।

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

>>> import time 
>>> def cached(f): 
...  cache = {} # <---- not an attribute this time! 
...  def _cachedf(*args): 
...   if args not in cache: 
...    cache[args] = f(*args) 
...   return cache[args] 
...  return _cachedf 
...  
... 
>>> def foo(duration): 
...  time.sleep(duration) 
...  return True 
...  
... 
>>> bob = cached(foo) 
>>> bob(2) # Takes two seconds 
True 
>>> bob(2) # returns instantly 
True 
>>> del bob # Deletes reference to bob (aka _cachedf) which holds ref to cache 
>>> bob = cached(foo) 
>>> bob(2) # takes two seconds 
True 
>>> 

रिकॉर्ड के लिए, Memoization क्या आप प्राप्त करने के लिए कोशिश कर रहे हैं कहा जाता है, और वहाँ एक और अधिक पूरा memoizing डेकोरेटर decorator pattern page जो एक ही बात करता है से उपलब्ध है, लेकिन एक का उपयोग कर सजावट वर्ग। आपका कोड और क्लास-आधारित सजावट अनिवार्य रूप से वही है, कक्षा-आधारित सजावटी भंडारण से पहले हैश-क्षमता की जांच कर रहा है।


संपादित करें (2017/02/02): @SiminJie टिप्पणी है कि cached(foo)(2) हमेशा एक देरी पड़ता है।

ऐसा इसलिए है क्योंकि cached(foo) ताजा कैश के साथ एक नया फ़ंक्शन देता है। जब cached(foo)(2) कहा जाता है, तो एक नया ताजा (खाली) कैश बनाया जाता है और फिर कैश किए गए फ़ंक्शन को तुरंत कॉल किया जाता है।

चूंकि कैश खाली है और मूल्य नहीं मिलेगा, यह अंतर्निहित फ़ंक्शन को फिर से चलाता है। इसके बजाय, cached_foo = cached(foo) करें और फिर cached_foo(2) कई बार कॉल करें। यह केवल पहली कॉल के लिए देरी होगी। ,

@cached 
def my_long_function(arg1, arg2): 
    return long_operation(arg1,arg2) 

my_long_function(1,2) # incurs delay 
my_long_function(1,2) # doesn't 

आप सज्जाकार के साथ परिचित नहीं हैं तो this answer पर एक नज़र डालें समझने के लिए ऊपर दिए गए कोड का अर्थ है: इसके अलावा, अगर एक डेकोरेटर के रूप में इस्तेमाल, यह उम्मीद के रूप में काम करेंगे।

+0

यह अजगर सजावट के लिए कैसे काम करता है? हर बार जब मैं 'कैश्ड (foo) (2)' कहता हूं, तो यह परिणाम कैश नहीं करता है और दो सेकंड सोता है। सजावटी समारोह के हर कॉल एक ही सजावट के संदर्भ में है? –

+0

@SiminJie - उत्तर में मेरा अतिरिक्त संपादन देखें। – brice

संबंधित मुद्दे