欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品

主頁 > 知識庫 > Python中l(wèi)ru_cache的使用和實現(xiàn)詳解

Python中l(wèi)ru_cache的使用和實現(xiàn)詳解

熱門標(biāo)簽:智能機(jī)器人電銷神器 外呼電信系統(tǒng) 熱門電銷機(jī)器人 河南虛擬外呼系統(tǒng)公司 上海企業(yè)外呼系統(tǒng) 惠州龍門400電話要怎么申請 萬利達(dá)百貨商場地圖標(biāo)注 電話機(jī)器人哪里有賣 okcc外呼系統(tǒng)怎么調(diào)速度

在計算機(jī)軟件領(lǐng)域,緩存(Cache)指的是將部分?jǐn)?shù)據(jù)存儲在內(nèi)存中,以便下次能夠更快地訪問這些數(shù)據(jù),這也是一個典型的用空間換時間的例子。一般用于緩存的內(nèi)存空間是固定的,當(dāng)有更多的數(shù)據(jù)需要緩存的時候,需要將已緩存的部分?jǐn)?shù)據(jù)清除后再將新的緩存數(shù)據(jù)放進(jìn)去。需要清除哪些數(shù)據(jù),就涉及到了緩存置換的策略,LRU(Least Recently Used,最近最少使用)是很常見的一個,也是 Python 中提供的緩存置換策略。

下面我們通過一個簡單的示例來看 Python 中的 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}')

上面的代碼中定義了函數(shù) factorial,通過遞歸的方式計算 n 的階乘,并且在函數(shù)調(diào)用的時候打印出 n 的值。然后分別計算 5 和 3 的階乘,并打印結(jié)果。運(yùn)行上面的代碼,輸出如下

計算 5 的階乘
計算 4 的階乘
計算 3 的階乘
計算 2 的階乘
計算 1 的階乘
5! = 120
計算 3 的階乘
計算 2 的階乘
計算 1 的階乘
3! = 6

可以看到, factorial(3) 的結(jié)果在計算 factorial(5) 的時候已經(jīng)被計算過了,但是后面又被重復(fù)計算了。為了避免這種重復(fù)計算,我們可以在定義函數(shù) factorial 的時候加上 lru_cache 裝飾器,如下所示

import functools
# 注意 lru_cache 后的一對括號,證明這是帶參數(shù)的裝飾器
@functools.lru_cache()
def factorial(n):
  print(f"計算 {n} 的階乘")
  return 1 if n = 1 else n * factorial(n - 1)

重新運(yùn)行代碼,輸入如下

計算 5 的階乘
計算 4 的階乘
計算 3 的階乘
計算 2 的階乘
計算 1 的階乘
5! = 120
3! = 6

可以看到,這次在調(diào)用 factorial(3) 的時候沒有打印相應(yīng)的輸出,也就是說 factorial(3) 是直接從緩存讀取的結(jié)果,證明緩存生效了。

被 lru_cache 修飾的函數(shù)在被相同參數(shù)調(diào)用的時候,后續(xù)的調(diào)用都是直接從緩存讀結(jié)果,而不用真正執(zhí)行函數(shù)。下面我們深入源碼,看看 Python 內(nèi)部是怎么實現(xiàn) lru_cache 的。寫作時 Python 最新發(fā)行版是 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

這段代碼中有如下幾個關(guān)鍵點

關(guān)鍵字參數(shù)

maxsize 表示緩存容量,如果為 None 表示容量不設(shè)限, typed 表示是否區(qū)分參數(shù)類型,注釋中也給出了解釋,如果 typed == True ,那么 f(3) 和 f(3.0) 會被認(rèn)為是不同的函數(shù)調(diào)用。

第 24 行的條件分支

如果 lru_cache 的第一個參數(shù)是可調(diào)用的,直接返回 wrapper,也就是把 lru_cache 當(dāng)做不帶參數(shù)的裝飾器,這是 Python 3.8 才有的特性,也就是說在 Python 3.8 及之后的版本中我們可以用下面的方式使用 lru_cache,可能是為了防止程序員在使用 lru_cache 的時候忘記加括號。

import functools
# 注意 lru_cache 后面沒有括號,
# 證明這是將其當(dāng)做不帶參數(shù)的裝飾器
@functools.lru_cache
def factorial(n):
  print(f"計算 {n} 的階乘")
  return 1 if n = 1 else n * factorial(n - 1)

注意,Python 3.8 之前的版本運(yùn)行上面代碼會報錯:TypeError: Expected maxsize to be an integer or None。

lru_cache 的具體邏輯是在 _lru_cache_wrapper 函數(shù)中實現(xiàn)的,還是一樣,列出源碼,保留注釋。

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

函數(shù)開始的地方 2~14 行定義了一些關(guān)鍵變量,

  • hits 和 misses 分別表示緩存命中和沒有命中的次數(shù)
  • root 雙向循環(huán)鏈表的頭結(jié)點,每個節(jié)點保存前向指針、后向指針、key 和 key 對應(yīng)的 result,其中 key 為 _make_key 函數(shù)根據(jù)參數(shù)結(jié)算出來的字符串,result 為被修飾的函數(shù)在給定的參數(shù)下返回的結(jié)果。 注意 ,root 是不保存數(shù)據(jù) key 和 result 的。
  • cache 是真正保存緩存數(shù)據(jù)的地方,類型為 dict。 cache 中的 key 也是 _make_key 函數(shù)根據(jù)參數(shù)結(jié)算出來的字符串,value 保存的是 key 對應(yīng)的雙向循環(huán)鏈表中的節(jié)點。

接下來根據(jù) maxsize 不同,定義不同的 wrapper 。

  • maxsize == 0 ,其實也就是沒有緩存,那么每次函數(shù)調(diào)用都不會命中,并且沒有命中的次數(shù) misses 加 1。
  • maxsize is None ,不限制緩存大小,如果函數(shù)調(diào)用不命中,將沒有命中次數(shù) misses 加 1,否則將命中次數(shù) hits 加 1。
  • 限制緩存的大小,那么需要根據(jù) LRU 算法來更新 cache ,也就是 42~97 行的代碼。
    • 如果緩存命中 key,那么將命中節(jié)點移到雙向循環(huán)鏈表的結(jié)尾,并且返回結(jié)果(47~58 行)
    • 這里通過字典加雙向循環(huán)鏈表的組合數(shù)據(jù)結(jié)構(gòu),實現(xiàn)了用 O(1) 的時間復(fù)雜度刪除給定的節(jié)點。
    • 如果沒有命中,并且緩存滿了,那么需要將最久沒有使用的節(jié)點(root 的下一個節(jié)點)刪除,并且將新的節(jié)點添加到鏈表結(jié)尾。在實現(xiàn)中有一個優(yōu)化,直接將當(dāng)前的 root 的 key 和 result 替換成新的值,將 root 的下一個節(jié)點置為新的 root,這樣得到的雙向循環(huán)鏈表結(jié)構(gòu)跟刪除 root 的下一個節(jié)點并且將新節(jié)點加到鏈表結(jié)尾是一樣的,但是避免了刪除和添加節(jié)點的操作(68~88 行)
    • 如果沒有命中,并且緩存沒滿,那么直接將新節(jié)點添加到雙向循環(huán)鏈表的結(jié)尾( root[PREV] ,這里我認(rèn)為是結(jié)尾,但是代碼注釋中寫的是開頭)(89~96 行)

最后給 wrapper 添加兩個屬性函數(shù) cache_info 和 cache_clear , cache_info 顯示當(dāng)前緩存的命中情況的統(tǒng)計數(shù)據(jù), cache_clear 用于清空緩存。對于上面階乘相關(guān)的代碼,如果在最后執(zhí)行 factorial.cache_info() ,會輸出

CacheInfo(hits=1, misses=5, maxsize=128, currsize=5)

第一次執(zhí)行 factorial(5) 的時候都沒命中,所以 misses = 5,第二次執(zhí)行 factorial(3) 的時候,緩存命中,所以 hits = 1。

最后需要說明的是, 對于有多個關(guān)鍵字參數(shù)的函數(shù),如果兩次調(diào)用函數(shù)關(guān)鍵字參數(shù)傳入的順序不同,會被認(rèn)為是不同的調(diào)用,不會命中緩存。另外,被 lru_cache 裝飾的函數(shù)不能包含可變類型參數(shù)如 list,因為它們不支持 hash。

總結(jié)一下,這篇文章首先簡介了一下緩存的概念,然后展示了在 Python 中 lru_cache 的使用方法,最后通過源碼分析了 Python 中 lru_cache 的實現(xiàn)細(xì)節(jié)。

到此這篇關(guān)于Python中l(wèi)ru_cache的使用和實現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Python lru_cache 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • Python 的lru_cache裝飾器使用簡介
  • Python實現(xiàn)的一個簡單LRU cache
  • python自帶緩存lru_cache用法及擴(kuò)展的使用

標(biāo)簽:百色 綏化 綿陽 合肥 周口 淮安 秦皇島 周口

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Python中l(wèi)ru_cache的使用和實現(xiàn)詳解》,本文關(guān)鍵詞  Python,中,lru,cache,的,使用,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Python中l(wèi)ru_cache的使用和實現(xiàn)詳解》相關(guān)的同類信息!
  • 本頁收集關(guān)于Python中l(wèi)ru_cache的使用和實現(xiàn)詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品
  • <rt id="w000q"><acronym id="w000q"></acronym></rt>
  • <abbr id="w000q"></abbr>
    <rt id="w000q"></rt>
    91精品国产全国免费观看| 偷偷要91色婷婷| 国产麻豆精品theporn| 人人妻人人藻人人爽欧美一区| 欧美精品123区| 亚洲福利电影网| 国产欧美视频一区| 欧美日韩在线三区| 一区二区三区**美女毛片| 99r精品视频| 欧美性猛交一区二区三区精品| 综合久久久久久| 99久久婷婷国产综合精品| 色94色欧美sute亚洲线路一ni| 亚洲人成亚洲人成在线观看图片| www.亚洲人| 91国在线观看| 一个色在线综合| 波多野结衣视频播放| 日韩一二在线观看| 蜜桃av一区二区三区电影| 日本二区在线观看| 国产欧美一区二区三区在线看蜜臀| 国产一区二区三区国产| 亚洲一区电影在线观看| 国产精品国产三级国产aⅴ入口 | 两女双腿交缠激烈磨豆腐| 欧美在线你懂得| 午夜在线成人av| 瑟瑟视频在线观看| 亚洲国产精品成人综合 | 天天操天天操天天操天天操天天操| 中文字幕欧美激情| 91同城在线观看| 欧美麻豆精品久久久久久| 免费成人小视频| 麻豆精品视频在线观看免费| 国产亚洲无码精品| 国产人成亚洲第一网站在线播放| 粉嫩在线一区二区三区视频| 欧美色视频在线观看| 青青青伊人色综合久久| 波多野结衣家庭教师在线观看| 中文字幕日韩精品一区| 性xxxxxxxxx| 26uuu国产日韩综合| 成人精品在线视频观看| 欧美美女喷水视频| 国产在线麻豆精品观看| 91久久一区二区| 日韩成人一区二区三区在线观看| wwwww黄色| 一区二区三区久久久| 美女洗澡无遮挡| 国产精品伦一区二区三级视频| 无码国产精品久久一区免费| 精品国产青草久久久久福利| 成人精品国产一区二区4080| 91精品国产色综合久久不卡蜜臀| 国产一区二区网址| 欧美日韩一区成人| 国产在线不卡一区| 欧美视频一区在线| 国产精品综合网| 欧美精品亚洲二区| 国产麻豆精品95视频| 欧美精品一级二级| 国产99久久久久久免费看农村| 欧美猛男gaygay网站| 国产成人免费视频一区| 欧美久久高跟鞋激| 国产91丝袜在线播放0| 欧美一区二区三区成人| 床上的激情91.| 91精品国产手机| 99国产精品视频免费观看| 26uuu亚洲综合色| 亚洲av无码成人精品区| 欧美激情一区在线| 亚洲男人在线天堂| 亚洲男同性恋视频| 少妇视频在线播放| 日韩国产欧美在线播放| 日本高清不卡在线观看| 国产剧情一区在线| 日韩一区二区精品| 日韩高清一二三区| 国产精品久线在线观看| 精品无人区无码乱码毛片国产| 亚洲理论在线观看| 欧美一级特黄高清视频| 蜜臀久久99精品久久久久宅男| 欧美在线观看禁18| 成人aa视频在线观看| 久久午夜色播影院免费高清| 亚洲婷婷在线观看| 一区二区三区国产精华| 欧美卡一卡二卡三| 国产精品99久| 26uuu另类欧美亚洲曰本| 一区二区免费在线观看视频| 亚洲黄色片在线观看| 欧美一区二区三区爽爽爽| 韩国成人福利片在线播放| 欧美一级爆毛片| 亚洲午夜久久久久久久久| 亚洲久草在线视频| 欧美视频www| 国产成人aaaa| 国产日韩成人精品| 色欲狠狠躁天天躁无码中文字幕 | 亚洲制服丝袜一区| 一本在线高清不卡dvd| 丁香婷婷综合网| 国产偷国产偷亚洲高清人白洁| 黄色在线观看av| 三级成人在线视频| 51精品久久久久久久蜜臀| 日本黄色三级网站| 一区二区三区在线观看视频| 色综合咪咪久久| 成人动漫一区二区三区| 国产精品无码永久免费888| 日韩欧美在线视频播放| 国产精品一二三| 日本一区二区电影| 婷婷社区五月天| 国产91高潮流白浆在线麻豆 | 亚洲欧美区自拍先锋| 丁香花五月激情| jlzzjlzz亚洲女人18| 亚洲婷婷在线视频| 一本色道久久综合亚洲精品按摩 | 久久亚洲精品国产精品紫薇| 欧美丰满美乳xxⅹ高潮www| 激情都市一区二区| 欧美国产精品一区二区三区| 一区二区三区在线播放视频| 大白屁股一区二区视频| 亚洲欧美另类小说视频| 欧美色电影在线| 亚洲精品国产成人av在线| 日韩和欧美的一区| 精品99999| 波多野结衣久久久久| 99re8在线精品视频免费播放| 亚洲精品菠萝久久久久久久| 欧美日韩色综合| 91黄色免费视频| 精品在线一区二区| 国产精品三级在线观看| 欧洲色大大久久| 黄色激情在线观看| 看片网站欧美日韩| 欧美激情一区二区三区全黄| 日本精品一区二区三区四区的功能| 91老师片黄在线观看| 婷婷久久综合九色国产成人| 欧美大片日本大片免费观看| sm捆绑调教视频| 91麻豆高清视频| 日韩在线一二三区| 久久久精品免费观看| 粉嫩av性色av蜜臀av网站| 男人添女人荫蒂国产| 男女男精品网站| 国产精品三级av| 欧美日韩精品二区第二页| 亚洲熟妇无码av| 风间由美一区二区av101| 亚洲国产另类av| 久久尤物电影视频在线观看| 放荡的美妇在线播放| 久久久久亚洲AV成人无码国产| 精品亚洲欧美一区| 亚洲精品五月天| 2欧美一区二区三区在线观看视频| 黄色片在线观看网站| jizz日本免费| 成人一区二区视频| 五月开心婷婷久久| 中文字幕av在线一区二区三区| 欧美日韩中文一区| 夜夜春很很躁夜夜躁| 4438x全国最大成人| 国产一区在线精品| 一区二区三区精品视频在线| 久久免费国产精品| 欧美精品视频www在线观看| 国产在线免费看| 中文字幕一区二区人妻电影丶| 粉嫩av亚洲一区二区图片| 亚洲成人资源网| 中文字幕av一区 二区| 欧美一区二区三区免费在线看| 蜜臀久久精品久久久用户群体| 大地资源二中文在线影视观看| 成人avav影音| 国产综合色精品一区二区三区| 午夜精品一区二区三区电影天堂|