How to cache a property at class level in python?

Viewed 1962

So I am working on a Customer class that is supposed to be wrapper for some other classes that retrieve information about a specific customer from a server and from online, as below.

class Customer:
    def __init__(self, name):
        self.name = name

    @property
    @lru_cache()
    def online_info(self):
       print('retrieving customer online info')
       return Online().search_result(for=self)

    @property
    @lru_cache()
    def server_info(self):
      print('retrieving customer server info')
      return Server().search_result(for=self)

The online and server calls have to be @property decorated. The problem I am facing is when trying to cache the online_info and server_info calls. The cache would somehow have to be at a class level so that even if a news customer is instantiated, the lru_cache wold remember previous calls from other instantiations for the same name call. Note my print statements. This is the behavious I am trying to achieve:

>>> cutomer1 = Customer('John')
>>> customer1.online_info
retrieving customer online info
John Smith has the following info bla bla bla ....

>>> cutomer2 = Customer('John')
>>> customer2.online_info # this one will not be calling the function, lru_cache will return the value saved from customer1.online_info
John Smith has the following info bla bla bla ....

Can someone explain how I achieve this behaviour? Is this possible?

1 Answers

Instead of caching the property values on the class, I would recommend re-using the same Customer instance for each "John", so that

>>> Customer('John') is Customer('John')
True

This would make Customer a singleton of sorts. Singleton implementations can be found aplenty in this question: Creating a singleton in Python. Borrowing one of those implementations gives us a pseudo-singleton metaclass like this:

class NameSingleton(type):
    def __init__(cls, *args, **kwargs):
        cls._instances = {}

    def __call__(cls, name, *args, **kwargs):
        try:
            return cls._instances[name]
        except KeyError:
            instance = super().__call__(name, *args, **kwargs)
            cls._instances[name] = instance
            return instance

Use this as the metaclass for Customer and you're done:

class Customer(metaclass=NameSingleton):
    def __init__(self, name):
        self.name = name

    ...

Demo:

>>> Customer('John') is Customer('John')
True
>>> Customer('John') is Customer('not John')
False
Related