Модуль, по-видимому, повторно импортирован в запомненную функцию Python

Я пишу утилиту командной строки Python, которая включает преобразование строки в TextBlob, который является частью модуля обработки естественного языка. Импортирование модуля очень медленное, ~300 мс в моей системе. Для ускорения я создал запомненную функцию, которая преобразует текст в TextBlob только при первом вызове функции. Важно отметить, что если я запускаю свой сценарий дважды для одного и того же текста, я хочу избежать повторного импорта TextBlob и повторного вычисления большого двоичного объекта, вместо этого извлекая его из кэша. Это все сделано и работает нормально, за исключением того, что по какой-то причине функция все еще очень медленная. На самом деле, это так же медленно, как и раньше. Я думаю, что это должно быть из-за того, что модуль повторно импортируется, даже если функция запоминается, а оператор import выполняется внутри функции memoized.

Цель здесь состоит в том, чтобы исправить следующий код, чтобы запомненные прогоны были настолько быстрыми, насколько они должны быть, учитывая, что результат не нужно пересчитывать.

Вот минимальный пример кода ядра:

@memoize
def make_blob(text):
     import textblob
     return textblob.TextBlob(text)


if __name__ == '__main__':
    make_blob("hello")

А вот декоратор памятки:

import os
import shelve
import functools
import inspect


def memoize(f):
    """Cache results of computations on disk in a directory called 'cache'."""
    path_of_this_file = os.path.dirname(os.path.realpath(__file__))
    cache_dirname = os.path.join(path_of_this_file, "cache")

    if not os.path.isdir(cache_dirname):
        os.mkdir(cache_dirname)

    cache_filename = f.__module__ + "." + f.__name__
    cachepath = os.path.join(cache_dirname, cache_filename)

    try:
        cache = shelve.open(cachepath, protocol=2)
    except:
        print 'Could not open cache file %s, maybe name collision' % cachepath
        cache = None

    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        argdict = {}

        # handle instance methods
        if hasattr(f, '__self__'):
            args = args[1:]

        tempargdict = inspect.getcallargs(f, *args, **kwargs)

        for k, v in tempargdict.iteritems():
            argdict[k] = v

        key = str(hash(frozenset(argdict.items())))

        try:
            return cache[key]
        except KeyError:
            value = f(*args, **kwargs)
            cache[key] = value
            cache.sync()
            return value
        except TypeError:
            call_to = f.__module__ + '.' + f.__name__
            print ['Warning: could not disk cache call to ',
                   '%s; it probably has unhashable args'] % (call_to)
            return f(*args, **kwargs)

    return wrapped

И вот демонстрация того, что памятка в настоящее время не экономит время:

❯ time python test.py
python test.py  0.33s user 0.11s system 100% cpu 0.437 total

~/Desktop
❯ time python test.py
python test.py  0.33s user 0.11s system 100% cpu 0.436 total

Это происходит, даже если функция правильно запоминается (операторы print, помещенные в функцию memoized, выдают результат только при первом запуске скрипта).

Я собрал все вместе в GitHub Gist на случай, если это будет полезно.

1 ответ

Как насчет другого подхода:

import pickle

CACHE_FILE = 'cache.pkl'

try:
    with open(CACHE_FILE) as pkl:
        obj = pickle.load(pkl)
except:
    import slowmodule
    obj = "something"
    with open(CACHE_FILE, 'w') as pkl:
        pickle.dump(obj, pkl)

print obj

Здесь мы кешируем объект, а не модуль. Обратите внимание, что это не даст вам никакой экономии, если объект, который требует ваше кэширование slowmodule, Таким образом, в приведенном выше примере вы увидите экономию, так как "something" является строкой и не требует slowmodule Модуль, чтобы понять это. Но если вы сделали что-то вроде

obj = slowmodule.Foo("bar")

Процесс расщепления автоматически импортирует slowmodule, сводя на нет все преимущества кэширования.

Так что, если вы можете манипулировать textblob.TextBlob(text) во что-то, что, когда для unpickled не требуется модуль textblob, вы увидите экономию при использовании этого подхода.

Другие вопросы по тегам