這種觀點在關于編程語言的討論中經常出現,經常掩蓋了Python的眾多優點。
事實是,如果能以Pythonic的方式編寫Python代碼,它是很快的。
細節決定成敗。經驗豐富的Python開發者掌握了一系列微妙而強大的技巧,可以顯著提高代碼的性能。
這些技巧乍看之下似乎微不足道,但它們可以帶來效率的大幅提升。讓我們深入了解其中的9種方法,改變編寫和優化Python代碼的方式。
如果有大量字符串等待處理,字符串連接將成為Python程序的瓶頸。
基本上,Python有兩種字符串連接的方式:
那么哪種方式更快呢?
現在,讓我們定義3個不同的函數來連接相同的字符串:
mylist = ["Yang", "Zhou", "is", "writing"]# 使用'+'def concat_plus(): result = "" for word in mylist: result += word + " " return result# 使用'join()'def concat_join(): return " ".join(mylist)# 直接連接而不使用列表def concat_directly(): return "Yang" + "Zhou" + "is" + "writing"
根據你的第一印象,你認為哪個函數最快,哪個最慢?
真正的結果可能會讓你驚訝:
import timeitprint(timeit.timeit(concat_plus, number=10000))# 0.002738415962085128print(timeit.timeit(concat_join, number=10000))# 0.0008482920238748193print(timeit.timeit(concat_directly, number=10000))# 0.00021425005979835987
如上所示,對于連接一組字符串,join()方法比在for循環中逐個添加字符串更快。
原因很簡單。一方面,字符串在Python中是不可變的數據,每次+=操作都會創建一個新字符串并復制舊字符串,這在計算上成本是昂貴的。
另一方面,.join()方法專門針對連接一系列字符串進行了優化。它會預先計算出所生成字符串的大小,然后一次性創建它。因此,它避免了循環中+=操作帶來的開銷,從而使速度更快。
然而,在我們的測試中,速度最快的函數是直接連接字符串文字。它的高速度是由于:
總之,如果需要連接一組字符串,請選擇join()而不是+=。如果想要直接連接字符串,只需使用+即可。
創建列表并不是很難的事情。常見的兩種方式是:
讓我們使用一個簡單的代碼片段來測試它們的性能:
import timeitprint(timeit.timeit('[]', number=10 ** 7))# 0.1368238340364769print(timeit.timeit(list, number=10 ** 7))# 0.2958830420393497
結果顯示,執行list()函數比直接使用[]要慢。
這是因為[]是一種字面量語法,而list()是一個構造函數調用。調用函數無疑需要額外的時間。
從同樣的邏輯出發,在創建字典時,我們也應該使用{}而不是dict()。
成員測試操作的性能在很大程度上依賴于底層數據結構:
import timeitlarge_dataset = range(100000)search_element = 2077large_list = list(large_dataset)large_set = set(large_dataset)def list_membership_test(): return search_element in large_listdef set_membership_test(): return search_element in large_setprint(timeit.timeit(list_membership_test, number=1000))# 0.01112208398990333print(timeit.timeit(set_membership_test, number=1000))# 3.27499583363533e-05
正如上述代碼所示,使用集合進行成員測試比使用列表更快。
為什么會這樣呢?
這里的關鍵在于:在編寫程序時要仔細考慮底層數據結構。正確利用合適的數據結構可以顯著加快代碼的運行速度。
Python中有四種推導式類型:列表推導式、字典推導式、集合推導式和生成器推導式。它們不僅為創建相對數據結構提供了更簡潔的語法,而且比使用for循環更高效,因為它們在Python的C實現中進行了優化。
import timeitdef generate_squares_for_loop(): squares = [] for i in range(1000): squares.append(i * i) return squaresdef generate_squares_comprehension(): return [i * i for i in range(1000)]print(timeit.timeit(generate_squares_for_loop, number=10000))# 0.2797503340989351print(timeit.timeit(generate_squares_comprehension, number=10000))# 0.2364629579242319
上述代碼是列表推導式和for循環之間的簡單速度比較。結果顯示,列表推導式更快。
在Python中,訪問局部變量比訪問全局變量或對象的屬性更快。
以下是一個實例來證明這一點:
import timeitclass Example: def __init__(self): self.value = 0obj = Example()def test_dot_notation(): for _ in range(1000): obj.value += 1def test_local_variable(): value = obj.value for _ in range(1000): value += 1 obj.value = valueprint(timeit.timeit(test_dot_notation, number=1000))# 0.036605041939765215print(timeit.timeit(test_local_variable, number=1000))# 0.024470250005833805
這就是Python的工作原理。直觀地說,當一個函數被編譯時,其中的局部變量是已知的,但其他外部變量需要時間來檢索。
這可能是一個小問題,但是當處理大量數據時,我們可以利用它來優化我們的代碼。
當工程師們說到Python時,默認情況下指的是CPython。因為CPython是Python語言的默認實現,也是使用最廣泛的實現。
鑒于它的大部分內置模塊和庫都是用C語言編寫的,而C語言是一種更快且更底層的語言,因此我們應該利用這些內置模塊和庫,避免重復勞動。
import timeitimport randomfrom collections import Counterdef count_frequency_custom(lst): frequency = {} for item in lst: if item in frequency: frequency[item] += 1 else: frequency[item] = 1 return frequencydef count_frequency_builtin(lst): return Counter(lst)large_list = [random.randint(0, 100) for _ in range(1000)]print(timeit.timeit(lambda: count_frequency_custom(large_list), number=100))# 0.005160166998393834print(timeit.timeit(lambda: count_frequency_builtin(large_list), number=100))# 0.002444291952997446
上面的程序比較了兩種統計列表中元素頻率的方法。可以看到,利用collections模塊中內置的Counter函數比自己編寫的for循環更快、更簡潔、更好。
緩存是一種常用的技術,用于避免重復計算并加快程序的運行速度。
幸運的是,在大多數情況下,我們不需要自己編寫緩存處理代碼,因為Python為此提供了一個開箱即用的裝飾器來實現這個目的——@functools.cache。
例如,下面的代碼將執行兩個生成斐波那契數的函數,一個有緩存裝飾器,而另一個沒有:
import timeitimport functoolsdef fibonacci(n): if n in (0, 1): return n return fibonacci(n - 1) + fibonacci(n - 2)@functools.cachedef fibonacci_cached(n): if n in (0, 1): return n return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)# 測試每個函數的執行時間print(timeit.timeit(lambda: fibonacci(30), number=1))# 0.09499712497927248print(timeit.timeit(lambda: fibonacci_cached(30), number=1))# 6.458023563027382e-06
結果證明了@functools.cache裝飾器是如何使我們的代碼變得更快的。
基本的fibonacci函數效率較低,因為在計算fibonacci(30)結果的過程中,它會多次重新計算相同的斐波那契數。
而使用緩存的版本要快得多,因為它緩存了之前的計算結果。因此,它只計算每個斐波那契數一次,并且對于相同的參數再次調用時會從緩存中獲取結果。
僅僅添加一個內置的裝飾器就可以帶來如此大的改進,這就是Pythonic的意義所在。
本文鏈接:http://www.www897cc.com/showinfo-26-68332-0.html掌握這九個技巧,讓Python代碼快如閃電
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: 自動化測試的十大誤區,你知道哪個?