Python Memoising/Deferred Lookup Property Decorator

Python memoising/deferred lookup property decorator

For all sorts of great utilities I'm using boltons.

As part of that library you have cachedproperty:

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)

Applying a decorator to a property getter

prop = property(memoized(prop.fget), prop.fset, prop.fdel, prop.__doc__)

Don't modify the property; make a new one.

Lazy class property decorator

Pyramid framework has a very nice decorator called reify, but it only works at instance level, and you want class level, so let's modify it a bit

class class_reify(object):
def __init__(self, wrapped):
self.wrapped = wrapped
try:
self.__doc__ = wrapped.__doc__
except: # pragma: no cover
pass

# original sets the attributes on the instance
# def __get__(self, inst, objtype=None):
# if inst is None:
# return self
# val = self.wrapped(inst)
# setattr(inst, self.wrapped.__name__, val)
# return val

# ignore the instance, and just set them on the class
# if called on a class, inst is None and objtype is the class
# if called on an instance, inst is the instance, and objtype
# the class
def __get__(self, inst, objtype=None):
# ask the value from the wrapped object, giving it
# our class
val = self.wrapped(objtype)

# and set the attribute directly to the class, thereby
# avoiding the descriptor to be called multiple times
setattr(objtype, self.wrapped.__name__, val)

# and return the calculated value
return val

class Test(object):
@class_reify
def foo(cls):
print("foo called for class", cls)
return 42

print(Test.foo)
print(Test.foo)

Run the program and it prints

foo called for class <class '__main__.Test'>
42
42

How to create decorator for lazy initialization of a property

Denis Otkidach's CachedAttribute is a method decorator which makes attributes lazy (computed once, accessible many). To make it also read-only, I added a __set__ method. To retain the ability to recalculate (see below) I added a __delete__ method:

class ReadOnlyCachedAttribute(object):    
'''Computes attribute value and caches it in the instance.
Source: Python Cookbook
Author: Denis Otkidach https://stackoverflow.com/users/168352/denis-otkidach
This decorator allows you to create a property which can be computed once and
accessed many times. Sort of like memoization
'''
def __init__(self, method, name=None):
self.method = method
self.name = name or method.__name__
self.__doc__ = method.__doc__
def __get__(self, inst, cls):
if inst is None:
return self
elif self.name in inst.__dict__:
return inst.__dict__[self.name]
else:
result = self.method(inst)
inst.__dict__[self.name]=result
return result
def __set__(self, inst, value):
raise AttributeError("This property is read-only")
def __delete__(self,inst):
del inst.__dict__[self.name]

For example:

if __name__=='__main__':
class Foo(object):
@ReadOnlyCachedAttribute
# @read_only_lazyprop
def bar(self):
print 'Calculating self.bar'
return 42
foo=Foo()
print(foo.bar)
# Calculating self.bar
# 42
print(foo.bar)
# 42
try:
foo.bar=1
except AttributeError as err:
print(err)
# This property is read-only
del(foo.bar)
print(foo.bar)
# Calculating self.bar
# 42

One of the beautiful things about CachedAttribute (and
ReadOnlyCachedAttribute) is that if you del foo.bar, then the next time you
access foo.bar, the value is re-calculated. (This magic is made possible by
the fact that del foo.bar removes 'bar' from foo.__dict__ but the property
bar remains in Foo.__dict__.)

If you don't need or don't want this ability to recalculate,
then the following (based on Mike Boers' lazyprop) is a simpler way to make a read-only lazy property.

def read_only_lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
@property
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
@_lazyprop.setter
def _lazyprop(self,value):
raise AttributeError("This property is read-only")
return _lazyprop

Accessor decorator that only runs decorated code once in Typescript, returns previously calculated value on subsequent calls

I believe we can compute the value the first time and store it, and then retrieve it on subsequent calls using reflect-metadata as follows.

import "reflect-metadata";

const metadataKey = Symbol("initialized");

function once(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const getter = descriptor.get!;
descriptor.get = function () {
const val = Reflect.getMetadata(metadataKey, target, propertyKey);

if (val) {
return val;
}

const newValue = getter.apply(this);
Reflect.defineMetadata(metadataKey, newValue, target, propertyKey);
return newValue;
};
}

A working example can be found here.

Big thanks to OP for helping to get the kinks worked out.



Related Topics



Leave a reply



Submit