Path: csiph.com!usenet.pasdenom.info!gegeweb.org!de-l.enfer-du-nord.net!feeder1.enfer-du-nord.net!feeds.phibee-telecom.net!newsfeed.xs4all.nl!newsfeed5.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.001 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'attribute': 0.05; 'cache': 0.05; 'try:': 0.07; 'welcome.': 0.07; 'wrapper': 0.07; 'python': 0.09; '**kwargs)': 0.09; '**kwargs):': 0.09; '@property': 0.09; 'descriptor': 0.09; 'def': 0.10; 'attribute,': 0.16; 'class:': 0.16; 'email addr:functools.wraps(func)': 0.16; 'foo()': 0.16; 'lazily': 0.16; 'wrote:': 0.17; 'replacing': 0.17; 'thu,': 0.17; '>>>': 0.18; 'keyerror:': 0.22; 'example': 0.23; 'programming': 0.23; '15,': 0.23; 'header:In-Reply-To:1': 0.25; 'message- id:@mail.gmail.com': 0.27; 'post': 0.28; 'email name:': 0.29; 'faster,': 0.29; 'once.': 0.29; 'skip:_ 10': 0.29; 'probably': 0.29; 'class': 0.29; 'that.': 0.30; 'usually': 0.30; 'url:2012': 0.30; 'function': 0.30; 'comments': 0.33; 'to:addr:python-list': 0.33; 'that,': 0.34; 'received:google.com': 0.34; 'nov': 0.35; 'pm,': 0.35; 'received:209.85': 0.35; 'add': 0.36; 'except': 0.36; 'should': 0.36; 'being': 0.37; 'received:209': 0.37; 'subject:: ': 0.38; 'object': 0.38; 'some': 0.38; 'shows': 0.38; 'to:addr:python.org': 0.39; 'header:Received:5': 0.40; 'your': 0.60; 'easy': 0.60; 'url:blogspot': 0.64; 'taking': 0.65; 'url:11': 0.71; 'entry,': 0.84; 'to:name:python': 0.84; 'demand': 0.96 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :content-type; bh=zHprNeRcFosEWcIuhiWGTZ4Hzt9jO5MDyXOyFD84fKk=; b=DkXvK8jrFBGDYR4otowD5dYpdgWTu2hhkAUBNTKu2SM+VzP5N9UXeFbsfeBJfRdSGb GcAX7dQ5VadTFXoM1LwSLER26I2vxdCbpSd2wjhtNmDvqW5zeov+Xa/maH1S6NclQi5C 0zCtO/PMz1PPfsHPL4IZ7HQq4Aajf1QW2irxmKKDZL/Isv4NeLYZ4jMN3fWDIKa0TCPz 4qzB/dfzQCVsc/xvnh7SVAL9rdm7b1v3EvM1NvGT/7YSNOn4CKqfWgDotUsdkKtO0c3L huXKnsub47cm5ahBEbxoC5HHFl5VZPMMeRMuWcbTYoDhoHwNO1ppOlsratUiMiaPMoNp BMTQ== MIME-Version: 1.0 In-Reply-To: References: From: Ian Kelly Date: Thu, 15 Nov 2012 15:46:19 -0700 Subject: Re: Lazy Attribute To: Python Content-Type: text/plain; charset=ISO-8859-1 X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 73 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1353019611 news.xs4all.nl 6946 [2001:888:2000:d::a6]:56500 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:33403 On Thu, Nov 15, 2012 at 12:33 PM, Andriy Kornatskyy wrote: > > A lazy attribute is an attribute that is calculated on demand and only once. > > The post below shows how you can use lazy attribute in your Python class: > > http://mindref.blogspot.com/2012/11/python-lazy-attribute.html > > Comments or suggestions are welcome. I should add that I like the approach you're taking here. Usually when I want a lazy property I just make an ordinary property of a memoized function call: def memoize(func): cache = {} @functools.wraps(func) def wrapper(*args, **kwargs): kwset = frozenset(kwargs.items()) try: return cache[args, kwset] except KeyError: result = cache[args, kwset] = func(*args, **kwargs) return result return wrapper class Foo: def __init__(self): self.times_called = 0 @property @memoize # Alternatively, use functools.lru_cache def forty_two(self): self.times_called += 1 return 6 * 9 >>> foo = Foo() >>> foo.times_called 0 >>> foo.forty_two 54 >>> foo.times_called 1 >>> foo.forty_two 54 >>> foo.times_called 1 Although you don't go into it in the blog entry, what I like about your approach of replacing the descriptor with an attribute is that, in addition to being faster, it makes it easy to force the object to lazily reevaluate the attribute, just by deleting it. Using the Person example from your blog post: >>> p = Person('John', 'Smith') >>> p.display_name 'John Smith' >>> p.display_name 'John Smith' >>> p.calls_count 1 >>> p.first_name = 'Eliza' >>> del p.display_name >>> p.display_name 'Eliza Smith' >>> p.calls_count 2 Although in general it's probably better to use some form of reactive programming for that.