2016-07-06 3 views
3

У меня есть программа с объектом формыинтернируя неизменные объекты

class MyObj(object): 
    def __init__(self, a, b): 
     self.__a = a 
     self.__b = b 
     self.cache = very_expensive_computation(a, b) 

a,b, где неизменен и пространством возможных параметров a,b очень малы. Эти объекты создаются и выходят за рамки постоянно во время выполнения программы, поэтому неудивительно много времени потрачено на пересчет self.cache для тех же значений a,b. Поскольку very_expensive_computation стоит очень дорого, кажется, было бы лучше просто избегать сбора мусора этих элементов и, если возможно, конструктора возвращать ссылки на уже существующие объекты, вроде как интернирование строк.

Очевидный способ сделать это мне кажется, чтобы добавить словарь в класс и переопределить __new__ и __init__ так, что они проверяют словарь и возвращать уже существующие экземпляры, если это возможно, но в то же время это чувствует своего рода неудовлетворительно так как это нужно было бы сделать для каждого класса отдельно, и с момента обнаружения того, являетесь ли вы фактическим новым объектом в __init__, вероятно, будет довольно хаки.

Любые другие предложения?

+0

Я хотел бы рассмотреть только кэширование 'very_expensive_computation', или добавление метода класса, который может вернуть сохраненные экземпляры вместо того, чтобы' MyObj() 'вернуть старый экземпляров. Выполнение 'MyObj()' возвращенных сохраненных экземпляров может стать беспорядочным. – user2357112

ответ

4

Я memoize very_expensive_computation хранения результатов в кэш LRU, чтобы гарантировать верхний предел на количество используемой памяти:

_very_expensive_computation_cache = RecentlyUsedContainer(100) 

def cached_very_expensive_computation(a, b): 
    if (a, b) not in _very_expensive_computation_cache: 
     _very_expensive_computation_cache[(a, b)] = very_expensive_computation(a, b) 
    return _very_expensive_computation_cache[(a, b)] 

Где RecentlyUsedContainer может быть такой: https://github.com/shazow/unstdlib.py/blob/master/unstdlib/standard/collections_.py#L12

Вы может также упростить код с декоратора:

from unstdlib.standard.functools_ import memoized 

@memoized(cache=RecentlyUsedContainer(100)) 
def cached_very_expensive_comptuation(a, b): 
    return very_expensive_computation(a, b) 

См: https://github.com/shazow/unstdlib.py/blob/master/unstdlib/standard/functools_.py#L59

Я предпочитаю хранить сохраненные в памяти версии функций отдельно от «реальных» версий, поэтому вызывающие могут явно видеть, что они могут получать кешированный результат, и это может облегчить тестирование. Это, в основном, личное предпочтение.

Редактировать: как указано в комментариях, Python 3 поставляется с functools.lru_cache.

+1

Учитывая, что вопрос отмечен Python 3, если вы хотите кеш LRU, вы можете просто использовать 'functools.lru_cache' из стандартной библиотеки. – user2357112

+1

Фактическая программа выполняет справедливую проверку в '__init__', поэтому я был обеспокоен тем, что просто memoizing' very_expensive_comput' не хватит, но после попытки я получил примерно 12-кратное ускорение, чего более чем достаточно. – dmtr

2

Вы также можете сделать это с переменным класса и экземпляра:

class BaseMyObj(object): 
    result_store = {} 

class MyObj(BaseMyObj): 
    def __init__(self, a, b): 
     self.__a = a 
     self.__b = b 
     try: 
      self.cache = BaseMyObj.result_store[(a,b)] 
     except KeyError: 
      self.cache = very_expensive_computation(a, b) 
      BaseMyObj.result_store[(a,b)] = self.cache 

def very_expensive_computation(a, b): 
    print('did calc') 
    return a+b 

item = MyObj(2, 9) 
>did calc 

item2 = MyObj(2, 9) 

item3 = MyObj(1, 4) 
>did calc 
+0

Что для этого нужно в BaseMyObj? Почему бы просто не хранить кеш на «MyObj» или на уровне модуля? – user2357112

+0

В случае, если 'very_expensive_function' не ограничивается этим типом экземпляра. В своем примере кода он назвал 'very_expensive_function', а не' self.very_expensive_function', поэтому я не был уверен. Теперь его можно унаследовать тем, что ему нужно. – Jeff

+0

Зачем вам использовать * наследование * для этого? – user2357112

Смежные вопросы