2012-06-06 16 views
6

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

मेरे मामले में, फ़ंक्शन के लिए पहला तर्क स्वयं द्वारा एक अद्वितीय पहचानकर्ता है, जो कैश लुकअप के लिए एक कोर कुंजी के रूप में उपयोग के लिए उपयुक्त है, हालांकि मैं कीवर्ड तर्कों का उपयोग करने की क्षमता चाहता था और फिर भी उसी कैश तक पहुंचने की क्षमता चाहता था। मेरा मतलब है, my_func('unique_id', 10) और my_func(foo=10, func_id='unique_id') दोनों को एक ही कैश किए गए परिणाम को वापस करना चाहिए।

ऐसा करने के लिए, हमें जो भी खोजना है, वह कहने के लिए एक साफ और पायदानपूर्ण तरीका है 'जो भी खोजशब्द है, वह पहले तर्क के अनुरूप है)। यही वह है जिसके साथ मैं आया:

class memoize(object): 
    def __init__(self, cls): 
     if type(cls) is FunctionType: 
      # Let's just pretend that the function you gave us is a class. 
      cls.instances = {} 
      cls.__init__ = cls 
     self.cls = cls 
     self.__dict__.update(cls.__dict__) 

    def __call__(self, *args, **kwargs): 
     """Return a cached instance of the appropriate class if it exists.""" 
     # This is some dark magic we're using here, but it's how we discover 
     # that the first argument to Photograph.__init__ is 'filename', but the 
     # first argument to Camera.__init__ is 'camera_id' in a general way. 
     delta = 2 if type(self.cls) is FunctionType else 1 
     first_keyword_arg = [k 
      for k, v in inspect.getcallargs(
       self.cls.__init__, 
       'self', 
       'first argument', 
       *['subsequent args'] * (len(args) + len(kwargs) - delta)).items() 
        if v == 'first argument'][0] 
     key = kwargs.get(first_keyword_arg) or args[0] 
     print key 
     if key not in self.cls.instances: 
      self.cls.instances[key] = self.cls(*args, **kwargs) 
     return self.cls.instances[key] 

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

@memoize 
class FooBar: 
    instances = {} 

    def __init__(self, unique_id, irrelevant=None): 
     print id(self) 

फिर अपने कोड से आप या तो FooBar('12345', 20) या FooBar(irrelevant=20, unique_id='12345') और वास्तव में FooBar का एक ही उदाहरण मिल कॉल कर सकते हैं। इसके बाद आप पहले तर्क के लिए एक अलग वर्ग के साथ एक अलग वर्ग को परिभाषित कर सकते हैं, क्योंकि यह सामान्य तरीके से काम करता है (यानी, सजावटी को इस काम के लिए सजाए जाने वाले वर्ग के बारे में कुछ भी जानने की आवश्यकता नहीं है)।

समस्या है, यह एक धर्मभ्रष्ट गड़बड़ ;-)

यह काम करता है क्योंकि inspect.getcallargs एक dict तर्क आप इसे आपूर्ति करने के लिए परिभाषित कीवर्ड मानचित्रण देता है, तो मैं यह आपूर्ति कुछ जाली तर्क और उसके बाद के लिए dict का निरीक्षण पारित किया गया पहला तर्क।

यदि कोई ऐसी चीज मौजूद थी, तो बहुत अच्छा होगा, inspect.getcallargs का एनालॉग है जो कीवर्ड तर्कों के एक नियम के रूप में तर्कों की सूची के रूप में एकीकृत दोनों प्रकार के तर्कों को वापस कर देता है। यही कारण है कि कुछ इस तरह की अनुमति होगी:

def __call__(self, *args, **kwargs): 
    key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1] 
    if key not in self.cls.instances: 
     self.cls.instances[key] = self.cls(*args, **kwargs) 
    return self.cls.instances[key] 

अन्य तरह से मैं से निपटने के लिए इस सीधे देखने कैश कुंजी के रूप में inspect.getcallargs द्वारा प्रदान की dict का उपयोग करेंगे के देख सकते हैं, लेकिन उस से समान तार बनाने के लिए एक repeatable तरह से की आवश्यकता होगी समान हैश, जो मैंने सुना है, पर भरोसा नहीं किया जा सकता है (मुझे लगता है कि मुझे चाबियाँ सॉर्ट करने के बाद स्ट्रिंग का निर्माण करना होगा)।

क्या किसी के पास इस पर कोई विचार है? क्या यह कीवर्ड तर्कों के साथ एक फ़ंक्शन को कॉल करना और परिणामों को कैश करना गलत है? या बस बहुत मुश्किल है?

उत्तर

4

मैं जैसे निम्नलिखित कुछ सुझाव देंगे:

import inspect 

class key_memoized(object): 
    def __init__(self, func): 
     self.func = func 
     self.cache = {} 

    def __call__(self, *args, **kwargs): 
     key = self.key(args, kwargs) 
     if key not in self.cache: 
      self.cache[key] = self.func(*args, **kwargs) 
     return self.cache[key] 

    def normalize_args(self, args, kwargs): 
     spec = inspect.getargs(self.func.__code__).args 
     return dict(kwargs.items() + zip(spec, args)) 

    def key(self, args, kwargs): 
     a = self.normalize_args(args, kwargs) 
     return tuple(sorted(a.items())) 

उदाहरण:

@key_memoized 
def foo(bar, baz, spam): 
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam) 
    return bar + baz + spam 

print foo(1, 2, 3) 
print foo(1, 2, spam=3)   #memoized 
print foo(spam=3, baz=2, bar=1) #memoized 

नोट कि आप key_memoized भी बढ़ा सकते हैं और अधिक विशिष्ट ज्ञापन रणनीतियां प्रदान करने के लिए key() विधि को ओवरराइड कर सकते हैं , उदा। तर्कों की कुछ अनदेखी करने के लिए:

class memoize_by_bar(key_memoized): 
    def key(self, args, kwargs): 
     return self.normalize_args(args, kwargs)['bar'] 

@memoize_by_bar 
def foo(bar, baz, spam): 
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam) 
    return bar 

print foo('x', 'ignore1', 'ignore2') 
print foo('x', 'ignore3', 'ignore4') 
+0

मुझे इसकी प्रकृति पसंद है लेकिन मैं उस हिस्से के बारे में चिंतित हूं जहां 'कुंजी' रिटर्न 'tuple (a.items()) '। क्या यह अलग-अलग लेकिन समान डिक्ट्स के लिए एक ही क्रम में चाबियाँ सॉर्ट करने की गारंटी है? मैंने सुना है कि समान रूप से दिए गए दोहराए जाने वाले तारों का उत्पादन करने के लिए 'str ({1: 2,3: 4})' जैसे चीजों पर निर्भर नहीं है और बहुत ही गलत है। – robru

+0

ऐसा लगता है कि 'inspect.getargspec (func) .args [0]' विशिष्ट प्रश्न का सटीक उत्तर है जिसे मैंने पूछा (पहली तर्क का नाम कैसे ढूंढें), लेकिन मैं इसे विस्तारित करना चाहता हूं अधिक सामान्य समाधान। एक बार मेरे पास इसे ठीक करने के लिए कुछ समय बाद मैं अपने परिणाम पोस्ट करूंगा। – robru

+0

@Robru: dict sorting के बारे में अच्छी बात है। 'Tuple में बदल दिया गया (क्रमबद्ध (a.items())) (एक और विकल्प' frozenset (a.items()) ') होगा। – georg

3

lru_cache का प्रयास करें:

@functools.lru_cache(maxsize=128, typed=False)

डेकोरेटर एक प्रतिदेय कि MAXSIZE सबसे हाल ही में कॉल करने के लिए बचाता है memoizing के साथ एक समारोह रैप करने के लिए। यह समय बचा सकता है जब एक महंगा या I/O बाध्य कार्य समय-समय पर एक ही तर्क के साथ बुलाया जाता है।

lru_cache अजगर 3.2 में जोड़ा है, लेकिन 2.x में बैकपोर्टेड किया जा सकता है

+0

दिलचस्प के बारे में पढ़ने के लिए, लेकिन दुर्भाग्य से मेरी स्थिति में काम नहीं करता क्योंकि मैं वर्ग staticmethods कैश्ड उदाहरणों से अधिक पुनरावृति करने में सक्षम होने की जरूरत है कि है, तो कैश ही करने की जरूरत है एक वर्ग विशेषता के रूप में उजागर किया जाना चाहिए। – robru

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

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