Reputation: 1034
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?
Upvotes: 2
Views: 3367
Reputation: 229
Assuming that you want to use the calling pattern you present: You want "property" attributes of instances which, when fetched, return information for the customer identified by self.name
. But you want that customer information to be cached on a class-wide basis, so if you create two instances with the same self.name
, work isn't duplicated between the two instances.
It seems to me that what you want is a "property" attribute whose fget function calls a class method, and the class method caches its results.
This code implements that pattern
import functools
class Customer:
def __init__(self, name):
self.name = name
@property
def info(self):
print('retrieving info 1 for', self.name)
return Customer.info_implement(self.name)
@classmethod
@functools.cache
def info_implement(cls, name):
print('retrieving info 2 for', name)
return "info for " + name
customer1 = Customer('John')
print("customer1 info", customer1.info)
print("customer1 info", customer1.info)
customer2 = Customer('John')
print("customer2 info", customer2.info)
print("customer2 info", customer2.info)
and produces this output
retrieving info 1 for John
retrieving info 2 for John
customer1 info info for John
retrieving info 1 for John
customer1 info info for John
retrieving info 1 for John
customer2 info info for John
retrieving info 1 for John
customer2 info info for John
Upvotes: 0
Reputation: 43166
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
Upvotes: 2