欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python實(shí)現(xiàn)緩存的兩個(gè)簡單方法

 更新時(shí)間:2024年11月17日 09:04:31   作者:python收藏家  
緩存是一種用于提高應(yīng)用程序性能的技術(shù),它通過臨時(shí)存儲(chǔ)程序獲得的結(jié)果,以便在以后需要時(shí)重用它們,本文將學(xué)習(xí)Python中的不同緩存技術(shù),感興趣的可以了解下

緩存是一種用于提高應(yīng)用程序性能的技術(shù),它通過臨時(shí)存儲(chǔ)程序獲得的結(jié)果,以便在以后需要時(shí)重用它們。

在本文中,我們將學(xué)習(xí)Python中的不同緩存技術(shù),包括functools模塊中的@ lru_cache和@ cache裝飾器。

簡單示例:Python緩存實(shí)現(xiàn)

要在Python中創(chuàng)建緩存,我們可以使用functools模塊中的@cache裝飾器。在下面的代碼中,注意print()函數(shù)只執(zhí)行一次:

import functools

@functools.cache
def square(n):
    print(f"Calculating square of {n}")
    return n * n

# Testing the cached function
print(square(4))  # Output: Calculating square of 4 \n 16
print(square(4))  # Output: 16 (cached result, no recalculation)

輸出

Calculating square of 4
16
16

Python中的緩存是什么

假設(shè)我們需要解決一個(gè)數(shù)學(xué)問題,花一個(gè)小時(shí)得到正確的答案。如果第二天我們必須解決同樣的問題,那么重用我們以前的工作而不是從頭開始會(huì)很有幫助。

Python中的緩存遵循類似的原則–它在函數(shù)調(diào)用中計(jì)算值時(shí)存儲(chǔ)這些值,以便在再次需要時(shí)重用它們。這種類型的緩存也稱為記憶化。

讓我們看一個(gè)簡短的例子,它計(jì)算了兩次大范圍的數(shù)字之和:

output = sum(range(100_000_001))
print(output)
output = sum(range(100_000_001))
print(output)

輸出

5000000050000000
5000000050000000

計(jì)算兩次運(yùn)行時(shí)間:

import timeit

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

輸出

1.2157779589979327
1.1848394999979064

輸出顯示,兩個(gè)調(diào)用所花費(fèi)的時(shí)間大致相同(取決于我們的設(shè)置,我們可能會(huì)獲得更快或更慢的執(zhí)行時(shí)間)。

但是,我們可以使用緩存來避免多次計(jì)算相同的值。我們可以使用內(nèi)置functools模塊重新定義:

import functools
import timeit

sum = functools.cache(sum)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

輸出

1.2760689580027247
2.3330067051574588e-06

第二個(gè)調(diào)用現(xiàn)在需要幾微秒的時(shí)間,而不是一秒鐘,因?yàn)閺?到100,000,000的數(shù)字之和的結(jié)果已經(jīng)計(jì)算并緩存了-第二個(gè)調(diào)用使用之前計(jì)算和存儲(chǔ)的值。

functools.cache()裝飾器是在Python 3.9版本中添加的,但我們可以在舊版本中使用functools.lru_cache()。

Python緩存:不同的方法

Python的functools模塊有兩個(gè)裝飾器用于將緩存應(yīng)用于函數(shù)。讓我們通過一個(gè)示例來探索functools.lru_cache()和functools.cache()。

讓我們編寫一個(gè)函數(shù)sum_digits(),它接受一個(gè)數(shù)字序列并返回這些數(shù)字的位數(shù)之和。例如,如果我們使用元組(23,43,8)作為輸入,那么:

  • 23的數(shù)字之和是5
  • 43的數(shù)字之和是7
  • 8的數(shù)字之和是8
  • 因此,總和為20。

這是我們可以編寫sum_digits函數(shù)的一種方式:

def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = 23, 43, 8

print(sum_digits(numbers))

輸出

20

讓我們使用這個(gè)函數(shù)來探索創(chuàng)建緩存的不同方法。

Python手動(dòng)緩存

讓我們首先手動(dòng)創(chuàng)建該高速緩存。雖然我們也可以很容易地自動(dòng)化,但手動(dòng)創(chuàng)建緩存有助于我們理解這個(gè)過程。

讓我們創(chuàng)建一個(gè)字典,并在每次使用新值調(diào)用函數(shù)時(shí)添加鍵值對來存儲(chǔ)結(jié)果。如果我們用一個(gè)已經(jīng)存儲(chǔ)在這個(gè)字典中的值調(diào)用函數(shù),函數(shù)將返回存儲(chǔ)的值,而不會(huì)再次計(jì)算:

import random
import timeit

def sum_digits(numbers):
    if numbers not in sum_digits.my_cache:
        sum_digits.my_cache[numbers] = sum(
            int(digit) for number in numbers for digit in str(number)
        )
    return sum_digits.my_cache[numbers]
sum_digits.my_cache = {}

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

輸出

0.28875587500078836
0.0044607500021811575

第二次調(diào)用sum_digits(numbers)比第一次調(diào)用快得多,因?yàn)樗褂昧司彺娴闹怠?/p>

現(xiàn)在讓我們更詳細(xì)地解釋上面的代碼。首先,請注意,我們在定義函數(shù)后創(chuàng)建了字典sum_digits.my_cache,即使我們在函數(shù)定義中使用了它。

函數(shù)的作用是:檢查傳遞給函數(shù)的參數(shù)是否已經(jīng)是sum_digits.my_cache字典中的鍵之一。僅當(dāng)參數(shù)不在該高速緩存中時(shí),才計(jì)算所有數(shù)字的和。

由于我們在調(diào)用函數(shù)時(shí)使用的參數(shù)作為字典中的鍵,因此它必須是可散列數(shù)據(jù)類型。列表是不可散列的,所以我們不能將它用作字典中的鍵。例如,讓我們嘗試用列表而不是元組來替換數(shù)字-這將引發(fā)TypeError:

# ...

numbers = [random.randint(1, 1000) for _ in range(1_000_000)]

# ...

輸出

Traceback (most recent call last):
...
TypeError: unhashable type: 'list'

手動(dòng)創(chuàng)建緩存非常適合學(xué)習(xí),但現(xiàn)在讓我們探索更快的方法。

使用functools.lru_cache()進(jìn)行Python緩存

Python從3.2版開始就有了lru_cache()裝飾器。函數(shù)名開頭的“lru”代表“least recently used”。我們可以把緩存看作是一個(gè)用來存儲(chǔ)經(jīng)常使用的東西的盒子–當(dāng)它填滿時(shí),LRU策略會(huì)扔掉我們很長時(shí)間沒有使用過的東西,為新的東西騰出空間。

讓我們用@functools.lru_cache來裝飾sum_digits函數(shù):

import functools
import random
import timeit

@functools.lru_cache
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

輸出

0.28326129099878017
0.002184917000704445

多虧了緩存,第二個(gè)調(diào)用的運(yùn)行時(shí)間大大縮短。

默認(rèn)情況下,該高速緩存存儲(chǔ)計(jì)算的前128個(gè)值。一旦所有128個(gè)位置都滿了,算法就會(huì)刪除最近最少使用的(LRU)值,為新值騰出空間。

當(dāng)我們使用maxsize參數(shù)修飾函數(shù)時(shí),我們可以設(shè)置不同的最大緩存大?。?/p>

import functools
import random
import timeit

@functools.lru_cache(maxsize=5)
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

# ...

在這種情況下,該高速緩存僅存儲(chǔ)5個(gè)值。如果我們不想限制該高速緩存的大小,也可以將maxsize參數(shù)設(shè)置為None。

使用functools.cache()進(jìn)行Python緩存

Python 3.9包含了一個(gè)更簡單、更快速的緩存裝飾器——functools. cache()。這個(gè)裝飾器有兩個(gè)主要特點(diǎn):

  • 它沒有最大大小-這類似于調(diào)用functools.lru_cache(maxsize=None)。
  • 它存儲(chǔ)所有函數(shù)調(diào)用及其結(jié)果(它不使用LRU策略)。這適用于輸出相對較小的函數(shù),或者當(dāng)我們不需要擔(dān)心緩存大小限制時(shí)。

讓我們在sum_digits函數(shù)上使用@functools.cache裝飾器:

import functools
import random
import timeit

@functools.cache
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

輸出

0.16661812500024098
0.0018135829996026587

使用@functools.cache修飾sum_digits()等效于將sum_digits賦值給functools.cache():

# ...

def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

sum_digits = functools.cache(sum_digits)

請注意,我們也可以使用不同的導(dǎo)入方式:

from functools import cache

這樣,我們就可以只使用@cache來裝飾我們的函數(shù)。

其他緩存策略

Python自己的工具實(shí)現(xiàn)了LRU緩存策略,刪除最近最少使用的條目,為新值騰出空間。

讓我們來看看其他一些緩存策略:

  • 先進(jìn)先出(FIFO):當(dāng)該高速緩存已滿時(shí),刪除添加的第一個(gè)項(xiàng),為新值騰出空間。LRU和FIFO之間的區(qū)別在于,LRU將最近使用的項(xiàng)保存在該高速緩存中,而FIFO丟棄最舊的項(xiàng)而不管是否使用。
  • 后進(jìn)先出(LIFO):當(dāng)該高速緩存已滿時(shí),刪除最近添加的項(xiàng)。想象一下自助餐廳里的一堆盤子。我們最近放入堆棧的盤子(最后一個(gè))是我們將首先取出的盤子(第一個(gè))。
  • Most-recently used(MRU):當(dāng)該高速緩存中需要空間時(shí),將丟棄最近使用的值。
  • 隨機(jī)替換(RR):該策略隨機(jī)丟棄一個(gè)項(xiàng)目,為新項(xiàng)目騰出空間。

這些策略還可以與有效生存期的度量相結(jié)合,有效生存期指的是該高速緩存中的一段數(shù)據(jù)被認(rèn)為有效或相關(guān)的時(shí)間。想象一下緩存中的一篇新聞文章。它可能經(jīng)常被訪問(LRU會(huì)保留它),但一周后,新聞可能會(huì)過時(shí)。

Python中緩存時(shí)的常見挑戰(zhàn)

我們已經(jīng)了解了Python中緩存的優(yōu)點(diǎn)。在實(shí)現(xiàn)緩存時(shí),還需要記住一些挑戰(zhàn)和缺點(diǎn):

  • 緩存失效和一致性:數(shù)據(jù)可能會(huì)隨著時(shí)間而變化。因此,存儲(chǔ)在緩存中的值也可能需要更新或刪除。
  • 內(nèi)存管理:在緩存中存儲(chǔ)大量數(shù)據(jù)需要內(nèi)存,如果緩存無限增長,這可能會(huì)導(dǎo)致性能問題。
  • 復(fù)雜性:添加緩存會(huì)在創(chuàng)建和維護(hù)該高速緩存時(shí)給系統(tǒng)帶來復(fù)雜性。通常,好處大于這些成本,但這種增加的復(fù)雜性可能會(huì)導(dǎo)致難以發(fā)現(xiàn)和糾正的錯(cuò)誤。

結(jié)論

當(dāng)對同一數(shù)據(jù)重復(fù)執(zhí)行計(jì)算密集型操作時(shí),我們可以使用緩存來優(yōu)化性能。

Python有兩個(gè)裝飾器在調(diào)用函數(shù)時(shí)創(chuàng)建緩存:functools模塊中的@lru_cache和@cache。

但是,我們需要確保該高速緩存保持最新,并正確管理內(nèi)存。

到此這篇關(guān)于Python實(shí)現(xiàn)緩存的兩個(gè)簡單方法的文章就介紹到這了,更多相關(guān)Python緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論