Python에서 lru_cache의 사용과 실현에 대한 상세한 설명
다음은 간단한 예시를 통해 파이썬의 lru_cache는 어떻게 사용합니까?
def factorial(n):
print(f" {n} ")
return 1 if n <= 1 else n * factorial(n - 1)
a = factorial(5)
print(f'5! = {a}')
b = factorial(3)
print(f'3! = {b}')
위의 코드는 함수factorial을 정의하여 n의 곱셈을 계산하고 함수가 호출될 때 n의 값을 출력합니다.그리고 각각 5와 3의 곱셈을 계산하고 결과를 출력합니다.위의 코드를 실행하면 다음과 같이 출력합니다계산 5의 곱하기
계산 4의 곱하기
계산 3 의 계승
계산 2의 곱하기
계산 1의 곱하기
5! = 120
계산 3 의 계승
계산 2의 곱하기
계산 1의 곱하기
3! = 육
보시다시피factorial(3)의 결과는factorial(5)을 계산할 때 이미 계산되었지만 뒤에는 다시 계산되었다.이런 중복 계산을 피하기 위해서, 우리는 함수factorial을 정의할 때 lru_를 추가할 수 있다cache 장식기, 아래와 같이
import functools
# lru_cache ,
@functools.lru_cache()
def factorial(n):
print(f" {n} ")
return 1 if n <= 1 else n * factorial(n - 1)
코드를 다시 실행하려면 다음과 같이 입력하십시오.계산 5의 곱하기
계산 4의 곱하기
계산 3 의 계승
계산 2의 곱하기
계산 1의 곱하기
5! = 120
3! = 육
보시다시피 이번에factorial(3)을 호출할 때 해당하는 출력을 출력하지 않았습니다. 즉factorial(3)은 캐시에서 직접 읽은 결과로 캐시가 효력이 발생했음을 증명합니다.
피lru_che 수식된 함수는 같은 매개 변수에 호출될 때 후속 호출은 캐시에서 결과를 직접 읽는 것이지 실제 함수를 실행하지 않습니다.다음은 파이썬 내부에서 어떻게 lru_를 실현하는지 원본 코드를 깊이 있게 살펴보겠습니다.캐치의글을 쓸 때 파이썬의 최신 발행판은 3.9이기 때문에 여기에서 사용하는 것은 Python 3.9 소스 원본의 주석을 보존하고 있다.
def lru_cache(maxsize=128, typed=False):
"""Least-recently-used cache decorator.
If *maxsize* is set to None, the LRU features are disabled and the cache
can grow without bound.
If *typed* is True, arguments of different types will be cached separately.
For example, f(3.0) and f(3) will be treated as distinct calls with
distinct results.
Arguments to the cached function must be hashable.
View the cache statistics named tuple (hits, misses, maxsize, currsize)
with f.cache_info(). Clear the cache and statistics with f.cache_clear().
Access the underlying function with f.__wrapped__.
See: http://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
"""
# Users should only access the lru_cache through its public API:
# cache_info, cache_clear, and f.__wrapped__
# The internals of the lru_cache are encapsulated for thread safety and
# to allow the implementation to change (including a possible C version).
if isinstance(maxsize, int):
# Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0
elif callable(maxsize) and isinstance(typed, bool):
# The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
elif maxsize is not None:
raise TypeError(
'Expected first argument to be an integer, a callable, or None')
def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
return decorating_function
이 코드에는 다음과 같은 몇 가지 관건이 있다키워드 매개변수
maxsize는 캐시 용량을 나타냅니다. 만약에 None이 용량을 표시하는 데 제한이 없다면, typed는 매개 변수 형식을 구분하는지 여부를 나타냅니다. 주석에서도 설명합니다. 만약에 typed==True라면 f(3)와 f(3.0)는 서로 다른 함수 호출로 여겨집니다.
24행 기준 분기
하면, 만약, 만약...cache의 첫 번째 매개 변수는 호출 가능합니다. wrapper로 바로 돌아갑니다. 즉, lru_cache는 파라미터가 없는 장식기로서 이것은 Python 3.8에서만 볼 수 있는 특성이다. 즉, Python 3.8과 그 후의 버전에서 우리는 아래의 방식으로 lru_를 사용할 수 있다.cache, 프로그래머가 lru_를 사용하는 것을 방지하기 위해서일 수도 있습니다cache할 때 괄호를 넣는 것을 잊어버리다.
import functools
# lru_cache ,
#
@functools.lru_cache
def factorial(n):
print(f" {n} ")
return 1 if n <= 1 else n * factorial(n - 1)
Python 3.8 이전 버전에서 위의 코드를 실행하면 오류가 발생합니다. TypeError: Expected maxsize to be an integer or None.lru_cache의 구체적인 논리는_lru_cache_래퍼 함수에서 이루어진 것은 마찬가지입니다. 원본 코드를 열거하고 주석을 보존합니다.
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
nonlocal misses
misses += 1
result = user_function(*args, **kwds)
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
misses += 1
result = user_function(*args, **kwds)
cache[key] = result
return result
else:
def wrapper(*args, **kwds):
# Size limited caching that tracks accesses by recency
nonlocal root, hits, misses, full
key = make_key(args, kwds, typed)
with lock:
link = cache_get(key)
if link is not None:
# Move the link to the front of the circular queue
link_prev, link_next, _key, result = link
link_prev[NEXT] = link_next
link_next[PREV] = link_prev
last = root[PREV]
last[NEXT] = root[PREV] = link
link[PREV] = last
link[NEXT] = root
hits += 1
return result
misses += 1
result = user_function(*args, **kwds)
with lock:
if key in cache:
# Getting here means that this same key was added to the
# cache while the lock was released. Since the link
# update is already done, we need only return the
# computed result and update the count of misses.
pass
elif full:
# Use the old root to store the new key and result.
oldroot = root
oldroot[KEY] = key
oldroot[RESULT] = result
# Empty the oldest link and make it the new root.
# Keep a reference to the old key and old result to
# prevent their ref counts from going to zero during the
# update. That will prevent potentially arbitrary object
# clean-up code (i.e. __del__) from running while we're
# still adjusting the links.
root = oldroot[NEXT]
oldkey = root[KEY]
oldresult = root[RESULT]
root[KEY] = root[RESULT] = None
# Now update the cache dictionary.
del cache[oldkey]
# Save the potentially reentrant cache[key] assignment
# for last, after the root and links have been put in
# a consistent state.
cache[key] = oldroot
else:
# Put result in a new link at the front of the queue.
last = root[PREV]
link = [last, root, key, result]
last[NEXT] = root[PREV] = cache[key] = link
# Use the cache_len bound method instead of the len() function
# which could potentially be wrapped in an lru_cache itself.
full = (cache_len() >= maxsize)
return result
def cache_info():
"""Report cache statistics"""
with lock:
return _CacheInfo(hits, misses, maxsize, cache_len())
def cache_clear():
"""Clear the cache and cache statistics"""
nonlocal hits, misses, full
with lock:
cache.clear()
root[:] = [root, root, None, None]
hits = misses = 0
full = False
wrapper.cache_info = cache_info
wrapper.cache_clear = cache_clear
return wrapper
함수가 시작되는 곳 2~14행은 관건적인 변수를 정의하고,
CacheInfo(hits=1, misses=5, maxsize=128, currsize=5)
factorial(5)을 처음 실행할 때 명중되지 않았기 때문에misses=5, 두 번째factorial(3)을 실행할 때 캐시가 명중되었기 때문에hits=1.마지막으로 설명해야 할 것은 여러 개의 키워드 파라미터가 있는 함수에 대해 두 번의 호출 함수 키워드 파라미터가 들어오는 순서가 다르면 서로 다른 호출로 여겨지고 캐시에 명중되지 않는다는 것이다.또한,lru_cache 장식의 함수는list와 같은 가변 형식의 매개 변수를 포함할 수 없습니다. 왜냐하면hash를 지원하지 않기 때문입니다.
요약하자면, 이 글은 우선 캐시의 개념을 소개한 다음에Python에서 lru_를 보여주었다cache의 사용 방법, 마지막으로 원본을 통해 Python에서 lru_ 분석cache의 실현 디테일.
파이썬에서 lru_cache의 사용과 실현에 대한 상세한 설명은 여기까지입니다.cache 내용은 저희의 이전 글을 검색하거나 아래의 관련 글을 계속 찾아보세요. 앞으로 많이 사랑해 주세요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Python의 None과 NULL의 차이점 상세 정보그래서 대상 = 속성 + 방법 (사실 방법도 하나의 속성, 데이터 속성과 구별되는 호출 가능한 속성 같은 속성과 방법을 가진 대상을 클래스, 즉 Classl로 분류할 수 있다.클래스는 하나의 청사진과 같아서 하나의 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.