Python程式設計方面的一些技巧

NullSpider發表於2019-02-27

有補充的可以聯絡我

如果有更好的技巧或者是一些心得也可以分享給我~

  • Author: Leo
  • Contributor: Leo
  • Wechat: Leo-sunhailin
  • E-mail: 379978424@qq.com
  • 會一直維護下去,待補充…


1. list切片的技巧 somelist[start:end:stride]


test = [1, 2, 3, 4, 5]

# 從索引最開始到結束,每隔兩個取出一個
# 實際上就是肉眼數的奇數位,索引的偶數位
odds = test[::2]
print(odds) # 結果 -> [1, 3, 5]

# 從索引第一位到結束,每隔兩個取出一個
# 實際上就是肉眼數的偶數位,索引的奇數位
evens = test[1::2]
print(evens) # 結果 -> [2, 4]

# 對於byte的字串來說還有神奇的特效(only byte)
byte_str = b`abcd`
print(byte_str[::-1]) # 結果 -> dcba

# 儘量不要很複雜的切片方式,儘量能夠多次解決複雜
# 不要同時出現start end stride三個引數複製程式碼

2. 多使用列表表示式

# 1. 例子沒有
# list, dict, set都有對列表表示式的支援
# 列表表示式代替使用map和filter,可以避免寫lambda函式

# 2. 例子
# 列表表示式處理多重for迴圈
martix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat_list = [x for row in martix for x in row]
print(flat_list) # 結果 -> [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 拆解程式碼大致就是:
new_list = []
for row in martix:
    for x in row:
        new_list.append(x)

# 3.例子
# 從一個列表中找出一個數字大於4且是偶數的
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 第一種
b = [x for x in a if x > 4 if x % 2 ==0]
# 第二種
c = [x for x in a if x > 4 and x % 2 ==0]
# 結果都是一樣的,只是判斷上的區別

# Tips:
# 列表表示式能用就儘量用,可以縮減一些程式碼量,但是不要寫的過於複雜
# 太複雜的表示式,查bug更難找,而且也不利於別人進行維護複製程式碼

3. 資料量大的時候儘量使用生成器表示式代替列表表示式

# 原因很簡單,列表表示式需要開闢較大的記憶體空間進行儲存
"""
官方解釋: 生成器表示式,它是對推導和生成器的一種泛化。
生成器在執行時不會將整個輸出序列呈現出來,而是會估值為迭代器,
這個迭代器每次可以根據生成器表示式產生一項資料。
"""

# 1. 例子
# 讀取一個多行文字,統計每一行的長度
it = (len(x) for x in open(`<檔案路徑>/<檔名>.<檔案字尾>`)
print(it) # 結果 -> <generator object <genexpr> at 某個記憶體地址>

# 需要輸出時就用next
print(next(it))複製程式碼

4. 使用enumerate代替range

# 原因很簡單,封裝的比range好
test = [`vannila`, `chocolate`, `pecan`, `strawberry`]
# 第一種
for i , flavor in enumerate(test):
    print(`%d: %s` % (i + 1, flavor))
# 結果如下
>>>
1: vannila
2: chcolate
3: pecan
4: strawberry
# 解釋一下: i + 1 實際上就是為了更好看,如果不i + 1,實際上就是索引的位置.

# 第二種
for i , flavor in enumerate(test, 1):
    print(`%d: %s` % (i, flavor))

# 結果一樣, 實際上就是在enumerate的函式中已經封裝了
# 顯得更簡便,而且同時能輸出索引位置或輸出實際中的計數位複製程式碼

5. 合理利用try/except/else/finally

# except的例子就不說了,用過都知道
# 直接上else的例子

# 函式的功能就是: 載入一個json,返回對應key的值
def load_json_key(data, key)
    try:
        result_dict = json.loads(data)
    except ValueError as err:
        raise KeyError from err
    else:
        return result_dict[key]

"""
解釋:
    實際上這個else可要可不要,因為寫在try裡面也是可以的
    但是如果為了程式碼的可閱讀性,else是一個很必要的東西
    程式碼閱讀上就知道try裡面是程式碼中可能存在錯誤的的地方
    如果寫在一堆的話,還有錯誤,那你的except就要增加多幾個了
    而且寫程式碼也並不建議巢狀try-except,畢竟那不服合程式碼的風格.
"""

# finally的話,實際上就一個程式碼清理的過程
# 一般用在IO或者資料庫讀寫上,用來關閉流, 例子就不寫了.複製程式碼

6. 執行緒方面的—使用concurrent.futures,實現平行計算

# coding: utf-8

from concurrent.futures import ThreadPoolExecutor as Pool
# from multiprocessing import Pool
import requests
import time

urls = ["http://www.gzcc.cn", "http://jwxw.gzcc.cn",
        "http://www.baidu.com", "http://www.qq.com",
        "http://www.163.com", "http://www.sohu.com"]


def task(url, timeout=10):
    return requests.get(url=url, timeout=timeout)


if __name__ == `__main__`:
    start_1 = time.time()
    for each_url in urls:
        response = task(url=each_url)
        print(`%s, %s` % (response.url, response.status_code))
    end_1 = time.time()
    print("順序執行的時長: %f" % (end_1 - start_1))

    start_2 = time.time()
    pool = Pool(max_workers=4)
    # pool = Pool(processes=4)
    processes = pool.map(task, urls)
    for each_process in processes:
        print(`%s, %s` % (each_process.url, each_process.status_code))
    end_2 = time.time()
    print("並行執行的時長: %f" % (end_2 - start_2))

# 第一種的結果: 1.4s
# 第二種的結果: 0.4s
# 結果的提升是肯定有的,但是和網路情況有關係。

# 關於匯入的包 concurrent.future
# 對於這個包裡面的ThreadPoolExecutor和multiprocessing的Pool對比,作用實際上差不多,具體時間差異我還沒怎麼測試過.
# 但是如果你認真看原始碼的話會發現,實際上future的包在process的那一塊也是呼叫multiprocessing的
# 按照原始碼的意思就是在子執行緒中執行多個python的直譯器,從而實現並行.
# 但是一般的程式碼或者多執行緒爬蟲上基本體會不出,因為爬蟲的核心還是在網路速度上,而一般的程式碼也沒必要
# 除非計算矩陣或者其他的需要巨大計算量的時候再考慮使用.複製程式碼

7. 與分析方面有關的—重視精度時使用decimal

# 例子
rate = 1.45
seconds = 3 * 60 + 42
cost = rate * seconds / 60
print(cost) # 結果很奇怪: 5.364999999999999

# 這時候可能會想到用round的函式
# 1、如果這時你的需求是不足一分也當一分的計算
#    類似於向上取整round的方法會把結果變成5.36而不是5.37
# 2、如果沒有要求的時候使用round就可以了
# 
# 針對第一種問題,就引出一個decimal的方法了,改寫一下
from decimal import Decimal

rate = Decimal(`1.45`)
seconds = Decimal(3 * 60 + 42)
cost = rate * seconds / Decimal(60)
print(cost) # 結果 -> 5.365

# 重點說下這裡。
# 有個很奇怪的地方,有興趣的可以研究下為什麼。
# 把rate的那個1.45去掉單引號包圍,再執行就明白為什麼奇怪了

# 反觀結果, 5.365貌似也不是我們想要的,這裡就引入一個quantize方法了
# 在程式碼頂部加上 
from decimal import ROUND_UP

# cost還是剛剛的cost
rounded = cost.quantize(Decimal(`0.01`), rounding=ROUND_UP)
print(rounded) # 結果 -> 5.37

# 兜兜轉轉就到結果這裡了.一般這些情況都是對精度要求很高才需要,一般情況就當看不見好了.複製程式碼

8. 協作開發的時候儘量不要寫import *

你的程式碼在導包的時候寫了import *,你自己開發是很明白有什麼方法的.
但是在協作開發或者開源專案的時候儘量避免.
因為其他開發者並不知道里面的方法是幹啥的.複製程式碼

9. 配置檔案獨立化

# 例如一些資料庫的配置,selenium的webdriver的配置,甚至開發的模式配置可以通過一些json格式的配置檔案進行維護.
# 好處1: 在於這樣管理專案不用"東奔西跑",為了一個全域性變數找半天
# 好處2: 在協同開發的時候,可以不用變動的程式碼的情況下,根據自己的開發環境確定一些全域性配置
# 缺點的話,實際上也算不上缺點.就是每次都要讀取一次配置檔案,程式碼的速度會減慢一點點,但是並不礙事.複製程式碼

回到頂部: 傳送門

相關文章