讓Python代碼運(yùn)行更快的9個(gè)小技巧
字符串拼接的技巧
如果有大量字符串等待處理,字符串連接將成為 Python 的瓶頸。
一般來講,Python 中有兩種字符串拼接方式:
- 使用該
join()
函數(shù)將字符串列表合并為一個(gè)字符串 - 使用
+
or+=
符號將每個(gè)字符串加成一個(gè)
那么哪種方式更快呢?我們一起來看一下
mylist = ["Yang", "Zhou", "is", "writing"] ? ? # Using '+' def concat_plus(): result = "" for word in mylist: result += word + " " return result ? ? # Using 'join()' def concat_join(): return " ".join(mylist) ? ? # Directly concatenation without the list def concat_directly(): return "Yang" + "Zhou" + "is" + "writing"
import timeit ? print(timeit.timeit(concat_plus, number=10000)) # 0.002738415962085128 print(timeit.timeit(concat_join, number=10000)) # 0.0008482920238748193 print(timeit.timeit(concat_directly, number=10000)) # 0.00021425005979835987
如上所示,對于拼接字符串列表, join()
方法比在 for 循環(huán)中逐個(gè)添加字符串更快。
原因很簡單。一方面,字符串是 Python 中的不可變數(shù)據(jù),每個(gè) +=
操作都會導(dǎo)致創(chuàng)建一個(gè)新字符串并復(fù)制舊字符串,這會導(dǎo)致非常大的開銷。
另一方面,.join()
方法是專門為連接字符串序列而優(yōu)化的。它預(yù)先計(jì)算結(jié)果字符串的大小,然后一次性構(gòu)建它。因此,它避免了與循環(huán)中 +=
操作相關(guān)的開銷,因此速度更快。
但是,我們發(fā)現(xiàn)最快其實(shí)是直接用 +
拼接字符串,這是因?yàn)椋?/p>
- Python 解釋器可以在編譯時(shí)優(yōu)化字符串的連接,將它們轉(zhuǎn)換為單個(gè)字符串。因?yàn)闆]有循環(huán)迭代或函數(shù)調(diào)用,所以它是一個(gè)非常高效的操作。
- 由于所有字符串在編譯時(shí)都是已知的,因此 Python 可以非??焖俚貓?zhí)行此操作,比循環(huán)中的運(yùn)行時(shí)連接甚至優(yōu)化
.join()
方法快得多。
總之,如果需要拼接字符串列表,請選擇 join()
;如果直接拼接字符串,只需使用 +
即可。
創(chuàng)建列表的技巧
Python 中創(chuàng)建列表的兩種常見方法是:
- 使用函數(shù)
list()
[]
直接使用
我們來看下這兩種方法的性能
import timeit ? print(timeit.timeit('[]', number=10 ** 7)) # 0.1368238340364769 print(timeit.timeit(list, number=10 ** 7)) # 0.2958830420393497
結(jié)果表明,執(zhí)行 list()
函數(shù)比直接使用 []
要慢。
這是因?yàn)?是 []
字面語法( literal syntax ),而 list()
是構(gòu)造函數(shù)調(diào)用。毫無疑問,調(diào)用函數(shù)需要額外的時(shí)間。
同理,在創(chuàng)建字典時(shí),我們也應(yīng)該利用 {}
而不是 dict()
成員關(guān)系測試的技巧
成員關(guān)系測試的性能很大程度上取決于底層數(shù)據(jù)結(jié)構(gòu)
import timeit ? large_dataset = range(100000) search_element = 2077 ? large_list = list(large_dataset) large_set = set(large_dataset) ? ? def list_membership_test(): return search_element in large_list ? ? def set_membership_test(): return search_element in large_set ? ? print(timeit.timeit(list_membership_test, number=1000)) # 0.01112208398990333 print(timeit.timeit(set_membership_test, number=1000)) # 3.27499583363533e-05
如上面的代碼所示,集合中的成員關(guān)系測試比列表中的成員關(guān)系測試要快得多。
這是為什么呢?
- 在 Python 列表中,成員關(guān)系測試 (
element in list
) 是通過遍歷每個(gè)元素來完成的,直到找到所需的元素或到達(dá)列表的末尾。因此,此操作的時(shí)間復(fù)雜度為 O(n)。 - Python 中的集合是作為哈希表實(shí)現(xiàn)的。在檢查成員資格 (
element in set
) 時(shí),Python 使用哈希機(jī)制,其時(shí)間復(fù)雜度平均為 O(1)。
這里的技巧重點(diǎn)是在編寫程序時(shí)仔細(xì)考慮底層數(shù)據(jù)結(jié)構(gòu)。利用正確的數(shù)據(jù)結(jié)構(gòu)可以顯著加快我們的代碼速度。
使用推導(dǎo)式而不是 for 循環(huán)
Python 中有四種類型的推導(dǎo)式:列表、字典、集合和生成器。它們不僅為創(chuàng)建相對數(shù)據(jù)結(jié)構(gòu)提供了更簡潔的語法,而且比使用 for 循環(huán)具有更好的性能。
因?yàn)樗鼈冊?Python 的 C 實(shí)現(xiàn)中進(jìn)行了優(yōu)化。
import timeit ? ? def generate_squares_for_loop(): squares = [] for i in range(1000): squares.append(i * i) return squares ? ? def generate_squares_comprehension(): return [i * i for i in range(1000)] ? ? print(timeit.timeit(generate_squares_for_loop, number=10000)) # 0.2797503340989351 print(timeit.timeit(generate_squares_comprehension, number=10000)) # 0.2364629579242319
上面的代碼是列表推導(dǎo)式和 for 循環(huán)之間的簡單速度比較。如結(jié)果所示,列表推導(dǎo)式速度更快。
訪問局部變量速度更快
在 Python 中,訪問局部變量比訪問全局變量或?qū)ο蟮膶傩愿臁?/p>
import timeit ? ? class Example: def __init__(self): self.value = 0 ? ? obj = Example() ? ? def test_dot_notation(): for _ in range(1000): obj.value += 1 ? ? def test_local_variable(): value = obj.value for _ in range(1000): value += 1 obj.value = value ? ? print(timeit.timeit(test_dot_notation, number=1000)) # 0.036605041939765215 print(timeit.timeit(test_local_variable, number=1000)) # 0.024470250005833805
原理也很簡單:當(dāng)編譯一個(gè)函數(shù)時(shí),它內(nèi)部的局部變量是已知的,但其他外部變量需要時(shí)間來檢索。
優(yōu)先考慮內(nèi)置模塊和庫
當(dāng)我們討論 Python 的時(shí)候,通常指的是 CPython,因?yàn)?CPython 是 Python 語言的默認(rèn)和使用最廣泛的實(shí)現(xiàn)。
考慮到它的大多數(shù)內(nèi)置模塊和庫都是用C語言編寫的,C語言是一種更快、更低級的語言,我們應(yīng)該利用它的內(nèi)置庫,避免重復(fù)造輪子。
import timeit import random from collections import Counter ? ? def count_frequency_custom(lst): frequency = {} for item in lst: if item in frequency: frequency[item] += 1 else: frequency[item] = 1 return frequency ? ? def 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.005160166998393834 print(timeit.timeit(lambda: count_frequency_builtin(large_list), number=100)) # 0.002444291952997446
上面的程序比較了計(jì)算列表中元素頻率的兩種方法。正如我們所看到的,利用 collections
模塊的內(nèi)置計(jì)數(shù)器比我們自己編寫 for
循環(huán)更快、更簡潔、更好。
使用緩存裝飾器
緩存是避免重復(fù)計(jì)算和提高程序速度的常用技術(shù)。
幸運(yùn)的是,在大多數(shù)情況下,我們不需要編寫自己的緩存處理代碼,因?yàn)?Python 提供了一個(gè)開箱即用的裝飾器 — @functools.cache
。
例如,以下代碼將執(zhí)行兩個(gè)斐波那契數(shù)生成函數(shù),一個(gè)具有緩存裝飾器,但另一個(gè)沒有:
import timeit import functools ? ? def fibonacci(n): if n in (0, 1): return n return fibonacci(n - 1) + fibonacci(n - 2) ? ? @functools.cache def fibonacci_cached(n): if n in (0, 1): return n return fibonacci_cached(n - 1) + fibonacci_cached(n - 2) ? ? # Test the execution time of each function print(timeit.timeit(lambda: fibonacci(30), number=1)) # 0.09499712497927248 print(timeit.timeit(lambda: fibonacci_cached(30), number=1)) # 6.458023563027382e-06
可以看到 functools.cache
裝飾器如何使我們的代碼運(yùn)行得更快。
緩存版本的速度明顯更快,因?yàn)樗彺媪讼惹坝?jì)算的結(jié)果。因此,它只計(jì)算每個(gè)斐波那契數(shù)一次,并從緩存中檢索具有相同參數(shù)的后續(xù)調(diào)用
while 1 VS while True
如果要?jiǎng)?chuàng)建無限 while 循環(huán),我們可以使用 while True
or while 1
.
它們的性能差異通??梢院雎圆挥?jì)。但有趣的是, while 1
稍微快一點(diǎn)。
這是因?yàn)槭?1 字面量,但 True 是一個(gè)全局名稱,需要在 Python 的全局作用域中查找。所以 1 的開銷很小。
import timeit ? ? def loop_with_true(): i = 0 while True: if i >= 1000: break i += 1 ? ? def loop_with_one(): i = 0 while 1: if i >= 1000: break i += 1 ? ? print(timeit.timeit(loop_with_true, number=10000)) # 0.1733035419601947 print(timeit.timeit(loop_with_one, number=10000)) # 0.16412191605195403
正如我們所看到的,確實(shí) while 1
稍微快一些。
然而,現(xiàn)代 Python 解釋器(如 CPython )是高度優(yōu)化的,這種差異通常是微不足道的。所以我們不需要擔(dān)心這個(gè)可以忽略不計(jì)的差異。更不用說 while True
比 while 1
可讀性更好。
按需導(dǎo)入 Python 模塊
在 Python 腳本開頭導(dǎo)入所有模塊似乎是每個(gè)人都會這么做的操作,事實(shí)上我們沒有必要導(dǎo)入全部的模塊。如果模塊太大,則根據(jù)需要導(dǎo)入它是一個(gè)更好的主意。
def my_function(): import heavy_module # rest of the function
如上面的代碼所示,heavy_module
在函數(shù)中導(dǎo)入。這是一種“延遲加載”的思想:只有 my_function
被調(diào)用的時(shí)候該模塊才會被導(dǎo)入。
這種方法的好處是,如果 my_function
在腳本執(zhí)行期間從未調(diào)用過,則 heavy_module
永遠(yuǎn)不會加載,從而節(jié)省資源并減少腳本的啟動(dòng)時(shí)間。
以上就是讓Python代碼運(yùn)行更快的9個(gè)小技巧的詳細(xì)內(nèi)容,更多關(guān)于Python代碼運(yùn)行的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python正則表達(dá)式match和search用法實(shí)例
這篇文章主要介紹了python正則表達(dá)式match和search用法,實(shí)例分析了正則表達(dá)式中match和search的功能、定義及相關(guān)使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03Python自動(dòng)化測試之登錄腳本的實(shí)現(xiàn)
本文主要介紹了Python自動(dòng)化測試之登錄腳本的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02python 讀文件,然后轉(zhuǎn)化為矩陣的實(shí)例
下面小編就為大家分享一篇python 讀文件,然后轉(zhuǎn)化為矩陣的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04python+pytest接口自動(dòng)化之token關(guān)聯(lián)登錄的實(shí)現(xiàn)
公司某管理后臺系統(tǒng),登錄后返回token,接著去請求其他接口時(shí)請求頭中都需要加上這個(gè)token,否則提示請先登錄,今天通過本文給大家介紹下python+pytest接口自動(dòng)化之token關(guān)聯(lián)登錄的實(shí)現(xiàn),感興趣的朋友一起看看吧2022-04-04Python List cmp()知識點(diǎn)總結(jié)
在本篇內(nèi)容里小編給大家整理了關(guān)于Python List cmp()用法相關(guān)知識點(diǎn),有需要的朋友們跟著學(xué)習(xí)下。2019-02-02