Python有時用起來確實很慢,我敢打賭你肯定抱怨過這一點,尤其是那些用慣了C,C ++或Java的人。
但其實很多時候,Python的效率并沒有達到它應有的速度,有一些讓它馬達開足的小技巧,一起來學習吧!
1.避免使用全局變量
import mathsize = 10000
for x in range(size):
for y in range(size):
z = math.sqrt(x) + math.sqrt(y)
許多程序員一開始都會用Python語言編寫一些簡單的腳本。編寫腳本時,通常直接使用全局變量,就像上面這段代碼。
但由于全局變量和局部變量的實現(xiàn)方式不同,全局變量中定義的代碼要比在函數(shù)中定義的函數(shù)運行起來慢得多。把腳本語句放入函數(shù)中,通常運行速度可提高15%-30%。如下所示:
import mathdef main():
size = 10000
for x in range(size):
for y in range(size):
z = math.sqrt(x) +math.sqrt(y)main()
2.避免數(shù)據(jù)重復
避免無意義的數(shù)據(jù)復制
def main():
size = 10000
for _ in range(size):
value = range(size)
value_list = [x for x in value]
square_list = [x * x for x invalue_list]main()
這段代碼中,value_list完全沒有必要,這會創(chuàng)建不必要的數(shù)據(jù)結構或復制。
def main():
size = 10000
for _ in range(size):
value = range(size)
square_list = [x * x for x invalue]main()
另一個原因在于Python的數(shù)據(jù)共享機制過于偏執(zhí),沒有很好理解或信任內存模型,例如濫用copy.deepcopy()函數(shù)。我們可以刪除此類代碼中的復制操作。
交換值時無需使用中間變量
def main():
size = 1000000
for _ in range(size):
a = 3
b = 5
temp = a
a = b
b = tempmain()
上述代碼在交換值時創(chuàng)建了一個臨時變量temp。如果沒有中間變量,代碼會更加簡潔,運行速度也更快。
def main():
size = 1000000
for _ in range(size):
a = 3
b = 5
a, b = b, amain()
使用字符串聯(lián)方法join ,而不是‘+’
import string
from typing import Listdef concatString(string_list: List[str]) -》 str:
result = ‘’
for str_i in string_list:
result += str_i
return resultdef main():
string_list =list(string.ascii_letters * 100)
for _ in range(10000):
result =concatString(string_list)main()
另一要點是a+b對字符串進行拼接,由于在Python中字符串是不可變的對象,所以實際上a和b分別復制到了應用程序的新內存空間中。
因此,如果拼接n個字符串會產(chǎn)生“ n-1”個中間結果,則每個字符串都會產(chǎn)生應用和復制內存所需的中間結果,從而嚴重影響操作效率。
在使用join()串聯(lián)字符串時,首先計算需要應用的總內存空間,然后立即申請所需的內存,再把每個字符串元素復制到內存中。
import string
from typing import Listdef concatString(string_list: List[str]) -》 str:
return ‘’.join(string_list)defmain():
string_list = list(string.ascii_letters* 100)
for _ in range(10000):
result =concatString(string_list)main()
3.避免使用以下函數(shù)屬性
避免訪問模塊和函數(shù)屬性
import mathdef computeSqrt(size:int):
result = []
for i in range(size):
result.append(math.sqrt(i))
return resultdef main():
size = 10000
for _ in range(size):
result = computeSqrt(size)main()
use(屬性訪問運算符)會觸發(fā)特定方法,例如getattribute()和getattr(),這些方法將執(zhí)行字典操作,會產(chǎn)生額外的時間消耗。
通過使用import語句,可以消除屬性訪問:
from math import sqrtdefcomputeSqrt(size: int):
result = []
for i in range(size):
result.append(sqrt(i))
return resultdef main():
size = 10000
for _ in range(size):
result = computeSqrt(size)main()
前文中我們討論了局部變量可以比全局變量實現(xiàn)更快查找,對于經(jīng)常訪問的變量(如sqrt),可以通過更改為局部變量以加快操作速度。
import mathdef computeSqrt(size:int):
result = []
sqrt = math.sqrt
for i in range(size):
result.append(sqrt(i))
return resultdef main():
size = 10000
for _ in range(size):
result = computeSqrt(size)main()
避免類屬性訪問
import math
from typing import Listclass DemoClass:
def __init__(self, value: int):
self._value = value
def computeSqrt(self, size: int)-》 List[float]:
result = []
append = result.append
sqrt = math.sqrt
for _ in range(size):
append(sqrt(self._value))
return resultdef main():
size = 10000
for _ in range(size):
demo_instance = DemoClass(size)
result =demo_instance.computeSqrt(size)main()
避免的原理也適用于類的屬性,并且訪問self._value的速度要比訪問局部變量的速度要慢。通過把需要頻繁訪問的類屬性分配給局部變量,可以提高代碼執(zhí)行速度。
import math
from typing import Listclass DemoClass:
def __init__(self, value: int):
self._value = value
def computeSqrt(self, size: int)-》 List[float]:
result = []
append = result.append
sqrt = math.sqrt
value = self._value
for _ in range(size):
append(sqrt(value))
return resultdef main():
size = 10000
for _ in range(size):
demo_instance = DemoClass(size)
demo_instance.computeSqrt(size)main()
4.避免不必要的抽象
class DemoClass:
def __init__(self, value: int):
self.value = value@property
def value(self) -》 int:
return self._value@value.setter
def value(self, x: int):
self._value = xdef main():
size = 1000000
for i in range(size):
demo_instance = DemoClass(size)
value = demo_instance.value
demo_instance.value = imain()
每當使用其他處理層(例如裝飾器、屬性訪問、描述符)封裝代碼時,代碼運行的速度也會變慢。在大多數(shù)情況下,重新檢查是否有必要使用屬性訪問器定義是很有必要的。
使用getter/setter函數(shù)訪問屬性通常是被C/C++程序員遺忘的一種編碼樣式。如果確實沒有必要,就使用簡單屬性就好。
class DemoClass:
def __init__(self, value: int):
self.value = valuedef main():
size = 1000000
for i in range(size):
demo_instance = DemoClass(size)
value = demo_instance.value
demo_instance.value = imain()
5.選擇合適的數(shù)據(jù)結構
眾所周知,列表是Python中的動態(tài)數(shù)組。當預分配的內存空間用完時,會預分配一定的內存空間,然后繼續(xù)向其中添加元素。然后復制之前的所有原始元素,形成一個新的內存空間,在插入新元素之前銷毀先前的內存空間。
因此,如果頻繁添加或刪除,或者添加或刪除的元素數(shù)量太大,列表的效率就會變低,目前最好使用collections.deque。
此雙端隊列具有堆棧和隊列的特性,并且可以在兩端以O(1)復雜度執(zhí)行插入和刪除操作。
列表搜索操作非常耗時。當需要頻繁查找某些元素或按順序頻繁訪問這些元素時,保持列表 對象有序的情況下使用二分法,使用二進制搜索以提高搜索效率,但二進制搜索僅適用于有序元素。
另一個常見的要求是找到最小值或最大值。此時,可以使用heapq模塊列出轉換為堆的列表,因此獲取最小值的時間復雜度為O(1)。
6.循環(huán)優(yōu)化
使用 for 循環(huán)代替while 循環(huán)
def computeSum(size: int) -》int:
sum_ = 0
i = 0
while i 《 size:
sum_ += i
i += 1
return sum_def main():
size = 10000
for _ in range(size):
sum_ = computeSum(size)main()
要知道,Python中的for循環(huán)要比while循環(huán)快得多。
def computeSum(size: int) -》int:
sum_ = 0
for i in range(size):
sum_ += i
return sum_def main():
size = 10000
for _ in range(size):
sum_ = computeSum(size)main()
使用隱式for循環(huán),而不是顯式for循環(huán)
對于上面的示例,可以進一步使用隱式for循環(huán)替換顯式for循環(huán)
def computeSum(size: int) -》int:
return sum(range(size))def main():
size = 10000
for _ in range(size):
sum = computeSum(size)main()
減少內部循環(huán)的計算
from math import sqrtdef main():
size = 10000 for x in range(size):
for y in range(size):
z = sqrt(x) + sqrt(y)main()
在上述for循環(huán)中的代碼sqrt(x)中,在訓練期間每次都需要進行重新計算,這會增加時間消耗。
import mathdef main():
size = 10000for x in range(size):
sqrt_x = sqrt(x)
for y in range(size):
z = sqrt_x + sqrt(y)main()
7.使用 numba.jit
繼續(xù)遵循上述示例,并在此基礎上使用numba.jit。Python函數(shù)JIT可以編譯為機器代碼用以執(zhí)行,這能大大提高了代碼執(zhí)行速度。
import numba@numba.jit
def computeSum(size: float) -》 int:
sum = 0
for i in range(size):
sum += i
return sumdef main():
size = 10000
for _ in range(size):
sum = computeSum(size)main()
8.代碼優(yōu)化原則
上文已經(jīng)介紹了許多加速Python代碼的技術。在編寫代碼的過程中,我們需要了解代碼優(yōu)化的一些基本原理,這可是“實用知識”。
第一個基本原則就是不要過早優(yōu)化代碼。
許多人一開始編寫代碼時就致力于性能優(yōu)化,“加快正確程序的速度要比確??焖俪绦虻恼_運作容易得多?!眱?yōu)化代碼的前提是確保代碼可以正常工作。過早的優(yōu)化可能會忽略對總體性能指標的掌握,并且在獲得總體結果之前不要顛倒順序。
第二個基本原則是權衡優(yōu)化代碼的成本。
優(yōu)化代碼是有代價的,想要解決所有性能問題幾乎不可能。通常面臨的選擇是時間換空間或空間換時間,還需要考慮開發(fā)成本。
第三個原則是不要優(yōu)化無關緊要的部分。
如果優(yōu)化代碼的每個部分后,這些變更會讓代碼變得難以閱讀和理解。如果代碼運行緩慢,首先必須找到代碼運行緩慢的位置(通常是內部循環(huán)),重點優(yōu)化代碼運行緩慢的地方。對于其他位置,時間的損失影響很小。
優(yōu)化代碼,讓你的Python開足馬力,快去實踐一下吧!
-
編程
+關注
關注
88文章
3574瀏覽量
93545 -
優(yōu)化
+關注
關注
0文章
220瀏覽量
23859 -
python
+關注
關注
55文章
4774瀏覽量
84386
發(fā)布評論請先 登錄
相關推薦
評論