如何寫出高質量的Python代碼--做好優化--改進算法點滴做起


小伙伴你的程序還是停留在糊牆嗎?優化代碼可以顯示程序員的素質歐!

普及一下基礎了歐:

一層for簡寫:y = [1,2,3,4,5,6],[(i*2) for i in y ]       會輸出  [2, 4, 6, 8, 10, 12] ,標准形式為: [ 對i的操作 for i in 列表 ]

                       

 

兩層for循環:[對i的操作 for 單個元素 in 列表 for i in 單個元素],

例子:

  1. y_list = [ 'assss','dvv'] 
  2. [print(i) for y in y_list for i in y]

 

 輸出:a s s s s d v v

相當於:


y_list = ['assss','dvv']
for y in y_list:
for i in y:
print(i)

if簡寫:[True的邏輯 if 條件 else False的邏輯]
例子:
    1. y = 0
    2.  x = y+ 3 if y > 3 else y-1

for 與 if 的結合怎么簡寫:[判斷為True的i的操作 for i in 列表 if i的判斷 ]

  1. x = [ 1,2,3,4,5,6,7]
  2. [print(i) for i in x if i > 3 ]

Python 代碼優化常見技巧

  代碼優化能夠讓程序運行更快,它是在不改變程序運行結果的情況下使得程序的運行效率更高,根據 80/20 原則,實現程序的重構、優化、擴展以及文檔相關的事情通常需要消耗 80% 的工作量。優化通常包含兩方面的內容:減小代碼的體積,提高代碼的運行效率。

改進算法,選擇合適的數據結構

  一個良好的算法能夠對性能起到關鍵作用,因此性能改進的首要點是對算法的改進。在算法的時間復雜度排序上依次是:

O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)

 

定位程序性能瓶頸

  對代碼優化的前提是需要了解性能瓶頸在什么地方,程序運行的主要時間是消耗在哪里,對於比較復雜的代碼可以借助一些工具來定位,python 內置了豐富的性能分析工具,如 profile,cProfile 與 hotshot 等。其中 Profiler 是 python 自帶的一組程序,能夠描述程序運行時候的性能,並提供各種統計幫助用戶定位程序的性能瓶頸。Python 標准模塊提供三種 profilers:cProfile,profile 以及 hotshot。

使用 profile 進行性能分析

import profile
def profileTest():
Total =1;
for i in range(10):
Total=Total*(i+1)
print(Total)
return Total
if __name__ == "__main__":
profile.run("profileTest()")

分析功能函數:這個可以直接使用的,自己查就行了
  • ncalls:表示函數調用的次數;
  • tottime:表示指定函數的總的運行時間,除掉函數中調用子函數的運行時間;
  • percall:(第一個 percall)等於 tottime/ncalls;
  • cumtime:表示該函數及其所有子函數的調用運行的時間,即函數開始調用到返回的時間;
  • percall:(第二個 percall)即函數運行一次的平均時間,等於 cumtime/ncalls;
  • filename:lineno(function):每個函數調用的具體信息;
  • 如果需要將輸出以日志的形式保存,只需要在調用的時候加入另外一個參數。如 profile.run("profileTest()","testprof")。

    對於 profile 的剖析數據,如果以二進制文件的時候保存結果的時候,可以通過 pstats 模塊進行文本報表分析,它支持多種形式的報表輸出,是文本界面下一個較為實用的工具。使用非常簡單:

    import pstats 
    p = pstats.Stats('testprof') 
    p.sort_stats("name").print_stats()  

      其中 sort_stats() 方法能夠對剖分數據進行排序, 可以接受多個排序字段,如 sort_stats('name', 'file') 將首先按照函數名稱進行排序,然后再按照文件名進行排序。常見的排序字段有 calls( 被調用的次數 ),time(函數內部運行時間),cumulative(運行的總時間)等。此外 pstats 也提供了命令行交互工具,執行 python – m pstats 后可以通過 help 了解更多使用方式。

      對於大型應用程序,如果能夠將性能分析的結果以圖形的方式呈現,將會非常實用和直觀,常見的可視化工具有 Gprof2Dot,visualpytune,KCacheGrind 等,讀者可以自行查閱相關官網,本文不做詳細討論。

時間復雜度

算法的時間復雜度對程序的執行效率影響最大,在 Python 中可以通過選擇合適的數據結構來優化時間復雜度,如 list 和 set 查找某一個元素的時間復雜度分別是 O(n)和 O(1)。不同的場景有不同的優化方式,總得來說,一般有分治,分支界限,貪心,動態規划等思想。

循環優化

每種編程語言都會強調需要優化循環。對循環的優化所遵循的原則是盡量減少循環過程中的計算量,有多重循環的盡量將內層的計算提到上一層。當使用 Python 的時候,你可以依靠大量的技巧使得循環運行得更快。然而,開發者經常漏掉的一個方法是:

避免在一個循環中使用點操作。每一次你調用方法 str.upper,Python 都會求該方法的值。然而, 如果你用一個變量代替求得的值,值就變成了已知的,Python 就可以更快地執行任務。優化循環的關鍵,是要減少 Python 在循環內部執行的工作量,因為 Python 原生的解釋器在那種情況下,真的會減緩執行的速度。(注意:優化循環的方法有很多,這只是其中的一個。例如,許多程序員都會說,列表推導是在循環中提高執行速度的最好方式。這里的關鍵是,優化循環是程序取得更高的執行速度的更好方式之一。)

為進行循環優化前

from time import time
t = time()
lista = [1,2,3,4,5,6,7,8,9,10]
listb =[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,0.01]
for i in range (1000000):
for a in range(len(lista)):
for b in range(len(listb)):
x=lista[a]+listb[b]
print("total run time:")
print(time()-t)
  #現在進行如下優化,將長度計算提到循環外,range 用 xrange 代替,同時將第三層的計算 lista[a] 提到循環的第二層。

  # 循環優化后

from time import time

t = time()
lista = [1,2,3,4,5,6,7,8,9,10]
listb =[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,0.01]
len1=len(lista)
len2=len(listb)
for i in xrange (1000000):
for a in xrange(len1):
temp=lista[a]
for b in xrange(len2):
x=temp+listb[b]
print("total run time:")
print(time()-t)
  #上述優化后的程序其運行時間縮短為 102.171999931。在清單 4 中 lista[a] 被計算的次數為 1000000*10*10,而在優化后的代碼中被計算的次數為 1000000*10,計算次數大幅度縮短,因此性能有所提升。

  #充分利用 Lazy if-evaluation 的特性

  #python 中條件表達式是 lazy evaluation 的,也就是說如果存在條件表達式 if x and y,在 x 為 false 的情況下 y 表達式的值將不再計算。因此可以利用該特性在一定程度上提高程序效率。

  #利用 Lazy if-evaluation 的特性

from time import time

t = time()
abbreviations = ['cf.', 'e.g.', 'ex.', 'etc.', 'fig.', 'i.e.', 'Mr.', 'vs.']
for i in range (1000000):
for w in ('Mr.', 'Hat', 'is', 'chasing', 'the', 'black', 'cat', '.'):
if w in abbreviations:
#if w[-1] == '.' and w in abbreviations:
pass
print("total run time:")

print(time()-t)

  #在未進行優化之前程序的運行時間大概為 8.84,如果使用注釋行代替第一個 if,運行的時間大概為 6.17。

並行編程

因為 GIL 的存在,Python 很難充分利用多核 CPU 的優勢。但是,可以通過內置的模塊
multiprocessing 實現下面幾種並行模式:
多進程:對於 CPU 密集型的程序,可以使用 multiprocessing 的 Process,Pool 等封裝好的類, 通過多進程的方式實現並行計算。但是因為進程中的通信成本比較大,對於進程之間需要大量數據交互的程序效率未必有大的提高。
多線程:對於 IO 密集型的程序,multiprocessing.dummy 模塊使用 multiprocessing 的接口封裝 threading,使得多線程編程也變得非常輕松(比如可以使用 Pool 的 map 接口,簡潔高效)。
分布式:multiprocessing 中的 Managers 類提供了可以在不同進程之共享數據的方式,可以在此基礎上開發出分布式的程序。
不同的業務場景可以選擇其中的一種或幾種的組合實現程序性能的優化。

使用性能分析工具

set 的用法

set 的 union,intersection,difference 操作要比 list 的迭代要快。因此如果涉及到求 list 交集,並集或者差的問題可以轉換為 set 來操作。

 

 字符串的優化

  python 中的字符串對象是不可改變的,因此對任何字符串的操作如拼接,修改等都將產生一個新的字符串對象,而不是基於原字符串,因此這種持續的 copy 會在一定程度上影響 python 的性能。對字符串的優化也是改善性能的一個重要的方面,特別是在處理文本較多的情況下。字符串的優化主要集中在以下幾個方面:

  • 在字符串連接的使用盡量使用 join() 而不是 +:在代碼清單 7 中使用 + 進行字符串連接大概需要 0.125 s,而使用 join 縮短為 0.016s。因此在字符的操作上 join 比 + 要快,因此要盡量使用 join 而不是 +。
  • 當對字符串可以使用正則表達式或者內置函數來處理的時候,選擇內置函數。如 str.isalpha(),str.isdigit(),str.startswith(('x', 'yz')),str.endswith(('x', 'yz'))
  • 對字符進行格式化比直接串聯讀取要快。4、
  • 使用列表解析(list comprehension)和生成器表達式(generator expression)。列表解析要比在循環中重新構建一個新的 list 更為高效,因此我們可以利用這一特性來提高運行的效率。
  • 如果需要交換兩個變量的值使用 a,b=b,a 而不是借助中間變量 t=a;a=b;b=t;
  • 使用局部變量,避免"global" 關鍵字。python 訪問局部變量會比全局變量要快得多,因 此可以利用這一特性提升性能。

    6.在耗時較多的循環中,可以把函數的調用改為內聯的方式;

    7.使用級聯比較 "x < y < z" 而不是 "x < y and y < z";

    8.while 1 要比 while True 更快(當然后者的可讀性更好);

    9.build in 函數通常較快,add(a,b) 要優於 a+b。

1.Python實現全排列

方案一:

  1.  
    a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9]
  2.  
    result = list(itertools.permutations(a, 9))

方案二:
上面是使用python的內建函數itertools.permutations對於只有9個元素的全排列速度上是驚人的。
如果是我們自己來寫全排列邏輯,可以是下面這樣的: 


def full_permutation(l):
if (len(l) <= 1):
return [l]

r = []
for i in range(len(l)):
s = l[:i] + l[i + 1:] # 將l的前三項以及l的第i+1后的字串賦給s
p = full_permutation(s)
for x in p:
r.append(l[i: i + 1] + x)

return r

所有在項目中還是建議使用Python內建的全排列函數,其中的第二個參數可以是1-9之間的任何一個整數,非常方便。2.

2、遍歷文件夾下所有子文件夾和文件
在Python中可以很方便地對文件目錄進行循環遍歷,檢查文件及目錄,代碼如下:
import os
import os.path


def cycle_visiting(root_dir=None):
for parent, folder_names, file_names in os.walk(root_dir):
for folder_name in folder_names:
print('folder: ' + folder_name)
for file_name in file_names:
print('file: ' + os.path.join(parent, file_name))

3.針對字符串的進制轉化

  如果你有其他語言的編程功底,可能你已對進制轉化十分熟悉。不過我這里要說的進制轉化可不是簡單從十進制轉化為二進制或是轉成十六進制。下面你可以試着來解決下面幾個問題:
  a.將a = 'ff'的十六進制數轉成十進制的255
  b.將a = 14的十進制數轉成十六進制的0e
解決方法:
  a.這里需要用一個參數指明原來的進制數
decstr = int(a, 16)
  b.這里需要用一個切片操作用來去掉前綴'0x'
decstr = hex(a)[2:]
if len(decstr) % 2 == 1:
    decstr = '0' + decstr

4.IP的點分型和整形數字之間的轉化

  首先需要import兩個模塊:socket和struct
  1.將ip1 = '172.123.156.241'轉化為ip2 = 2893782257L
    ip2 = socket.ntohl(struct.unpack("I",socket.inet_aton(ip1))[0])
  2.將ip2 = 2893782257L轉化為ip1 = '172.123.156.241'
    ip1 = socket.inet_ntoa(struct.pack('I',socket.htonl(ip2)))

5.Python獲得Linux控制台中的輸出信息

  可以通過兩種方式來解決這個問題,分別如下:
  方法一:
  1.  
    import subprocess
  2.  
    import os
  3.  
    output = os.popen( 'cat /proc/cpuinfo | grep model')
  4.  
    print output.read()
  方法二:
  1.  
    status, model = commands.getstatusoutput(shell)
  2.  

 

6.使用enumerate()函數獲取序列迭代的索引和值

  有時我們在對序列進行迭代的時候,不單單是只要知道序列中的值,還想要知道這個值在序列的什么位置。

  如果要使用代碼實現,的確不難。不過會顯得多余,因為Python已經為我們做了這些工作。如下:

def test_enumerate():
array = [1, 2, 3, 4, 5, 6]
for index, data in enumerate(array):
print("%d: %d" % (index, data))
t = test_enumerate()

7.i+=1與++i有區別

 

  我們知道Python中是不支持自增操作的。所以,你是不是就會以為這里的++i會拋出一個語法錯誤呢?

  很可惜,這里並不會拋出語法錯誤。對於i++的確是有這樣的問題,不過對於++i則不會。例如在PyCharm編輯器中,我們可以看到如下現象:

  我們可以看到PyCharm對a++拋出了一個錯誤提示,對於++a則是一個警告。

  原因是在Python里,++a會被看成是+(+a),也就是說,“+”被理解成了一個正符號。所以,++a的結果還是a。同理,--a的結果也是a.

2.遍歷文件夾下所有子文件夾和文件

  在Python中可以很方便地對文件目錄進行循環遍歷,檢查文件及目錄,代碼如下:

 

 

  1.  
    import os
  2.  
    import os.path
  3.  
     
  4.  
    def cycle_visiting(root_dir=None):
  5.  
    for parent, folder_names, file_names in os.walk(root_dir):
  6.  
    for folder_name in folder_names:
  7.  
    print 'folder: ' + folder_name
  8.  
    for file_name in file_names:
  9. 定位程序性能瓶頸

      對代碼優化的前提是需要了解性能瓶頸在什么地方,程序運行的主要時間是消耗在哪里,對於比較復雜的代碼可以借助一些工具來定位,python 內置了豐富的性能分析工具,如 profile,cProfile 與 hotshot 等。其中 Profiler 是 python 自帶的一組程序,能夠描述程序運行時候的性能,並提供各種統計幫助用戶定位程序的性能瓶頸。Python 標准模塊提供三種 profilers:cProfile,profile 以及 hotshot

    定位程序性能瓶頸

      對代碼優化的前提是需要了解性能瓶頸在什么地方,程序運行的主要時間是消耗在哪里,對於比較復雜的代碼可以借助一些工具來定位,python 內置了豐富的性能分析工具,如 profile,cProfile 與 hotshot 等。其中 Profiler 是 python 自帶的一組程序,能夠描述程序運行時候的性能,並提供各種統計幫助用戶定位程序的性能瓶頸。Python 標准模塊提供三種 profilers:cProfile,profile 以及 hotshot

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM