Skip to content

Instantly share code, notes, and snippets.

@wonderbeyond
Last active March 23, 2023 14:12
Show Gist options
  • Save wonderbeyond/df700e559f7114822d15 to your computer and use it in GitHub Desktop.
Save wonderbeyond/df700e559f7114822d15 to your computer and use it in GitHub Desktop.
lazy property implementation in python
"""
See: https://segmentfault.com/a/1190000005818249
(A better implementation: django.utils.functional.cached_property)
Usage:
class Circle(object):
def __init__(self, radius):
self.radius = radius
@lazy_property
def area(self):
print 'evalute'
return 3.14 * self.radius**2
"""
class lazy_property(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, instance, cls):
value = self.fget(instance)
setattr(instance, self.fget.__name__, value)
return value
"""
See also: sqlalchemy.util.langhelpers.memoized_property
"""
# Property name to hold all lazy data
_data_holder_attr = '_lazy_properties'
def clean_lazy_proterties(instance):
'''Clean all lazy properties'''
setattr(instance, _data_holder_attr, {})
class lazy_property(object):
"""lazy property decorator, just like built-in `property`"""
def __init__(self, fget):
self.fget = fget
self.property_name = fget.__name__
def get_lazy_data(self, instance):
if not hasattr(instance, _data_holder_attr):
setattr(instance, _data_holder_attr, {})
return getattr(instance, _data_holder_attr)
def __get__(self, instance, owner):
if instance is None:
return None
lazy_data = self.get_lazy_data(instance)
if self.property_name in lazy_data:
return lazy_data[self.property_name]
value = self.fget(instance)
lazy_data[self.property_name] = value
return value
if __name__ == '__main__':
class Test(object):
'''Test case for lazy_property'''
@lazy_property
def somenum(self):
import random
return random.randrange(0, 1000)
t = Test()
print(t.somenum, t.somenum, t.somenum) # print a random number three times
clean_lazy_proterties(t)
print(t.somenum, t.somenum, t.somenum) # print another random number three times
@gyli
Copy link

gyli commented Mar 20, 2023

I think the functionality of the versions are not exactly the same. The first one (latest one) is more like Python's built-in property, that will always be evaluated when getting value. The property in second one will only be evaluated once and then stored in cache, like cached_property. So the meaning of "lazy" is not clear and different in two versions.

@wonderbeyond
Copy link
Author

wonderbeyond commented Mar 23, 2023

@gyli The first one (latest one) is more like Python's built-in property, that will always be evaluated when getting value

Have you noticed this line in latest implemention?

setattr(instance, self.fget.__name__, value)

Which overrides the property with a direct attribute, so latter references with the same prop name don't even bother the "lazy_property" getter.

@gyli
Copy link

gyli commented Mar 23, 2023

Ah! You are right, __get__ will not be triggered when getting attribute value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment