Какова точка паттерна «часовой объект» в Python - PullRequest
0 голосов
/ 08 апреля 2020

Я недавно узнал о паттерне "объект стража" в python. Я был взят им и начал использовать его везде, где мог. Однако, после того, как я использовал его где-то там, где он не нужен, сотрудник спросил меня об этом. Теперь я не вижу его использования, учитывая, что "x in dict" существует. Вот (усеченный) канонический пример из библиотеки кеша functools LRU:

def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
    # Constants shared by all lru cache instances:
    sentinel = object()          # unique object used to signal cache misses
    make_key = _make_key         # build a key from the function arguments
    PREV, NEXT, KEY, RESULT = 0, 1, 2, 3   # names for the link fields

    cache = {}
    hits = misses = 0
    full = False
    cache_get = cache.get    # bound method to lookup a key or return None
    cache_len = cache.__len__  # get cache size without calling len()
    lock = RLock()           # because linkedlist updates aren't threadsafe
    root = []                # root of the circular doubly linked list
    root[:] = [root, root, None, None]     # initialize by pointing to self

    if maxsize == 0:

        def wrapper(*args, **kwds):
            # No caching -- just a statistics update after a successful call
            nonlocal misses
            result = user_function(*args, **kwds)
            misses += 1
            return result

    elif maxsize is None:

        def wrapper(*args, **kwds):
            # Simple caching without ordering or size limit
            nonlocal hits, misses
            key = make_key(args, kwds, typed)
            result = cache_get(key, sentinel)
            if result is not sentinel:
                hits += 1
                return result
            result = user_function(*args, **kwds)
            cache[key] = result
            misses += 1
            return result

Теперь просто сосредоточимся на той части, где используется шаблон:

            result = cache_get(key, sentinel)                    
            if result is not sentinel:                           
                hits += 1                                        
                return result                                    
            result = user_function(*args, **kwds)                
            cache[key] = result                                  
            misses += 1                                          
            return result 

Насколько Я могу сказать, это можно переписать следующим образом:

            if key not in cache:
                result = user_function(*args, **kwds)
                cache[key] = result
                misses += 1
            else:
                result = cache_get(key)
                hits += 1
            return result

Я задавался вопросом: в чем преимущество этого дозорного метода? Я думал, что это может быть эффективность. Вики python говорят, что "x in s" - это среднее значение O (n), а элемент get - O (1) среднее значение. Но действительно ли это имеет практическую разницу во времени?

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

В ответ на @martineau Я не думаю, что есть какая-то дополнительная функциональность, которую мы получаем из этого шаблона, как продемонстрировано в этом интерактивном сеансе:

>>> d={1:None}
>>> if 1 in d:
...     print('one is there')
... 
one is there
>>> if 2 in d:
...     print('two is not')
... 
>>> d={1:None,None:3}
>>> if None in d:
...     print('we can find a none key as well')
... 
we can find a none key as well

Итак, остается вопрос: в чем смысл этого шаблона?

1 Ответ

2 голосов
/ 09 апреля 2020

В показанном вами коде использование dict.get со значением часового является небольшой оптимизацией для случая, когда ключ действительно существует в словаре. В этом случае вам нужно всего лишь go пройти через процесс хеширования и поиска ключа один раз, в вызове get, а не в два раза больше, чем нужно в if key in dict: value = dict[key] эквиваленте.

Это не измените сложность вычислений, поскольку индексирование словаря и тестирование членства являются O(1), но даже небольшие улучшения производительности могут быть важны, если они находятся в «горячем» коде, который запускается очень часто. И именно в таких случаях запоминание, подобное предоставленному вами коду, наиболее полезно!

Есть некоторые другие довольно распространенные микрооптимизации, которые вы можете увидеть в некотором коде Python в стандартной библиотеке. Ваш пример содержит еще один, сохраняя связанный метод (cache.get) в локальной переменной (cache_get). Это позволяет коду избегать повторного связывания метода каждый раз, когда это необходимо, что включает в себя индексирование в словари экземпляров и классов и создание объекта связанного метода.

...