這種觀點在關(guān)于編程語言的討論中經(jīng)常出現(xiàn),經(jīng)常掩蓋了Python的眾多優(yōu)點。
事實是,如果能以Pythonic的方式編寫Python代碼,它是很快的。
細節(jié)決定成敗。經(jīng)驗豐富的Python開發(fā)者掌握了一系列微妙而強大的技巧,可以顯著提高代碼的性能。
這些技巧乍看之下似乎微不足道,但它們可以帶來效率的大幅提升。讓我們深入了解其中的9種方法,改變編寫和優(yōu)化Python代碼的方式。
如果有大量字符串等待處理,字符串連接將成為Python程序的瓶頸。
基本上,Python有兩種字符串連接的方式:
那么哪種方式更快呢?
現(xiàn)在,讓我們定義3個不同的函數(shù)來連接相同的字符串:
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"根據(jù)你的第一印象,你認為哪個函數(shù)最快,哪個最慢?
真正的結(jié)果可能會讓你驚訝:
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循環(huán)中逐個添加字符串更快。
原因很簡單。一方面,字符串在Python中是不可變的數(shù)據(jù),每次+=操作都會創(chuàng)建一個新字符串并復(fù)制舊字符串,這在計算上成本是昂貴的。
另一方面,.join()方法專門針對連接一系列字符串進行了優(yōu)化。它會預(yù)先計算出所生成字符串的大小,然后一次性創(chuàng)建它。因此,它避免了循環(huán)中+=操作帶來的開銷,從而使速度更快。
然而,在我們的測試中,速度最快的函數(shù)是直接連接字符串文字。它的高速度是由于:
總之,如果需要連接一組字符串,請選擇join()而不是+=。如果想要直接連接字符串,只需使用+即可。
創(chuàng)建列表并不是很難的事情。常見的兩種方式是:
讓我們使用一個簡單的代碼片段來測試它們的性能:
import timeitprint(timeit.timeit('[]', number=10 ** 7))# 0.1368238340364769print(timeit.timeit(list, number=10 ** 7))# 0.2958830420393497結(jié)果顯示,執(zhí)行l(wèi)ist()函數(shù)比直接使用[]要慢。
這是因為[]是一種字面量語法,而list()是一個構(gòu)造函數(shù)調(diào)用。調(diào)用函數(shù)無疑需要額外的時間。
從同樣的邏輯出發(fā),在創(chuàng)建字典時,我們也應(yīng)該使用{}而不是dict()。
成員測試操作的性能在很大程度上依賴于底層數(shù)據(jù)結(jié)構(gòu):
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正如上述代碼所示,使用集合進行成員測試比使用列表更快。
為什么會這樣呢?
這里的關(guān)鍵在于:在編寫程序時要仔細考慮底層數(shù)據(jù)結(jié)構(gòu)。正確利用合適的數(shù)據(jù)結(jié)構(gòu)可以顯著加快代碼的運行速度。
Python中有四種推導(dǎo)式類型:列表推導(dǎo)式、字典推導(dǎo)式、集合推導(dǎo)式和生成器推導(dǎo)式。它們不僅為創(chuàng)建相對數(shù)據(jù)結(jié)構(gòu)提供了更簡潔的語法,而且比使用for循環(huán)更高效,因為它們在Python的C實現(xiàn)中進行了優(yōu)化。
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上述代碼是列表推導(dǎo)式和for循環(huán)之間的簡單速度比較。結(jié)果顯示,列表推導(dǎo)式更快。
在Python中,訪問局部變量比訪問全局變量或?qū)ο蟮膶傩愿臁?span style="display:none">5p028資訊網(wǎng)——每日最新資訊28at.com
以下是一個實例來證明這一點:
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的工作原理。直觀地說,當一個函數(shù)被編譯時,其中的局部變量是已知的,但其他外部變量需要時間來檢索。
這可能是一個小問題,但是當處理大量數(shù)據(jù)時,我們可以利用它來優(yōu)化我們的代碼。
當工程師們說到Python時,默認情況下指的是CPython。因為CPython是Python語言的默認實現(xiàn),也是使用最廣泛的實現(xiàn)。
鑒于它的大部分內(nèi)置模塊和庫都是用C語言編寫的,而C語言是一種更快且更底層的語言,因此我們應(yīng)該利用這些內(nèi)置模塊和庫,避免重復(fù)勞動。
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上面的程序比較了兩種統(tǒng)計列表中元素頻率的方法。可以看到,利用collections模塊中內(nèi)置的Counter函數(shù)比自己編寫的for循環(huán)更快、更簡潔、更好。
緩存是一種常用的技術(shù),用于避免重復(fù)計算并加快程序的運行速度。
幸運的是,在大多數(shù)情況下,我們不需要自己編寫緩存處理代碼,因為Python為此提供了一個開箱即用的裝飾器來實現(xiàn)這個目的——@functools.cache。
例如,下面的代碼將執(zhí)行兩個生成斐波那契數(shù)的函數(shù),一個有緩存裝飾器,而另一個沒有:
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)# 測試每個函數(shù)的執(zhí)行時間print(timeit.timeit(lambda: fibonacci(30), number=1))# 0.09499712497927248print(timeit.timeit(lambda: fibonacci_cached(30), number=1))# 6.458023563027382e-06結(jié)果證明了@functools.cache裝飾器是如何使我們的代碼變得更快的。
基本的fibonacci函數(shù)效率較低,因為在計算fibonacci(30)結(jié)果的過程中,它會多次重新計算相同的斐波那契數(shù)。
而使用緩存的版本要快得多,因為它緩存了之前的計算結(jié)果。因此,它只計算每個斐波那契數(shù)一次,并且對于相同的參數(shù)再次調(diào)用時會從緩存中獲取結(jié)果。
僅僅添加一個內(nèi)置的裝飾器就可以帶來如此大的改進,這就是Pythonic的意義所在。
本文鏈接:http://m.www897cc.com/showinfo-26-68332-0.html掌握這九個技巧,讓Python代碼快如閃電
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com