Python代碼優化及技巧筆記(二)

概述

這裏是記錄一些本人在開發過程中遇到的一些細節及代碼優化問題,希望與君共勉。


版權說明

著作權歸作者所有。
商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
作者:Coding-Naga
發表日期: 2016年3月17日
鏈接:http://blog.csdn.net/lemon_tree12138/article/details/50854673
來源:CSDN
更多內容:分類 >> Thinking In Python


目錄

奇技淫巧


Python 代碼獲取命令行輸出

這裏可以通過subprocess模塊中的Popen, PIPE來實現。
比如有這如下代碼,就可以嘗試捕獲控制檯輸出的文本信息:

from subprocess import Popen, PIPE

label = "Hello, Shell."
print(label)

f = Popen(("python", "catch_output.py"), stdout=PIPE).stdout
print("Catch Output: {0}".format(f.readline()))

上面的代碼中我們可以打印出自身的控制檯輸出,還有通過捕獲輸出獲得的輸出信息。如下:

Hello, Shell.
Catch Output: Hello, Shell.

不要將表達式作爲函數的默認參數

在Python有一個比較基礎也是比較重要的特性,那就是在python的函數中,我們可以給參數指定一個默認的值。但是,這是有一個小小的陷阱。對於新手來說可能會經常引起困擾,比如我們像下面這樣編寫python代碼:

def test_args(array=[]):
    array.append("Bob")
    return array

以上的代碼中正常情況下,是沒有什麼問題的,因爲的的確確可以打印出“[Bob]”。可是,問題是出現在我們重複的調用上。怎麼說?就以上面的test_args()方法,我們進行三次重複調用。打印的結果卻是:

['Bob']
['Bob', 'Bob']
['Bob', 'Bob', 'Bob']

WTF!
這是怎麼會回事呢?因爲,可選參數默認值的設置在 Python 中只會被執行一次
怎麼來理解?很簡單,也就是我們的參數默認值只有在對此函數進行定義,並且在函數調用的時候不傳遞此參數的值的時候,才被認爲需要對其進行默認值的賦值。
想要修改此函數也很簡單,代碼如下:

def test_args(array=None):
    if array is None:
        array = []
    array.append("Bob")
    return array

指定異常代碼塊(exception block)的參數

在python2.x中,我們知道可以使用逗號來進行異常參數的指定。如下:

def fun():
    try:
        print("Hello, Exception")
    except Exception, e:
        print(e)
    pass

可是在python3.x中,這種寫法卻存在着語法上的錯誤。不過對於python2.x與python3.x來說,都可以使用as來進行指定。如下:

def fun():
    try:
        print("Hello, Exception")
    except Exception as e:
        print(e)
    pass

所以,對於可能存在版本差異的項目來說,最好還是使用as來指定參數更爲妥當。


在遍歷列表時更改列表

正常情況下我們很難在遍歷列表的過程中能列表進行修改,這一條不僅在Python中適用,在Java中也存在着同樣的規則。比如,如下的寫法就會拋出一些異常。

def test_list_modify():
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8]
    odd = lambda x: bool(x % 2)
    for i in xrange(len(a)):
        if odd(a[i]):
            a.remove(i)
    print(a)

上面的代碼一定會拋出異常,異常如下所示:

Traceback (most recent call last):
  File "E:/workspace/src/Python/Demo/SimpleDemo-python/test/test_demo.py", line 106, in <module>
    test_list_modify()
  File "E:/workspace/src/Python/Demo/SimpleDemo-python/test/test_demo.py", line 76, in test_list_modify
    if odd(a[i]):
IndexError: list index out of range

原因也很容易找到,上面的代碼中,我們嘗試去修改列表的長度,這樣會導致循環的過程中,訪問數組的下標會超出修改後的列表長度。從而拋出以上異常信息。
不過,我們可以利用Python語言自身優雅的編程範式。修改後的代碼如下:

def test_list_modify():
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8]
    odd = lambda x: bool(x % 2)
    a[:] = [n for n in a if not odd(n)]
    print(a)

更優雅地打印出 JSON

在Python中,json模塊爲我們提供了非常好的json對象的打印接口——json.dumps()
當然json.dumps()中傳入的是json這個對象,而不是原始的json字符串。想要實現將原始json字符串優雅地打印出來,我們可以對其進行二次封裝。

  1. 通過**json.loads()**將json字符串轉化成json對象;
  2. 通過**json.dumps()**將json字符串優雅地打印出來.
import json

json_data = "{\"status\": \"OK\", \"count\": 2, \"results\": [{\"age\": 27, \"name\": \"Oz\", \"lactose_intolerant\": true}," \
            " {\"age\": 29, \"name\": \"Joe\", \"lactose_intolerant\": false}]}"

def parser():
    json_obj = json.loads(json_data)
    show(json_obj)
    pass

def show(json_obj):
    print(json.dumps(json_obj, indent=4))
    pass

if __name__ == '__main__':
    parser()
    pass

打印的結果如下:
這裏寫圖片描述


_init_.py 的功能

有時我們需要向一個自定義模塊中導入很多其他模塊的內容,這樣會讓代碼顯得有一些臃腫。不過,好在我們可以通過_init_.py這個文件來解決這個問題。做法是將導入模塊的代碼放在_init_.py這個文件中,再在目標文件中導入_init_.py這一個模塊中的全部內容即可。
在_init_.py中有如下代碼:

import os
import time
import datetime as date

測試文件中的調用代碼如下:

from __init__ import *

print(time.ctime())
print(date.date.day)
print(os.system("python test_init.py"))

當然,你也可以選擇一個一個地導入,如下:

from __init__ import sys
from __init__ import getopt

from __init__ import pack
from __init__ import pehelp
from __init__ import PEParser
from __init__ import Signature

使用 _import_ 函數動態加載模塊

有時我們需要加載一些不確定的模塊,所以我們不好在一開始就對其進行指定,因爲這樣可能會導致加載的模塊過多。還有我們不能導入帶有"-" (hyphens)的模塊。不過在在Python中我們可以動態加載一些模塊,使用的是內建的_import_。使用方式如下:

import glob
import os

modules = []
for module_file in glob.glob("*-plugin.py"):
    try:
        module_name, ext = os.path.splitext(os.path.basename(module_file))
        module = __import__(module_name)
        modules.append(module)
    except ImportError:
        pass  # ignore broken modules

# say hello to all modules
for module in modules:
    module.hello()

從上面的代碼中,也可以很明顯地看出這是一種延遲導入模塊的方式。


使用 reload 函數

我們假設一種情形:如果你有一個大項目,這個項目在加載啓動的時間很長,又或是不可以給你多次加載的機會。可是,對於後臺程序,我們需要對其進行更新,而更新的內容又是即時更新到項目中去,這需要怎麼做呢?Python的做法真是太棒了,這就是reload函數。此函數接收一個模塊爲參數,即重新加載此模塊。
比如,我們需要每隔3秒循環調用hello模塊中的say_hello()函數。代碼是這樣的:

from time import sleep
import hello

while True:
    hello.say_hello()
    sleep(3)
    pass

hello.py的代碼如下:

def say_hello():
    print("Hello reload.")
    pass

如果有一天,這個hello模塊的say_hello()函數需要被更改,我們無計可施,只能停止之前程序的運行,等修改完這個hello模塊之後,再運行程序。可是,對於大型項目來說,這一點可能無法做到。這時,可以使用Python中的reload()函數。如下:

from time import sleep
import hello

while True:
    reload(hello)
    hello.say_hello()
    sleep(3)
    pass

當我們在程序運行的過程中,修改了hello模塊的say_hello()打印信息。程序運行的結果如下:

Hello reload.
Hello reload.
Hello reload.
Hello reload, and this is modify...
Hello reload, and this is modify...

random 模塊的其他用途

關於這一點,只是針對一些從其他語言上轉過來的“混球”們。比如,我就是從Java轉過來的(這裏說轉過來稍微有一些不妥,畢竟我還是一直人事Java開發的),那麼我之前可能會這樣來寫隨機生成一段字符串:

public static String randomString(int length) {
        StringBuffer buffer = new StringBuffer();
        RandomUtils random = new RandomUtils(26);
        for (int i = 0; i < length; i++) {
            char c = (char) (random.nextInt() + 'a');
            buffer.append(c);
        }
        
        return buffer.toString();
    }

可是,python已經對此進行了封裝,而我還是傻不拉幾地按照之前的邏輯來編寫代碼,好水!
好了,來看看python是怎麼做的吧:

def random_string(min, max):
    length = int(random.uniform(min, max))
    print(length)
    label = string.join(random.sample(
        ['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm',
         'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'], length))\
        .replace(' ', '')
    return label

只是這裏有一點需要注意,就是max的最大值不要超過下面列表的長度。
另外還有一些如下:

在一段字符串中隨選擇一個字符

print random.choice('abcdefghijklmnopqrstuvwxyz!@#$%^&*()')

在一段字符串中隨選擇若干個字符,形成列表

print random.sample('zyxwvutsrqponmlkjihgfedcba', 5)

隨機選取字符串

random.choice(['剪刀', '石頭', '布'])

打亂排序

items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
random.shuffle(items)
print(items)

對數據庫的批量操作

昨天在做一個數據庫的測試,需要一個千萬級的數據量作支持。這個不可能人爲一條一條添加了,所以我就編寫了代碼進行自動化生成。可是,對數據庫後面的插入操作很慢很慢,我把程序放在公司跑,跑了一個晚上才生成了百萬級數據,這個不行!我總不能花上一個星期來生成數據吧。你是不是想說使用多線程,是的這是一個辦法,可是不夠完美。想到在寫Java程序時,有一個批量更新操作。所以,這裏就Google了一下,這個倒不是什麼難事。於是有了下面的批量操作的方法,如下:

這是一個批量插入的核心方法

def insert_many(self, sql, values):
    """
    全部的批量插入操作
    """
    flag = False
    if self.__connection:
        try:
            self.__cursor.executemany(sql, values)
            self.__connection.commit()
            flag = True
        except Exception as e:
            flag = False
            print("Update database exception, {0}".format(e))
    return flag

對上面方法的調用也很簡單,下面是一個實際程序中的一段測試代碼,如下:

def init_labels():
    max_length = 10000000
    db = DatabaseServer("school")
    values = []
    sql = "INSERT INTO labels(label) VALUES(%s);"
    for index in xrange(max_length):
        if (index + 1) % 1000 == 0:
            db.insert_many(sql, values)
            values = []
            print("index: {0}".format(index + 1))
        values.append(random_string())
    db.insert_many(sql, values)
    db.close()
    pass

Ref

  • http://codingpy.com/article/top-10-mistakes-that-python-programmers-make/
  • http://www.vaikan.com/improving-your-python-productivity/
  • 《Python標準庫》

徵集

如果你也需要使用ProcessOn這款在線繪圖工具,可以使用如下邀請鏈接進行註冊:
https://www.processon.com/i/56205c2ee4b0f6ed10838a6d

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章