पायथन मेमोइज़िंग / डिफर्ड लुकअप प्रॉपर्टी डेकोरेटर


109

हाल ही में मैं एक मौजूदा कोड बेस से गुजरा हूं जिसमें कई वर्ग हैं, जहां एट्रिब्यूट्स एक डेटाबेस में संग्रहीत मूल्यों को दर्शाते हैं। मैंने इनमें से बहुत सारी विशेषताओं को अपने डेटाबेस लुकअप को स्थगित करने के लिए निरस्त कर दिया है, अर्थात। कंस्ट्रक्टर में इनिशियलाइज़ नहीं किया जाना चाहिए लेकिन केवल पहले पढ़ने पर। ये विशेषताएँ उदाहरण के जीवनकाल में नहीं बदलती हैं, लेकिन वे पहली बार गणना करने के लिए एक वास्तविक अड़चन हैं और केवल विशेष मामलों के लिए ही एक्सेस की जाती हैं। इसलिए उन्हें डेटाबेस से पुनर्प्राप्त किए जाने के बाद भी कैश किया जा सकता है (यह इसलिए मेमॉलेशन की परिभाषा फिट बैठता है जहां इनपुट बस "कोई इनपुट नहीं है")।

मैं अपने आप को विभिन्न वर्गों में विभिन्न विशेषताओं के लिए बार-बार कोड के निम्नलिखित स्निपेट टाइप करता हुआ पाता हूं:

class testA(object):

  def __init__(self):
    self._a = None
    self._b = None

  @property
  def a(self):
    if self._a is None:
      # Calculate the attribute now
      self._a = 7
    return self._a

  @property
  def b(self):
    #etc

क्या पायथन में पहले से ही ऐसा करने के लिए एक मौजूदा डेकोरेटर है जिससे मैं अनजान हूँ? या, एक डेकोरेटर को परिभाषित करने का एक बहुत सरल तरीका है जो ऐसा करता है?

मैं पायथन 2.5 के तहत काम कर रहा हूं, लेकिन अगर वे काफी अलग हैं, तो 2.6 जवाब अभी भी दिलचस्प हो सकते हैं।

ध्यान दें

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


मैं पायथन 2.7 का उपयोग कर रहा हूं, और मुझे इसके लिए तैयार सज्जाकारों के बारे में कुछ भी नहीं दिख रहा है। क्या आप तैयार किए गए सज्जाकारों को एक लिंक प्रदान कर सकते हैं जो प्रश्न में उल्लिखित हैं?
— Bamcclur

@Bamcclur क्षमा करें, इनका विवरण देने के लिए अन्य टिप्पणियां भी हुआ करती थीं, यह सुनिश्चित नहीं था कि इन्हें हटाया क्यों गया। केवल एक जिसे मैं अभी पा सकता हूं वह है पायथन 3 वन functools.lru_cache():।
— detly

सुनिश्चित नहीं हैं कि बिल्ट-इन (कम से कम पायथन 2.7) हैं, लेकिन वहाँ बोल्ट्स लाइब्रेरी की कैश्डप्रॉपर्टी है
— मर्दानाड

@guyarad मैंने अब तक यह टिप्पणी नहीं देखी थी। यह एक शानदार पुस्तकालय है! उत्तर के रूप में पोस्ट करें ताकि मैं इसे बढ़ा सकूं।
— जासूसी

जवाबों:


12

सभी प्रकार की महान उपयोगिताओं के लिए मैं बोल्टन का उपयोग कर रहा हूं ।

उस लाइब्रेरी के हिस्से के रूप में आपके पास कैश्प्रोपरेटी है :

from boltons.cacheutils import cachedproperty

class Foo(object):
    def __init__(self):
        self.value = 4

    @cachedproperty
    def cached_prop(self):
        self.value += 1
        return self.value


f = Foo()
print(f.value)  # initial value
print(f.cached_prop)  # cached property is calculated
f.value = 1
print(f.cached_prop)  # same value for the cached property - it isn't calculated again
print(f.value)  # the backing value is different (it's essentially unrelated value)

124

यहाँ एक आलसी संपत्ति डेकोरेटर का एक उदाहरण कार्यान्वयन है:

import functools

def lazyprop(fn):
    attr_name = '_lazy_' + fn.__name__

    @property
    @functools.wraps(fn)
    def _lazyprop(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)

    return _lazyprop


class Test(object):

    @lazyprop
    def a(self):
        print 'generating "a"'
        return range(5)

इंटरएक्टिव सत्र:

>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]

1
क्या कोई आंतरिक कार्य के लिए उपयुक्त नाम सुझा सकता है? मैं सुबह चीजों को नाम देने में बहुत बुरा हूं ...
— माइक बोर्स

2
मैं आमतौर पर आंतरिक फ़ंक्शन को पूर्ववर्ती अंडरस्कोर के साथ बाहरी फ़ंक्शन के समान नाम देता हूं। तो "_lazyprop" - pep 8 के "आंतरिक उपयोग केवल" दर्शन का अनुसरण करता है
— 16:27 बजे खर्च

1
यह बहुत अच्छा काम करता है :) मुझे नहीं पता कि मेरे लिए ऐसा क्यों हुआ, जैसे कि एक नेस्टेड फ़ंक्शन पर एक डेकोरेटर का उपयोग करना।
— detly

4
गैर-डेटा डिस्क्रिप्टर प्रोटोकॉल को देखते हुए, यह नीचे दिए गए उत्तर की तुलना में बहुत धीमा और कम सुरुचिपूर्ण है__get__
— Ronny

1
टिप: अपने @wraps(fn)@propertywrapsfunctools
— डॉक्स

111

मैंने इसे अपने लिए लिखा है ... सच में एक बार की गणना के लिए इस्तेमाल किया जा सकता है आलसी गुण। मुझे यह पसंद है क्योंकि यह वस्तुओं पर अतिरिक्त विशेषताओं को चिपकाने से बचता है, और एक बार सक्रिय होने के बाद विशेषता उपस्थिति आदि के लिए समय की जाँच नहीं करता है।

import functools

class lazy_property(object):
    '''
    meant to be used for lazy evaluation of an object attribute.
    property should represent non-mutable data, as it replaces itself.
    '''

    def __init__(self, fget):
        self.fget = fget

        # copy the getter function's docstring and other attributes
        functools.update_wrapper(self, fget)

    def __get__(self, obj, cls):
        if obj is None:
            return self

        value = self.fget(obj)
        setattr(obj, self.fget.__name__, value)
        return value


class Test(object):

    @lazy_property
    def results(self):
        calcs = 1  # Do a lot of calculation here
        return calcs

नोट: lazy_propertyवर्ग एक गैर-डेटा विवरणक है , जिसका अर्थ है कि यह केवल पढ़ने के लिए है। एक __set__विधि जोड़ने से इसे सही तरीके से काम करने से रोका जा सकेगा।


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

2
पश्चात के लिए: इसके अन्य संस्करणों को अन्य उत्तरों में प्रस्तावित किया गया है (रेफ 1 और 2 )। लगता है कि यह पायथन वेब फ्रेमवर्क (पिरामिड और वर्कर्ज़ग में डेरिवेटिव मौजूद है) में एक लोकप्रिय है।
— एंड्रे कारन

1
यह ध्यान देने के लिए धन्यवाद कि Werkzeug के पास werkzeug.utils.cached_property है: werkzeug.pocoo.org/docs/utils/#werkzeug.utils.cached_property
— diviraira

3
मुझे यह विधि चयनित उत्तर की तुलना में 7.6 गुना तेज लगी। (2.45 2.s / 322 ns) आईपीथॉन नोटबुक देखें
— डेव बटलर

1
ध्यान दें: इस काम को नहीं रोकता है करने के लिए fgetजिस तरह से @propertyकरता है। अपरिवर्तनीयता / मूर्खता सुनिश्चित करने के लिए, आपको एक __set__()विधि जोड़ने की आवश्यकता है AttributeError('can\'t set attribute')जो (या जो भी अपवाद / संदेश आपको सूट करता है, लेकिन यह वही है property)। यह दुर्भाग्य से एक माइक्रोसेकंड का एक अंश के एक प्रदर्शन प्रभाव के साथ आता है, क्योंकि __get__()प्रत्येक का उपयोग करने के बजाय से खींच fget मूल्य पर बुलाया जाएगा dict दूसरे और बाद के उपयोग पर। अच्छी तरह से यह मेरी राय में अपरिवर्तनीयता / मूर्खता को बनाए रखने के लिए है, जो मेरे उपयोग के मामलों के लिए महत्वपूर्ण है, लेकिन YMMV।
— स्केनी

4

यहाँ एक प्रतिदेय कि में एक वैकल्पिक टाइमआउट तर्क लेता है, __call__तो आप भी पर कॉपी कर सकता है __name__, __doc__, __module__समारोह के नाम स्थान से:

import time

class Lazyproperty(object):

    def __init__(self, timeout=None):
        self.timeout = timeout
        self._cache = {}

    def __call__(self, func):
        self.func = func
        return self

    def __get__(self, obj, objcls):
        if obj not in self._cache or \
          (self.timeout and time.time() - self._cache[key][1] > self.timeout):
            self._cache[obj] = (self.func(obj), time.time())
        return self._cache[obj]

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

class Foo(object):

    @Lazyproperty(10)
    def bar(self):
        print('calculating')
        return 'bar'

>>> x = Foo()
>>> print(x.bar)
calculating
bar
>>> print(x.bar)
bar
...(waiting 10 seconds)...
>>> print(x.bar)
calculating
bar


3

क्या आप वास्तव में चाहते हैं reify(स्रोत जुड़ा हुआ है!) पिरामिड से डेकोरेटर:

एक वर्ग विधि डेकोरेटर के रूप में उपयोग करें। यह पायथन @propertyडेकोरेटर की तरह लगभग संचालित होता है , लेकिन यह पहली कॉल के बाद इसे जिस तरीके से सजाता है, उसके परिणाम को पहली कॉल के बाद प्रभावी ढंग से बदल देता है। यह पायथन parlance में है, एक गैर-डेटा विवरणक। निम्नलिखित एक उदाहरण और इसका उपयोग है:

>>> from pyramid.decorator import reify

>>> class Foo(object):
...     @reify
...     def jammy(self):
...         print('jammy called')
...         return 1

>>> f = Foo()
>>> v = f.jammy
jammy called
>>> print(v)
1
>>> f.jammy
1
>>> # jammy func not called the second time; it replaced itself with 1
>>> # Note: reassignment is possible
>>> f.jammy = 2
>>> f.jammy
2

1
एक अच्छा, वही करता है जिसकी मुझे जरूरत थी ... हालाँकि पिरामिड एक डेकोरेटर के लिए एक बड़ी निर्भरता हो सकती है:)
— det14

@detly सज्जाकार कार्यान्वयन सरल है, और आप इसे स्वयं लागू कर सकते हैं, pyramidनिर्भरता की कोई आवश्यकता नहीं है ।
— पीटर वुड

इसलिए लिंक "स्रोत से जुड़ा हुआ" कहता है: डी
— एंटी हवाला

@AnttiHaapala मैंने देखा, लेकिन मैंने सोचा था कि मैं उन लोगों के लिए लागू करना आसान हूं जो लिंक का पालन नहीं करते हैं।
— पीटर लकड़ी

1

प्रश्न और उत्तर दोनों में अवधारणाओं का मिश्रण और / या भ्रम की स्थिति है।

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

(*) वास्तव में एक सच्चा आलसी मूल्यांकन (उदाहरण के लिए हैस्केल की तुलना) अजगर में प्राप्त करना बहुत कठिन है (और कोड में परिणाम जो मुहावरे से दूर है)।

संस्मरण सही शब्द है जो पूछने वाले के लिए लग रहा है। शुद्ध मूल्य जो रिटर्न वैल्यू मूल्यांकन के लिए साइड इफेक्ट पर निर्भर नहीं होते हैं, उन्हें सुरक्षित रूप से याद किया जा सकता है और वास्तव में फंक्शंस में एक डेकोरेटर है, @functools.lru_cacheइसलिए जब तक आपको विशेष व्यवहार की आवश्यकता न हो, तब तक खुद के डेकोरेटर लिखने की कोई आवश्यकता नहीं है।


मैंने "आलसी" शब्द का उपयोग किया क्योंकि मूल कार्यान्वयन में, सदस्य को ऑब्जेक्ट डीबिसाइज़ेशन के समय एक DB से गणना / पुनर्प्राप्त किया गया था, और मैं उस गणना को तब तक स्थगित करना चाहता हूं जब तक कि संपत्ति वास्तव में एक टेम्पलेट में उपयोग नहीं की गई थी। यह मुझे आलस की परिभाषा से मेल खाता हुआ लग रहा था। मैं इस बात से सहमत हूं कि चूंकि मेरा प्रश्न पहले से ही किसी समाधान का उपयोग करता है @property, इसलिए "आलसी" उस बिंदु पर बहुत मायने नहीं रखता है। (मैंने कैश्ड आउटपुट के लिए इनपुट के मानचित्र के रूप में मेमॉएशन के बारे में भी सोचा था, और चूंकि इन गुणों में केवल एक इनपुट है, कुछ नहीं, एक नक्शा आवश्यक से अधिक जटिलता की तरह लग रहा था।)
— 20:24

ध्यान दें कि सभी डेकोरेटर जिन्हें लोगों ने "आउट ऑफ द बॉक्स" समाधान के रूप में सुझाया है, जब मैंने यह पूछा, तब भी मौजूद नहीं था।
— २०

मैं जेसन से सहमत हूं, यह कैशिंग / ज्ञापन के बारे में एक प्रश्न है जो आलसी मूल्यांकन नहीं है।
— पोइंडेक्सटर

@poindexter - कैशिंग इसे काफी कवर नहीं करता है; यह ऑब्जेक्ट इनिट समय पर मूल्य को देखने में अंतर नहीं करता है और इसे मूल्य को देखने से कैशिंग करता है और संपत्ति के एक्सेस होने पर इसे कैशिंग करता है (जो कि यहां की प्रमुख विशेषता है)। इसे क्या कहूं? "कैश-आफ्टर-फर्स्ट-यूज़" डेकोरेटर?
— जासूस

@ याद रखें। आपको इसे मेमोइज़ कहना चाहिए। en.wikipedia.org/wiki/Memoization
— poindexter

0

आप पायथन देशी संपत्ति से एक वर्ग का निर्माण करके यह अच्छा और आसानी से कर सकते हैं:

class cached_property(property):
    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func

    def __set__(self, obj, value):
        obj.__dict__[self.__name__] = value

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.__name__, None)
        if value is None:
            value = self.func(obj)
            obj.__dict__[self.__name__] = value
        return value

हम इस संपत्ति वर्ग को नियमित वर्ग संपत्ति की तरह उपयोग कर सकते हैं (यह आइटम असाइनमेंट का भी समर्थन कर सकते हैं जैसा कि आप देख सकते हैं)

class SampleClass():
    @cached_property
    def cached_property(self):
        print('I am calculating value')
        return 'My calculated value'


c = SampleClass()
print(c.cached_property)
print(c.cached_property)
c.cached_property = 2
print(c.cached_property)
print(c.cached_property)

मूल्य केवल पहली बार गणना की गई और उसके बाद हमने अपने सहेजे गए मूल्य का उपयोग किया

आउटपुट:

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