পাইথন মেমোজাইজিং / বিলম্বিত লুকআপ প্রপার্টি ডেকরেটার


109

সম্প্রতি আমি একটি বিদ্যমান কোড বেসটি পেরিয়েছি যেখানে অনেকগুলি ক্লাস রয়েছে যেখানে উদাহরণের বৈশিষ্ট্যগুলি একটি ডাটাবেসে সঞ্চিত মানগুলি প্রতিফলিত করে। আমি এই বৈশিষ্ট্যগুলির অনেকগুলি রিফ্যাক্টর করেছি যাতে তাদের ডাটাবেস অনুসন্ধানগুলি স্থগিত করা যায়, অর্থাত্। কনস্ট্রাক্টরটিতে আরম্ভ করা হবে না কেবল প্রথম পড়ার পরে। এই বৈশিষ্ট্যগুলি উদাহরণস্বরূপ জীবদ্দশায় পরিবর্তিত হয় না, তবে তারা প্রথম বার গণনা করার জন্য সত্যই বাধা এবং কেবলমাত্র বিশেষ ক্ষেত্রে for সুতরাং ডাটাবেস থেকে পুনরুদ্ধার করার পরে এগুলিও ক্যাশে করা যায় (এটি অতএব স্মৃতিচারণের সংজ্ঞা অনুসারে ফিট করে যেখানে ইনপুটটি কেবল "কোনও ইনপুট নয়")।

আমি নিজেকে বিভিন্ন ক্লাসে বিভিন্ন বৈশিষ্ট্যের জন্য বার বার নীচের কোডের স্নিপেট টাইপ করতে দেখছি:

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 এর অধীনে কাজ করছি তবে ২.6 উত্তর এখনও উল্লেখযোগ্য হতে পারে যদি তা উল্লেখযোগ্যভাবে আলাদা হয়।

বিঃদ্রঃ

পাইথন এর জন্য প্রচুর প্রস্তুত সজ্জিত করার আগে এই প্রশ্ন জিজ্ঞাসা করা হয়েছিল। আমি এটি কেবলমাত্র পরিভাষা সংশোধন করার জন্য আপডেট করেছি।


আমি পাইথন ২.7 ব্যবহার করছি এবং আমি এটির জন্য প্রস্তুত সজ্জকার সম্পর্কে কিছুই দেখতে পাচ্ছি না। প্রশ্নটিতে উল্লিখিত সাজসজ্জাগুলির জন্য আপনি কি একটি লিঙ্ক সরবরাহ করতে পারেন?
বামক্লাকুর

@ বামক্লাকুর দুঃখিত, তাদের বিশদে অন্যান্য মন্তব্য থাকত, কেন তারা মুছে ফেলা হয়েছে তা নিশ্চিত নয় not শুধুমাত্র এক আমি এই মুহূর্তে জানতে পারেন একটি পাইথন 3 এগুলির মধ্যে একটি: functools.lru_cache()
অবধি

সেখানে বিল্ট-ইনগুলি রয়েছে (কমপক্ষে পাইথন ২.7) নিশ্চিত না হলেও বোল্টনস
লোকারাড

@ গুয়ারাড আমি এই মন্তব্যটি এখন পর্যন্ত দেখিনি। এটি একটি দুর্দান্ত লাইব্রেরি! উত্তর হিসাবে পোস্ট করুন যাতে আমি এটি upvote করতে পারেন।
অবধি ২

উত্তর:


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
কেউ কি অভ্যন্তরীণ ফাংশন জন্য উপযুক্ত নাম প্রস্তাব করতে পারেন? সকালে কিছু নামকরণ এত খারাপ ...
মাইক Boers

2
আমি সাধারণত অভ্যন্তরীণ ফাংশনটির নাম পূর্ববর্তী আন্ডারস্কোর সহ বাইরের ফাংশনের মতোই রাখি। সুতরাং "_লাজিপ্রপ" - পিপ ৮ এর "কেবল অভ্যন্তরীণ ব্যবহারের" দর্শন অনুসরণ করে
স্পেনথিল

1
এটি দুর্দান্ত কাজ করে :) আমি জানি না কেন আমার মতো কোনও নেস্টেড ফাংশনে ডেকরেটার ব্যবহার করার বিষয়টি কেন কখনও ঘটেনি।
নিখরচায়

4
অ-ডেটা বর্ণনাকারী প্রোটোকল দেওয়া, এটি নীচের উত্তরগুলির চেয়ে অনেক ধীর এবং কম মার্জিত__get__
রনি

1
টিপ: আপনার ডক স্ট্রিং ইত্যাদি আলগা না করার জন্য একটি @wraps(fn)নীচে রাখুন @property( wrapsআসে functools)
লেটমাইক

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.utils.cached_property আছে: লক্ষ করার জন্য ধন্যবাদ: werkzeug.pocoo.org/docs/utils/#werkzeug.utils.cached_property
ডিভিয়ের

3
আমি এই পদ্ধতিটি নির্বাচিত উত্তরের চেয়ে 7.6 গুণ গতিতে পেয়েছি। (2.45 µs / 322 এনএস) আইপথন নোটবুকটি দেখুন
ডেভ বাটলার

1
বিশেষ দ্রষ্টব্য: এই নিয়োগ বাধা দেয় না করার fgetউপায় @propertyনেই। অপরিবর্তনীয়তা / আদর্শহীনতা নিশ্চিত করার জন্য, আপনাকে __set__()এমন একটি পদ্ধতি যুক্ত করতে হবে যা উত্থাপন করে AttributeError('can\'t set attribute')(বা যাই হোক না কেন ব্যতিক্রম / বার্তা আপনার পক্ষে উপযুক্ত তবে এটিই propertyউত্থাপন করে)। দুর্ভাগ্যক্রমে এটি একটি মাইক্রোসেকেন্ডের ভগ্নাংশের কর্মক্ষমতা প্রভাবের সাথে আসে কারণ দ্বিতীয় এবং পরবর্তী অ্যাক্সেসে ডিক__get__() থেকে fget মান টানানোর পরিবর্তে প্রতিটি অ্যাক্সেসের জন্য ডাকা হবে । অপরিবর্তনীয়তা / আদর্শহীনতা বজায় রাখার পক্ষে আমার মতে এটি মূল্যবান, যা আমার ব্যবহারের ক্ষেত্রে গুরুত্বপূর্ণ তবে ওয়াইএমএমভি।
স্ক্যানি

4

এখানে একটি callable যে একটি ঐচ্ছিক সময়সীমার যুক্তি লাগে, এর __call__আপনার কাছে সময় অনুলিপি পারে __name__, __doc__, __module__func এর নামস্থান থেকে:

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

propertyএকটি বর্গ। নির্ভুল হতে একটি বর্ণনাকারী । কেবল এটি থেকে উদ্ভূত এবং পছন্দসই আচরণ বাস্তবায়ন করুন।

class lazyproperty(property):
   ....

class testA(object):
   ....
  a = lazyproperty('_a')
  b = lazyproperty('_b')

3

আপনি কি সত্যিই চান reify(! সোর্স লিঙ্ক) পিরামিড থেকে প্রসাধক:

একটি শ্রেণিবদ্ধ সাজসজ্জা হিসাবে ব্যবহার করুন। এটি পাইথন @propertyডেকরেটারের মতো প্রায় পরিচালনা করে , তবে এটি প্রথম কলের পরে উদাহরণ পদ্ধতিতে এটি যে পদ্ধতিতে সজ্জিত হয় তার ফলাফলটি কার্যকরভাবে কার্যকরভাবে ফাংশনটির পরিবর্তে উদাহরণস্বরূপ ভেরিয়েবলের সাথে প্রতিস্থাপন করে। এটি পাইথন পার্লেন্সে, একটি অ-ডেটা বর্ণনাকারী। নিম্নলিখিতটি একটি উদাহরণ এবং এর ব্যবহার:

>>> 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
খুব ভাল, আমার যা প্রয়োজন ঠিক তা ঠিক করে দেয় ... যদিও পিরামিড এক সজ্জাকারীর জন্য বড় নির্ভরতা হতে পারে:)
অবশেষে

@ তাত্ক্ষণিকভাবে সাজসজ্জার বাস্তবায়ন সহজ, এবং আপনি নিজেই এটি প্রয়োগ করতে পারেন, pyramidনির্ভরতার প্রয়োজন নেই ।
পিটার উড

অতএব লিঙ্কটি "উত্স সংযুক্ত" বলে: ডি
আন্টি হাপাল

অ্যান্টিহাপালা আমি লক্ষ্য করেছি, তবে আমি ভেবেছিলাম যে আমি লিখিতভাবে অনুসরণ করি না তাদের জন্য এটি কার্যকর করা সহজ highlight
পিটার উড

1

প্রশ্ন এবং উত্তর এ পর্যন্ত উভয় শর্তাবলী এবং / বা ধারণার বিভ্রান্তির মিশ্রণ রয়েছে।

অলস মূল্যায়নের অর্থ হ'ল রান-টাইমে কোনও সম্ভাব্য মুহুর্তে যখন কোনও মূল্য প্রয়োজন হয় তখন কোনও কিছু মূল্যায়ন করা হয়। স্ট্যান্ডার্ড @propertyসাজসজ্জাটি ঠিক তা করে। (*) সজ্জিত ফাংশনটি কেবল মূল্যায়ন করা হয় এবং প্রতিবার আপনার সেই সম্পত্তিটির মূল্য প্রয়োজন। (অলস মূল্যায়ন সম্পর্কে উইকিপিডিয়া নিবন্ধ দেখুন)

(*) প্রকৃতপক্ষে সত্যিকারের অলস মূল্যায়ন (তুলনা করুন হ্যাশেল) অজগর (এবং কোডের ফলাফল যা মূর্তিমানের থেকে অনেক দূরে) অর্জন করা খুব কঠিন।

মেমোইজেশন হ'ল প্রশ্নকর্তাকে যা খুঁজছে বলে মনে হচ্ছে তার সঠিক শব্দ। বিশুদ্ধ ফাংশন যে ফেরত মান মূল্যায়নের জন্য পার্শ্ব প্রতিক্রিয়া উপর নির্ভর করে না এবং নিরাপদভাবে memoized করা যেতে পারে এবং সেখানে আসলে একটি প্রসাধক হয় functools @functools.lru_cache তাই নিজের টেকনিক লেখা যদি না আপনি আচরণ বিশেষ প্রয়োজন জন্য কোন প্রয়োজন।


আমি "অলস" শব্দটি ব্যবহার করেছি কারণ আসল বাস্তবায়নের সময়, সদস্যটি ডিবি থেকে বস্তুর সূচনা করার সময় গণনা করা / পুনরুদ্ধার করা হয়েছিল, এবং সম্পত্তিটি আসলে কোনও টেম্পলেটে ব্যবহৃত না হওয়া পর্যন্ত আমি সেই গণনা স্থগিত করতে চাই। এটি আমার কাছে অলসতার সংজ্ঞাটির সাথে মেলে বলে মনে হয়েছিল। আমি সম্মত হই যেহেতু আমার প্রশ্নটি ইতিমধ্যে একটি সমাধান গ্রহণ করে এটি ব্যবহার করে @property, "অলস" সেই মুহুর্তে খুব একটা বোঝায় না। (আমি ক্যাশেড আউটপুটগুলির ইনপুটগুলির মানচিত্র হিসাবে স্মৃতিচারণের কথাও ভেবেছিলাম এবং যেহেতু এই বৈশিষ্ট্যগুলির কেবল একটি ইনপুট রয়েছে, কিছুই নেই, একটি মানচিত্র প্রয়োজনের চেয়ে আরও জটিলতার মতো মনে হয়েছিল))
অবধি

নোট করুন যে লোকেরা "বাক্সের বাইরে" সমাধান হিসাবে প্রস্তাবিত সমস্ত আলংকারীর উপস্থিতি উপস্থিত ছিল না আমি যখন এটি জিজ্ঞাসা করি তখনও।
বিশদভাবে

আমি জেসনের সাথে একমত, এটি ক্যাশে / স্মৃতিচারণ সম্পর্কে অলস মূল্যায়ন নয়।
poindexter

@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
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.