3

The goal is to create a custom python class, that allows for lazyloading of attributes (a fairly common question here with a variety of different solutions) but that only runs the expensive initialization step once ON lazy access. Here's an example:

class LazyDateModule()
    def __init__(self, args):
        self.args = args
        self._expensive_date_attr = None

    def _expensive_init():
        time.sleep(10)
        self._expensive_date_attr = date.today()

    @memoize
    def date_str():
        return str(self._expensive_date_attr)

    @memoize
    def month_num():
        return self._expensive_date_attr.month

In this example, my @memoize decorator handles the caching step for me.

If I'm defining my LazyDateModule elsewhere:

LDM = LazyDateModule()

How do I run the _expensive_init() ONLY on the first time any of the memoized attribute methods were accessed? I've tried something like this:

class LazyDateModule()
    def __init__(self, args):
        self.args = args
        self._assembled = False
        self._expensive_date_attr = None

    def _expensive_init():
        time.sleep(10)
        self._expensive_date_attr = date.today()
        self._assembled = True

    @memoize
    def date_str():
        if self._assembled is False:
            self._expensive_init()
        return str(self._expensive_date_attr)

    @memoize
    def month_num():
        if self._assembled is False:
            self._expensive_init()
        return self._expensive_date_attr.month

But that's not very clean. Ideally I'd like this behavior to either be at a class-level—but I've struggled in my attempts to overwrite __getattr__ or __getattribute__. Another decorator would also work.

I'm sorry if the above was confusing. Let me know if I can clarify it in any way!

EDIT 1: I think the above example is a little too simple. Let's say my _expensive_init() does a bunch of stuff, as opposed to just defining a single attribute.

def _expensive_init(self):
    time.sleep(5)
    self._expensive_date_attr = date.today()

    time.sleep(1)
    # send a message to someone saying that this occurred

    time.sleep(1)
    # run a db call to update when this action occurred

    etc...

Ideally this method would handle a variety of different behaviors.

1
  • I wrote a similar module. I decided not to finish it. It worked in two steps. In the first step a simple method decorator registers every method to be memoized by adding an attribute to the decorated function itself. The class itself does not exist at that time, so this is all it can be done. In the second step a class decorator checks all methods for that added attribute, creates a table, removes those methods from the class, and installs a custom __getattr__ which catches the first access to names in the table, calls the function and adds the result as a regular attribute. Hope it helps. Commented Apr 24, 2017 at 19:16

1 Answer 1

2

You have a caching decorator memoize, so why not use it to cache your _expensive_date_attr ? and BTW turn it into a property:

class LazyDateModule():
    def __init__(self, args):
        self.args = args
        self._assembled = False

    @memoize
    @property
    def _expensive_date_attr(self):
        return date.today

    @memoize
    def date_str():
        return str(self._expensive_date_attr)

    @memoize
    def month_num():
        return self._expensive_date_attr.month
Sign up to request clarification or add additional context in comments.

6 Comments

Is there a reason why date_str and month_num should be memoized as well?
No other reason than the fact that they were memoized in your code
This is clean, but unfortunately I don't think this will work for what I'm envisioning. The example was too simple in this regard. Check out my most recent changes.
Assuming your memoize decorator does the obvious thing (that is, memorizing a method result on first call and returning it directly on next calls), I don't understand why you think it would not work for your edit. If needed, you may use functools.lru_cache to achieve that effect
Also, note that the _expensive_date_attr() method does not set any attribute, but is a property by itself
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.