वहाँ एक डेकोरेटर बस कैश फ़ंक्शन रिटर्न मान है?


157

निम्नलिखित को धयान मे रखते हुए:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

मैं नया हूँ, लेकिन मुझे लगता है कि कैशिंग को एक डेकोरेटर में बदल दिया जा सकता है। केवल मुझे यह पसंद नहीं आया;)

पुनश्च वास्तविक गणना उत्परिवर्तनीय मूल्यों पर निर्भर नहीं करती है


वहाँ एक डेकोरेटर हो सकता है जिसमें कुछ क्षमता हो जैसे, लेकिन आपने पूरी तरह से निर्दिष्ट नहीं किया है कि आप क्या चाहते हैं। आप किस तरह के कैशिंग बैकेंड का उपयोग कर रहे हैं? और वैल्यू कैसे की जाएगी? मैं आपके कोड से यह मान रहा हूं कि आप वास्तव में जो मांग रहे हैं वह कैश्ड रीड-ओनली प्रॉपर्टी है।
डेविड बर्जर

मेमोइज़िंग डेकोरेटर्स हैं जो प्रदर्शन करते हैं जिसे आप "कैशिंग" कहते हैं; वे आम तौर पर फ़ंक्शंस पर काम करते हैं (जैसे कि विधियाँ बनने के लिए या नहीं) जिनके परिणाम उनके तर्कों पर निर्भर होते हैं (जैसे कि स्वयं के रूप में नहीं! -) और इसलिए एक अलग ज्ञापन रखें।
एलेक्स मार्टेली

जवाबों:


206

पायथन 3.2 से शुरू एक निर्मित सज्जाकार है:

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

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

फाइबोनैचि संख्या की गणना के लिए LRU कैश का उदाहरण :

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> print([fib(n) for n in range(16)])
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> print(fib.cache_info())
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

यदि आप अजगर 2.x के साथ फंस गए हैं, तो यहां अन्य संगत संस्मरण पुस्तकालयों की एक सूची दी गई है:



बैकपोर्ट अब यहां पाया जा सकता है: pypi.python.org/pypi/backports.functools_lru_cache
फ्रेडरिक नॉर्ड

सिद्धांत में @gerrit यह सामान्य रूप से हैशटैब ऑब्जेक्ट्स के लिए काम करता है - हालाँकि कुछ वॉशेबल ऑब्जेक्ट्स केवल समान होते हैं यदि वे एक ही ऑब्जेक्ट (जैसे उपयोगकर्ता-परिभाषित ऑब्जेक्ट्स बिना स्पष्ट __hash __ () फ़ंक्शन) हों।
जोनाथन

1
@ जोनाथन यह काम करता है, लेकिन गलत तरीके से। अगर मैं फ़ंक्शन के पहले कॉल के बाद एक वॉशेबल, म्यूटेबल तर्क पास करता हूं, और ऑब्जेक्ट का मान बदलता है, तो दूसरी कॉल बदले हुए मूल को लौटाएगी, न कि ऑरिजनल को। यह लगभग निश्चित रूप से नहीं है कि उपयोगकर्ता क्या चाहता है। इसके लिए परिवर्तनशील तर्कों के लिए काम करने के लिए lru_cacheजो भी परिणाम कैशिंग है, उसकी प्रतिलिपि बनाने की आवश्यकता होगी , और functools.lru_cacheकार्यान्वयन में ऐसी कोई भी प्रतिलिपि नहीं बनाई जा रही है । ऐसा करना एक बड़ी वस्तु को कैश करने के लिए हार्ड-टू-फाइंड मेमोरी समस्याओं को पैदा करने का जोखिम भी पैदा करेगा।
गेरिट

@gerrit क्या आप यहाँ पर विचार करेंगे: stackoverflow.com/questions/44583381/… ? मैंने पूरी तरह से आपके उदाहरण का पालन नहीं किया।
जोनाथन

28

ऐसा लगता है कि आप एक सामान्य-उद्देश्य ज्ञापन डेकोरेटर के लिए नहीं पूछ रहे हैं (यानी, आप उस सामान्य मामले में रुचि नहीं रखते हैं, जहां आप अलग-अलग तर्क मूल्यों के लिए रिटर्न वैल्यू कैश करना चाहते हैं)। यही है, आप यह करना चाहेंगे:

x = obj.name  # expensive
y = obj.name  # cheap

जबकि एक सामान्य प्रयोजन के संस्मरण सज्जाकार आपको यह देगा:

x = obj.name()  # expensive
y = obj.name()  # cheap

मैं प्रस्तुत करता हूं कि मेथड-कॉल सिंटैक्स बेहतर शैली है, क्योंकि इससे महंगी गणना की संभावना का पता चलता है जबकि संपत्ति सिंटैक्स एक त्वरित लुकअप का सुझाव देता है।

[अद्यतन: क्लास-आधारित मेमोइज़ेशन डेकोरेटर जिसे मैंने पहले यहां लिंक किया था और उद्धृत किया था, वह विधियों के लिए काम नहीं करता है। मैंने इसे एक डेकोरेटर फ़ंक्शन के साथ बदल दिया है।] यदि आप सामान्य प्रयोजन के मेमोइज़ेशन डेकोरेटर का उपयोग करने के लिए तैयार हैं, तो यहां एक आसान है:

def memoize(function):
  memo = {}
  def wrapper(*args):
    if args in memo:
      return memo[args]
    else:
      rv = function(*args)
      memo[args] = rv
      return rv
  return wrapper

उदाहरण उपयोग:

@memoize
def fibonacci(n):
  if n < 2: return n
  return fibonacci(n - 1) + fibonacci(n - 2)

कैश आकार पर सीमा के साथ एक और संस्मरण डेकोरेटर यहां पाया जा सकता है


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

2
मुझे लगता है कि अगर आर्गन्स हैवी नहीं है तो आप एक समस्या में भाग सकते हैं।
अज्ञात

1
@ ज्ञात हां, मैंने यहां जो पहला डेकोरेटर उद्धृत किया है, वह केवल धोने योग्य प्रकारों तक सीमित है। ActiveState (कैश आकार सीमा के साथ) में तर्क एक (हैशेबल) स्ट्रिंग में चुनता है जो निश्चित रूप से अधिक महंगा है, लेकिन अधिक सामान्य है।
नाथन किचन

वर्ग आधारित सज्जाकार की सीमाओं को इंगित करने के लिए @vanity धन्यवाद। मैंने एक डेकोरेटर फ़ंक्शन को दिखाने के लिए अपने जवाब को संशोधित किया है, जो तरीकों के लिए काम करता है (मैंने वास्तव में यह परीक्षण किया है)।
नाथन किचन

1
@SiminJie डेकोरेटर को केवल एक बार कॉल किया जाता है, और यह लिपटे हुए फ़ंक्शन को वापस लौटाता है, जो सभी अलग-अलग कॉल के लिए उपयोग किया जाता है fibonacci। वह फ़ंक्शन हमेशा एक ही memoशब्दकोश का उपयोग करता है ।
नाथन रसोई

22
class memorize(dict):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args):
        return self[args]

    def __missing__(self, key):
        result = self[key] = self.func(*key)
        return result

नमूना का उपयोग करता है:

>>> @memorize
... def foo(a, b):
...     return a * b
>>> foo(2, 4)
8
>>> foo
{(2, 4): 8}
>>> foo('hi', 3)
'hihihi'
>>> foo
{(2, 4): 8, ('hi', 3): 'hihihi'}

अजीब! यह कैसे काम करता है? यह अन्य सज्जाकारों की तरह नहीं लगता है जो मैंने देखा है।
पास्कलवुकुटेन

1
यदि कोई कीवर्ड तर्क का उपयोग करता है, जैसे foo (3, b = 5)
kadee

1
समाधान की समस्या यह है कि इसमें मेमोरी सीमा नहीं है। नामित तर्कों के लिए, आप उन्हें __ call__ और __ लापता__ में जोड़ सकते हैं जैसे ** nargs
Leonid Mednikov

16

पायथन 3.8 functools.cached_propertyडेकोरेटर

https://docs.python.org/dev/library/functools.html#functools.cached_property

cached_propertyWerkzeug से उल्लेख किया गया था: https://stackoverflow.com/a/5295190/895245 लेकिन माना जाता है कि एक व्युत्पन्न संस्करण 3.8 में विलय कर दिया जाएगा, जो भयानक है।

इस डेकोरेटर को कैशिंग के रूप में देखा जा सकता है @property, या क्लीनर @functools.lru_cacheके रूप में जब आपके पास कोई तर्क नहीं होता है।

डॉक्स कहते हैं:

@functools.cached_property(func)

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

उदाहरण:

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

संस्करण 3.8 में नया।

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


10

Werkzeug में एक cached_propertyडेकोरेटर ( डॉक्स , स्रोत ) है


हाँ। यह सामान्य संस्मरण मामले से अलग करने के लिए सार्थक है, क्योंकि कक्षा के धोने योग्य नहीं होने पर मानक संस्मरण काम नहीं करता है।
जेम्सन क्विन

1
अब Python 3.8 में: docs.python.org/dev/library/…
Ciro Santilli 病

9

मैंने इस सरल डेकोरेटर वर्ग को फ़ंक्शन प्रतिक्रियाओं को कैश करने के लिए कोडित किया। मुझे यह मेरी परियोजनाओं के लिए बहुत उपयोगी लगता है:

from datetime import datetime, timedelta 

class cached(object):
    def __init__(self, *args, **kwargs):
        self.cached_function_responses = {}
        self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0))

    def __call__(self, func):
        def inner(*args, **kwargs):
            max_age = kwargs.get('max_age', self.default_max_age)
            if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age):
                if 'max_age' in kwargs: del kwargs['max_age']
                res = func(*args, **kwargs)
                self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()}
            return self.cached_function_responses[func]['data']
        return inner

उपयोग सीधा है:

import time

@cached
def myfunc(a):
    print "in func"
    return (a, datetime.now())

@cached(default_max_age = timedelta(seconds=6))
def cacheable_test(a):
    print "in cacheable test: "
    return (a, datetime.now())


print cacheable_test(1,max_age=timedelta(seconds=5))
print cacheable_test(2,max_age=timedelta(seconds=5))
time.sleep(7)
print cacheable_test(3,max_age=timedelta(seconds=5))

1
आपकी पहली @cachedमिसाल कोष्ठक है। इसके बजाय यह केवल cachedवस्तु को बदले में लौटाएगा myfuncऔर जब बुलाया जाएगा myfunc()तब innerहमेशा रिटर्न वैल्यू के रूप में लौटाया जाएगा
मार्कस मेस्कैनन

6

अस्वीकरण: मैं बच्चों के लेखक हूँ । कैश

आपको जांचना चाहिए kids.cache, यह एक @cacheडेकोरेटर प्रदान करता है जो अजगर 2 और अजगर 3 पर काम करता है। कोई निर्भरता नहीं, कोड की 100 पंक्तियाँ। उदाहरण के लिए, अपने कोड को ध्यान में रखते हुए, इसका उपयोग करना बहुत सरल है, आप इसे इस तरह उपयोग कर सकते हैं:

pip install kids.cache

फिर

from kids.cache import cache
...
class MyClass(object):
    ...
    @cache            # <-- That's all you need to do
    @property
    def name(self):
        return 1 + 1  # supposedly expensive calculation

या आप (उसी परिणाम) के @cacheबाद डेकोरेटर डाल सकते हैं @property

किसी संपत्ति पर कैश का उपयोग करना आलसी मूल्यांकन कहलाता है , kids.cacheयह बहुत कुछ कर सकता है (यह किसी भी तर्क, गुण, किसी भी प्रकार के तरीकों और यहां तक ​​कि कक्षाओं ...) के साथ कार्य करता है। उन्नत उपयोगकर्ताओं के लिए, kids.cacheसमर्थन करता है cachetoolsजो अजगर 2 और अजगर 3 (LRU, LFU, TTL, RR cache) को फैंसी कैश स्टोर प्रदान करता है।

महत्वपूर्ण नोट : डिफ़ॉल्ट कैश स्टोर kids.cacheएक मानक तानाशाही है, जिसे कभी भी अलग-अलग प्रश्नों के साथ लंबे समय तक चलने वाले कार्यक्रम के लिए अनुशंसित नहीं किया जाता है क्योंकि यह कभी बढ़ते कैशिंग स्टोर का नेतृत्व करेगा। इस उपयोग के लिए आप उदाहरण के लिए ( @cache(use=cachetools.LRUCache(maxsize=2))अपने कार्य / संपत्ति / वर्ग / विधि को सजाने के लिए) का उपयोग करके अन्य कैश स्टोर प्लग इन कर सकते हैं ...


इस मॉड्यूल से अजगर 2 ~ 0.9 (देखें: pastebin.com/raw/aA1ZBE9Z ) पर धीमी गति से आयात समय लगता है । मुझे संदेह है कि यह इस लाइन के कारण है। github.com/0k/kids.cache/blob/master/src/kids/__init__.py#L3 (cf setuptools entry points)। मैं इसके लिए एक मुद्दा बना रहा हूं।
ऋग्वेद

यहाँ ऊपर github.com/0k/kids.cache/issues/9 के लिए एक मुद्दा है ।
Att Righ

यह स्मृति रिसाव की ओर जाता है।
टिमोथी जांग

@vaab एक उदाहरण बनाने cकी MyClass, और साथ निरीक्षण objgraph.show_backrefs([c], max_depth=10), वहाँ वर्ग वस्तु से एक रेफरी श्रृंखला है MyClassकरने के लिए c। यह कहना है, cजब तक जारी नहीं किया MyClassगया था।
टिमोथी जांग

@TimothyZhang आप आमंत्रित हैं और github.com/0k/kids.cache/issues/10 में अपनी चिंताओं को जोड़ने के लिए आपका स्वागत है । Stackoverflow उस पर एक उचित चर्चा करने के लिए सही जगह नहीं है। और आगे स्पष्टीकरण की आवश्यकता है। आपकी प्रतिक्रिया के लिए आपका धन्यवाद।
वाब


4

नहीं है fastcache जो है, "अजगर 3 functools.lru_cache की सी कार्यान्वयन। मानक पुस्तकालय से अधिक 10-30x की speedup प्रदान करता है।"

चुने हुए उत्तर के रूप में भी , बस अलग आयात:

from fastcache import lru_cache
@lru_cache(maxsize=128, typed=False)
def f(a, b):
    pass

इसके अलावा, यह एनाकोंडा में स्थापित किया गया है , जो कि फंक्शंस के विपरीत स्थापित होना चाहिए


1
functoolsमानक पुस्तकालय का एक हिस्सा है, आपके द्वारा पोस्ट किया गया लिंक एक यादृच्छिक गिट कांटा या कुछ और है ...
cz

3

पायथन विकी में मेमोइज़ डेकोरेटर का एक और उदाहरण अभी भी है :

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

यह उदाहरण थोड़ा सा स्मार्ट है, क्योंकि यह परिणाम को कैश नहीं करेगा यदि पैरामीटर उत्परिवर्तनीय हैं। (उस कोड की जाँच करें, यह बहुत सरल और दिलचस्प है!)


3

यदि आप Django फ्रेमवर्क का उपयोग कर रहे हैं, तो इसके पास एपीआई को उपयोग करने के दृश्य या प्रतिक्रिया को कैश करने के लिए ऐसी संपत्ति है @cache_page(time)और साथ ही अन्य विकल्प भी हो सकते हैं।

उदाहरण:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

अधिक विवरण यहां पाया जा सकता है


2

मेमोइज़ उदाहरण के साथ मुझे निम्नलिखित अजगर पैकेज मिले:

  • कैशेपी ; यह ttl और \ या कैश्ड फ़ंक्शंस के लिए कॉल की संख्या सेट करने की अनुमति देता है; इसके अलावा, एक एन्क्रिप्टेड फ़ाइल-आधारित कैश का उपयोग कर सकता है ...
  • percache

1

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

उस ने कहा, मैं शपथ लेता हूं कि मुझे एक मौजूदा मॉड्यूल मिला, जिसने ऐसा किया और खुद को उस मॉड्यूल को खोजने की कोशिश कर रहा था ... निकटतम मैं यह पा सकता हूं, जो सही के बारे में दिखता है: http: //chase-seibert.github। कब / ब्लॉग / 2011/11/23 / pythondjango-डिस्क-आधारित-कैशिंग-decorator.html

एकमात्र समस्या जो मुझे दिखाई देती है, वह बड़े इनपुट के लिए अच्छी तरह से काम नहीं करेगी क्योंकि यह स्ट्रैस (arg) है, जो विशालकाय सरणियों के लिए अद्वितीय नहीं है।

यह अच्छा होगा यदि कोई अनूठे_हाश () प्रोटोकॉल थे जो एक वर्ग को अपनी सामग्री के सुरक्षित हैश लौटाते थे। मैंने मूल रूप से उन प्रकारों के लिए मैन्युअल रूप से लागू किया है जिनकी मैंने परवाह की थी।



1

यदि आप Django का उपयोग कर रहे हैं और विचारों को कैश करना चाहते हैं, तो निखिल कुमार का जवाब देखें


लेकिन यदि आप किसी भी फ़ंक्शन के परिणाम को कैश करना चाहते हैं, तो आप django-cache-utils का उपयोग कर सकते हैं ।

यह Django कैश का पुन cached: उपयोग करता है और डेकोरेटर का उपयोग करने के लिए आसान प्रदान करता है :

from cache_utils.decorators import cached

@cached(60)
def foo(x, y=0):
    print 'foo is called'
    return x+y

1

@lru_cache डिफ़ॉल्ट फ़ंक्शन मानों के साथ सही नहीं है

मेरा memडेकोरेटर:

import inspect


def get_default_args(f):
    signature = inspect.signature(f)
    return {
        k: v.default
        for k, v in signature.parameters.items()
        if v.default is not inspect.Parameter.empty
    }


def full_kwargs(f, kwargs):
    res = dict(get_default_args(f))
    res.update(kwargs)
    return res


def mem(func):
    cache = dict()

    def wrapper(*args, **kwargs):
        kwargs = full_kwargs(func, kwargs)
        key = list(args)
        key.extend(kwargs.values())
        key = hash(tuple(key))
        if key in cache:
            return cache[key]
        else:
            res = func(*args, **kwargs)
            cache[key] = res
            return res
    return wrapper

और परीक्षण के लिए कोड:

from time import sleep


@mem
def count(a, *x, z=10):
    sleep(2)
    x = list(x)
    x.append(z)
    x.append(a)
    return sum(x)


def main():
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5, z=6))
    print(count(1,2,3,4,5, z=6))
    print(count(1))
    print(count(1, z=10))


if __name__ == '__main__':
    main()

परिणाम - नींद के साथ केवल 3 बार

लेकिन इसके साथ @lru_cache4 गुना होगा, क्योंकि यह:

print(count(1))
print(count(1, z=10))

दो बार गणना की जाएगी (चूक के साथ खराब काम)

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.