Внутренние вызовы Python кеша, используя технику, похожую на фиктивный патч - PullRequest
3 голосов
/ 27 марта 2019

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

@cache_patch('lib.Someobjclass.func1',ttl=200)
@cache_patch('lib.Someotherobjclass.func2',ttl=1000)
function abc(*args, **kwargs):
    '''do stuff'''
    cache1 = someobj.func1(args,kwargs)
    '''do more stuff'''
    cache2 = someotherobj.func2(args,kwargs)

Можно ли использовать какую-либо библиотеку или технику?

1 Ответ

0 голосов
/ 11 апреля 2019

Предположение

Мне не на 100% ясно, какое именно поведение вы хотите, поэтому я предполагаю, что кэширование должно использоваться только во время выполнения функции, к которой применяется декоратор.


Необходимые биты и кусочки

Вам необходимо

  • использовать functools.lru_cache для реализации кэширования
  • создать декоратор, способныйпринять дополнительные аргументы
  • использовать importlib для импорта класса, указанного в качестве первого аргумента, в декоратор из строки
  • monkey patch нужного метода с кэшированной версией

Собираем все вместе

import importlib
from functools import lru_cache


class Addition:
    def __init__(self, a):
        self.a = a

    def uncached_addition(self, b):
        # print is only used to demonstrate if the method is actually called or not
        print(f"Computing {self.a} + {b}")
        return self.a + b


class cache_patch:
    def __init__(self, method_as_str, ttl):
        # split the given path into module, class and method name
        class_as_str, method_name = method_as_str.rsplit(".", 1)
        module_path, class_name = class_as_str.rsplit(".", 1)

        self.clazz = getattr(importlib.import_module(module_path), class_name)
        self.method_name = method_name
        self.ttl = ttl

    def __call__(self, func):
        def wrapped(*args, **kwargs):
            # monkey patch the original method with a cached version
            uncached_method = getattr(self.clazz, self.method_name)
            cached_method = lru_cache(maxsize=self.ttl)(uncached_method)
            setattr(self.clazz, self.method_name, cached_method)

            result = func(*args, **kwargs)

            # replace cached method with original
            setattr(self.clazz, self.method_name, uncached_method)
            return result
        return wrapped


@cache_patch('__main__.Addition.uncached_addition', ttl=128)
def perform_patched_uncached_addition(a, b):
    d = Addition(a=1)
    print("Patched nr. 1\t", d.uncached_addition(2))
    print("Patched nr. 2\t", d.uncached_addition(2))
    print("Patched nr. 3\t", d.uncached_addition(2))
    print()


if __name__ == '__main__':
    perform_patched_uncached_addition(1, 2)
    d = Addition(a=1)
    print("Unpatched nr. 1\t", d.uncached_addition(2))
    print("Unpatched nr. 2\t", d.uncached_addition(2))
    print("Unpatched nr. 3\t", d.uncached_addition(2))

Результат

Как видно из вывода, вызов perform_patched_uncached_addition будет выводить Computing 1 + 2 только один раз, после чего кэшированный результат будетused:

Computing 1 + 2
Patched call nr. 1:  3
Patched call nr. 2:  3
Patched call nr. 3:  3

Любые вызовы, сделанные в класс вне этой функции, будут использовать непатчированную не кэшированную версию метода:

Computing 1 + 2
Unpatched call nr. 1:    3
Computing 1 + 2
Unpatched call nr. 2:    3
Computing 1 + 2
Unpatched call nr. 3:    3

Предостережения

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


Примечание

В отличие от кода выше functools.lru_cache используется в качестве декоратора в большинстве случаев:


class Addition:
    def __init__(self, a):
        self.a = a

    @lru_cache(maxsize=128)
    def cached_addition(self, b):
        print(f"Computing {self.a} + b")
        return self.a + b
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...