国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Python 開發(fā)工具集:關(guān)于文檔、測試、調(diào)試、程序的優(yōu)化和分析

shenhualong / 2393人閱讀

摘要:通過單元測試,開發(fā)者可以為構(gòu)成程序的每一個(gè)元素例如,獨(dú)立的函數(shù),方法,類以及模塊編寫一系列獨(dú)立的測試用例。在每個(gè)測試中,斷言可以用來對不同的條件進(jìn)行檢查。當(dāng)退出調(diào)試器時(shí),調(diào)試器會(huì)自動(dòng)恢復(fù)程序的執(zhí)行。

Python已經(jīng)演化出了一個(gè)廣泛的生態(tài)系統(tǒng),該生態(tài)系統(tǒng)能夠讓Python程序員的生活變得更加簡單,減少他們重復(fù)造輪的工作。同樣的理念也適用于工具開發(fā)者的工作,即便他們開發(fā)出的工具并沒有出現(xiàn)在最終的程序中。本文將介紹Python程序員必知必會(huì)的開發(fā)者工具。

對于開發(fā)者來說,最實(shí)用的幫助莫過于幫助他們編寫代碼文檔了。pydoc模塊可以根據(jù)源代碼中的docstrings為任何可導(dǎo)入模塊生成格式良好的文檔。Python包含了兩個(gè)測試框架來自動(dòng)測試代碼以及驗(yàn)證代碼的正確性:1)doctest模塊,該模塊可以從源代碼或獨(dú)立文件的例子中抽取出測試用例。2)unittest模塊,該模塊是一個(gè)全功能的自動(dòng)化測試框架,該框架提供了對測試準(zhǔn)備(test fixtures), 預(yù)定義測試集(predefined test suite)以及測試發(fā)現(xiàn)(test discovery)的支持。

trace模塊可以監(jiān)控Python執(zhí)行程序的方式,同時(shí)生成一個(gè)報(bào)表來顯示程序的每一行執(zhí)行的次數(shù)。這些信息可以用來發(fā)現(xiàn)未被自動(dòng)化測試集所覆蓋的程序執(zhí)行路徑,也可以用來研究程序調(diào)用圖,進(jìn)而發(fā)現(xiàn)模塊之間的依賴關(guān)系。編寫并執(zhí)行測試可以發(fā)現(xiàn)絕大多數(shù)程序中的問題,Python使得debug工作變得更加簡單,這是因?yàn)樵诖蟛糠智闆r下,Python都能夠?qū)⑽幢惶幚淼腻e(cuò)誤打印到控制臺中,我們稱這些錯(cuò)誤信息為traceback。如果程序不是在文本控制臺中運(yùn)行的,traceback也能夠?qū)㈠e(cuò)誤信息輸出到日志文件或是消息對話框中。當(dāng)標(biāo)準(zhǔn)的traceback無法提供足夠的信息時(shí),可以使用cgitb 模塊來查看各級棧和源代碼上下文中的詳細(xì)信息,比如局部變量。cgitb模塊還能夠?qū)⑦@些跟蹤信息以HTML的形式輸出,用來報(bào)告web應(yīng)用中的錯(cuò)誤。

一旦發(fā)現(xiàn)了問題出在哪里后,就需要使用到交互式調(diào)試器進(jìn)入到代碼中進(jìn)行調(diào)試工作了,pdb模塊能夠很好地勝任這項(xiàng)工作。該模塊可以顯示出程序在錯(cuò)誤產(chǎn)生時(shí)的執(zhí)行路徑,同時(shí)可以動(dòng)態(tài)地調(diào)整對象和代碼進(jìn)行調(diào)試。當(dāng)程序通過測試并調(diào)試后,下一步就是要將注意力放到性能上了。開發(fā)者可以使用profile以及 timit 模塊來測試程序的速度,找出程序中到底是哪里很慢,進(jìn)而對這部分代碼獨(dú)立出來進(jìn)行調(diào)優(yōu)的工作。Python程序是通過解釋器執(zhí)行的,解釋器的輸入是原有程序的字節(jié)碼編譯版本。這個(gè)字節(jié)碼編譯版本可以在程序執(zhí)行時(shí)動(dòng)態(tài)地生成,也可以在程序打包的時(shí)候就生成。compileall 模塊可以處理程序打包的事宜,它暴露出了打包相關(guān)的接口,該接口能夠被安裝程序和打包工具用來生成包含模塊字節(jié)碼的文件。同時(shí),在開發(fā)環(huán)境中,compileall模塊也可以用來驗(yàn)證源文件是否包含了語法錯(cuò)誤。

在源代碼級別,pyclbr 模塊提供了一個(gè)類查看器,方便文本編輯器或是其他程序?qū)ython程序中有意思的字符進(jìn)行掃描,比如函數(shù)或者是類。在提供了類查看器以后,就無需引入代碼,這樣就避免了潛在的副作用影響。

文檔字符串與doctest模塊

如果函數(shù),類或者是模塊的第一行是一個(gè)字符串,那么這個(gè)字符串就是一個(gè)文檔字符串。可以認(rèn)為包含文檔字符串是一個(gè)良好的編程習(xí)慣,這是因?yàn)檫@些字符串可以給Python程序開發(fā)工具提供一些信息。比如,help()命令能夠檢測文檔字符串,Python相關(guān)的IDE也能夠進(jìn)行檢測文檔字符串的工作。由于程序員傾向于在交互式shell中查看文檔字符串,所以最好將這些字符串寫的簡短一些。例如

# mult.py
class Test:
    """
    >>> a=Test(5)
    >>> a.multiply_by_2()
    10
    """
    def __init__(self, number):
        self._number=number

    def multiply_by_2(self):
        return self._number*2

在編寫文檔時(shí),一個(gè)常見的問題就是如何保持文檔和實(shí)際代碼的同步。例如,程序員也許會(huì)修改函數(shù)的實(shí)現(xiàn),但是卻忘記了更新文檔。針對這個(gè)問題,我們可以使用doctest模塊。doctest模塊收集文檔字符串,并對它們進(jìn)行掃描,然后將它們作為測試進(jìn)行執(zhí)行。為了使用doctest模塊,我們通常會(huì)新建一個(gè)用于測試的獨(dú)立的模塊。例如,如果前面的例子Test class包含在文件mult.py中,那么,你應(yīng)該新建一個(gè)testmult.py文件用來測試,如下所示:

# testmult.py

import mult, doctest

doctest.testmod(mult, verbose=True)

# Trying:
#     a=Test(5)
# Expecting nothing
# ok
# Trying:
#     a.multiply_by_2()
# Expecting:
#     10
# ok
# 3 items had no tests:
#     mult
#     mult.Test.__init__
#     mult.Test.multiply_by_2
# 1 items passed all tests:
#    2 tests in mult.Test
# 2 tests in 4 items.
# 2 passed and 0 failed.
# Test passed.

在這段代碼中,doctest.testmod(module)會(huì)執(zhí)行特定模塊的測試,并且返回測試失敗的個(gè)數(shù)以及測試的總數(shù)目。如果所有的測試都通過了,那么不會(huì)產(chǎn)生任何輸出。否則的話,你將會(huì)看到一個(gè)失敗報(bào)告,用來顯示期望值和實(shí)際值之間的差別。如果你想看到測試的詳細(xì)輸出,你可以使用testmod(module, verbose=True).

如果不想新建一個(gè)多帶帶的測試文件的話,那么另一種選擇就是在文件末尾包含相應(yīng)的測試代碼:

if __name__ == "__main__":
    import doctest
    doctest.testmod()

如果想執(zhí)行這類測試的話,我們可以通過-m選項(xiàng)調(diào)用doctest模塊。通常來講,當(dāng)執(zhí)行測試的時(shí)候沒有任何的輸出。如果想查看詳細(xì)信息的話,可以加上-v選項(xiàng)。

$ python -m doctest -v mult.py
單元測試與unittest模塊

如果想更加徹底地對程序進(jìn)行測試,我們可以使用unittest模塊。通過單元測試,開發(fā)者可以為構(gòu)成程序的每一個(gè)元素(例如,獨(dú)立的函數(shù),方法,類以及模塊)編寫一系列獨(dú)立的測試用例。當(dāng)測試更大的程序時(shí),這些測試就可以作為基石來驗(yàn)證程序的正確性。當(dāng)我們的程序變得越來越大的時(shí)候,對不同構(gòu)件的單元測試就可以組合起來成為更大的測試框架以及測試工具。這能夠極大地簡化軟件測試的工作,為找到并解決軟件問題提供了便利。

# splitter.py
import unittest

def split(line, types=None, delimiter=None):
    """Splits a line of text and optionally performs type conversion.
    ...
    """
    fields = line.split(delimiter)
    if types:
        fields = [ ty(val) for ty,val in zip(types,fields) ]
    return fields

class TestSplitFunction(unittest.TestCase):
    def setUp(self):
        # Perform set up actions (if any)
        pass
    def tearDown(self):
        # Perform clean-up actions (if any)
        pass
    def testsimplestring(self):
        r = split("GOOG 100 490.50")
        self.assertEqual(r,["GOOG","100","490.50"])
    def testtypeconvert(self):
        r = split("GOOG 100 490.50",[str, int, float])
        self.assertEqual(r,["GOOG", 100, 490.5])
    def testdelimiter(self):
        r = split("GOOG,100,490.50",delimiter=",")
        self.assertEqual(r,["GOOG","100","490.50"])

# Run the unittests
if __name__ == "__main__":
    unittest.main()

#...
#----------------------------------------------------------------------
#Ran 3 tests in 0.001s

#OK

在使用單元測試時(shí),我們需要定義一個(gè)繼承自unittest.TestCase的類。在這個(gè)類里面,每一個(gè)測試都以方法的形式進(jìn)行定義,并都以test打頭進(jìn)行命名——例如,’testsimplestring‘’testtypeconvert‘以及類似的命名方式(有必要強(qiáng)調(diào)一下,只要方法名以test打頭,那么無論怎么命名都是可以的)。在每個(gè)測試中,斷言可以用來對不同的條件進(jìn)行檢查。

實(shí)際的例子:

假如你在程序里有一個(gè)方法,這個(gè)方法的輸出指向標(biāo)準(zhǔn)輸出(sys.stdout)。這通常意味著是往屏幕上輸出文本信息。如果你想對你的代碼進(jìn)行測試來證明這一點(diǎn),只要給出相應(yīng)的輸入,那么對應(yīng)的輸出就會(huì)被顯示出來。

# url.py

def urlprint(protocol, host, domain):
    url = "{}://{}.{}".format(protocol, host, domain)
    print(url)

內(nèi)置的print函數(shù)在默認(rèn)情況下會(huì)往sys.stdout發(fā)送輸出。為了測試輸出已經(jīng)實(shí)際到達(dá),你可以使用一個(gè)替身對象對其進(jìn)行模擬,并且對程序的期望值進(jìn)行斷言。unittest.mock模塊中的patch()方法可以只在運(yùn)行測試的上下文中才替換對象,在測試完成后就立刻返回對象原始的狀態(tài)。下面是urlprint()方法的測試代碼:

#urltest.py

from io import StringIO
from unittest import TestCase
from unittest.mock import patch
import url

class TestURLPrint(TestCase):
    def test_url_gets_to_stdout(self):
        protocol = "http"
        host = "www"
        domain = "example.com"
        expected_url = "{}://{}.{}
".format(protocol, host, domain)

        with patch("sys.stdout", new=StringIO()) as fake_out:
            url.urlprint(protocol, host, domain)
            self.assertEqual(fake_out.getvalue(), expected_url)

urlprint()函數(shù)有三個(gè)參數(shù),測試代碼首先給每個(gè)參數(shù)賦了一個(gè)假值。變量expected_url包含了期望的輸出字符串。為了能夠執(zhí)行測試,我們使用了unittest.mock.patch()方法作為上下文管理器,把標(biāo)準(zhǔn)輸出sys.stdout替換為了StringIO對象,這樣發(fā)送的標(biāo)準(zhǔn)輸出的內(nèi)容就會(huì)被StringIO對象所接收。變量fake_out就是在這一過程中所創(chuàng)建出的模擬對象,該對象能夠在with所處的代碼塊中所使用,來進(jìn)行一系列的測試檢查。當(dāng)with語句完成時(shí),patch方法能夠?qū)⑺械臇|西都復(fù)原到測試執(zhí)行之前的狀態(tài),就好像測試沒有執(zhí)行一樣,而這無需任何額外的工作。但對于某些Python的C擴(kuò)展來講,這個(gè)例子卻顯得毫無意義,這是因?yàn)檫@些C擴(kuò)展程序繞過了sys.stdout的設(shè)置,直接將輸出發(fā)送到了標(biāo)準(zhǔn)輸出上。這個(gè)例子僅適用于純Python代碼的程序(如果你想捕獲到類似C擴(kuò)展的輸入輸出,那么你可以通過打開一個(gè)臨時(shí)文件然后將標(biāo)準(zhǔn)輸出重定向到該文件的技巧來進(jìn)行實(shí)現(xiàn))。

Python調(diào)試器與pdb模塊

Python在pdb模塊中包含了一個(gè)簡單的基于命令行的調(diào)試器。pdb模塊支持事后調(diào)試(post-mortem debugging),棧幀探查(inspection of stack frames),斷點(diǎn)(breakpoints),單步調(diào)試(single-stepping of source lines)以及代碼審查(code evaluation)。

好幾個(gè)函數(shù)都能夠在程序中調(diào)用調(diào)試器,或是在交互式的Python終端中進(jìn)行調(diào)試工作。

在所有啟動(dòng)調(diào)試器的函數(shù)中,函數(shù)set_trace()也許是最簡易實(shí)用的了。如果在復(fù)雜程序中發(fā)現(xiàn)了問題,可以在代碼中插入set_trace()函數(shù),并運(yùn)行程序。當(dāng)執(zhí)行到set_trace()函數(shù)時(shí),這就會(huì)暫停程序的執(zhí)行并直接跳轉(zhuǎn)到調(diào)試器中,這時(shí)候你就可以大展手腳開始檢查運(yùn)行時(shí)環(huán)境了。當(dāng)退出調(diào)試器時(shí),調(diào)試器會(huì)自動(dòng)恢復(fù)程序的執(zhí)行。

假設(shè)你的程序有問題,你想找到一個(gè)簡單的方法來對它進(jìn)行調(diào)試。

如果你的程序崩潰時(shí)報(bào)了一個(gè)異常錯(cuò)誤,那么你可以用python3 -i someprogram.py這個(gè)命令來運(yùn)行你的程序,這能夠很好地發(fā)現(xiàn)問題所在。-i選項(xiàng)表明只要程序終結(jié)就立即啟動(dòng)一個(gè)交互式shell。在這個(gè)交互式shell中,你就可以很好地探查到底發(fā)生了什么導(dǎo)致程序的錯(cuò)誤。例如,如果你有以下代碼:

def function(n):
    return n + 10

function("Hello")

如果使用python3 -i 命令運(yùn)行程序就會(huì)產(chǎn)生如下輸出:

python3 -i sample.py
Traceback (most recent call last):
  File "sample.py", line 4, in 
    function("Hello")
  File "sample.py", line 2, in function
    return n + 10
TypeError: Can"t convert "int" object to str implicitly
>>> function(20)
30
>>>

如果你沒有發(fā)現(xiàn)什么明顯的錯(cuò)誤,那么你可以進(jìn)一步地啟動(dòng)Python調(diào)試器。例如:

>>> import pdb
>>> pdb.pm()
> sample.py(4)func()
-> return n + 10
(Pdb) w
sample.py(6)()
-> func("Hello")
> sample.py(4)func()
-> return n + 10
(Pdb) print n
"Hello"
(Pdb) q
>>>

如果你的代碼身處的環(huán)境很難啟動(dòng)一個(gè)交互式shell的話(比如在服務(wù)器環(huán)境下),你可以增加錯(cuò)誤處理的代碼,并自己輸出跟蹤信息。例如:

import traceback
import sys
try:
    func(arg)
except:
    print("**** AN ERROR OCCURRED ****")
    traceback.print_exc(file=sys.stderr)

如果你的程序并沒有崩潰,而是說程序的行為與你的預(yù)期表現(xiàn)的不一致,那么你可以嘗試在一些可能出錯(cuò)的地方加入print()函數(shù)。如果你打算采用這種方案的話,那么還有些相關(guān)的技巧值得探究。首先,函數(shù)traceback.print_stack()能夠在被執(zhí)行時(shí)立即打印出程序中棧的跟蹤信息。例如:

>>> def sample(n):
...     if n > 0:
...         sample(n-1)
...     else:
...         traceback.print_stack(file=sys.stderr)
...
>>> sample(5)
File "", line 1, in 
File "", line 3, in sample
File "", line 3, in sample
File "", line 3, in sample
File "", line 3, in sample
File "", line 3, in sample
File "", line 5, in sample
>>>

另外,你可以在程序中任意一處使用pdb.set_trace()手動(dòng)地啟動(dòng)調(diào)試器,就像這樣:

import pdb
def func(arg):
    ...
    pdb.set_trace()
    ...

在深入解析大型程序的時(shí)候,這是一個(gè)非常實(shí)用的技巧,這樣操作能夠清楚地了解程序的控制流或是函數(shù)的參數(shù)。比如,一旦調(diào)試器啟動(dòng)了之后,你就可以使用print或者w命令來查看變量,來了解棧的跟蹤信息。

在進(jìn)行軟件調(diào)試時(shí),千萬不要讓事情變得很復(fù)雜。有時(shí)候僅僅需要知道程序的跟蹤信息就能夠解決大部分的簡單錯(cuò)誤(比如,實(shí)際的錯(cuò)誤總是顯示在跟蹤信息的最后一行)。在實(shí)際的開發(fā)過程中,將print()函數(shù)插入到代碼中也能夠很方便地顯示調(diào)試信息(只需要記得在調(diào)試完以后將print語句刪除掉就行了)。調(diào)試器的通用用法是在崩潰的函數(shù)中探查變量的值,知道如何在程序崩潰以后再進(jìn)入到調(diào)試器中就顯得非常實(shí)用。在程序的控制流不是那么清楚的情況下,你可以插入pdb.set_trace()語句來理清復(fù)雜程序的思路。本質(zhì)上,程序會(huì)一直執(zhí)行直到遇到set_trace()調(diào)用,之后程序就會(huì)立刻跳轉(zhuǎn)進(jìn)入到調(diào)試器中。在調(diào)試器里,你就可以進(jìn)行更多的嘗試。如果你正在使用Python的IDE,那么IDE通常會(huì)提供基于pdb的調(diào)試接口,你可以查閱IDE的相關(guān)文檔來獲取更多的信息。

下面是一些Python調(diào)試器入門的資源列表:

閱讀Steve Ferb的文章 “Debugging in Python”

觀看Eric Holscher的截圖 “Using pdb, the Python Debugger”

閱讀Ayman Hourieh的文章 “Python Debugging Techniques”

閱讀 Python documentation for pdb – The Python Debugger

閱讀Karen Tracey的D jango 1.1 Testing and Debugging一書中的第九章——When You Don’t Even Know What * to Log: Using Debuggers

程序分析

profile模塊和cProfile模塊可以用來分析程序。它們的工作原理都一樣,唯一的區(qū)別是,cProfile模塊是以C擴(kuò)展的方式實(shí)現(xiàn)的,如此一來運(yùn)行的速度也快了很多,也顯得比較流行。這兩個(gè)模塊都可以用來收集覆蓋信息(比如,有多少函數(shù)被執(zhí)行了),也能夠收集性能數(shù)據(jù)。對一個(gè)程序進(jìn)行分析的最簡單的方法就是運(yùn)行這個(gè)命令:

% python -m cProfile someprogram.py

此外,也可以使用profile模塊中的run函數(shù):

run(command [, filename])

該函數(shù)會(huì)使用exec語句執(zhí)行command中的內(nèi)容。filename是可選的文件保存名,如果沒有filename的話,該命令的輸出會(huì)直接發(fā)送到標(biāo)準(zhǔn)輸出上。

下面是分析器執(zhí)行完成時(shí)的輸出報(bào)告:

126 function calls (6 primitive calls) in 5.130 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.030 0.030 5.070 5.070 :1(?)
121/1 5.020 0.041 5.020 5.020 book.py:11(process)
1 0.020 0.020 5.040 5.040 book.py:5(?)
2 0.000 0.000 0.000 0.000 exceptions.py:101(_ _init_ _)
1 0.060 0.060 5.130 5.130 profile:0(execfile("book.py"))
0 0.000 0.000 profile:0(profiler)

當(dāng)輸出中的第一列包含了兩個(gè)數(shù)字時(shí)(比如,121/1),后者是元調(diào)用(primitive call)的次數(shù),前者是實(shí)際調(diào)用的次數(shù)(譯者注:只有在遞歸情況下,實(shí)際調(diào)用的次數(shù)才會(huì)大于元調(diào)用的次數(shù),其他情況下兩者都相等)。對于絕大部分的應(yīng)用程序來講使用該模塊所產(chǎn)生的的分析報(bào)告就已經(jīng)足夠了,比如,你只是想簡單地看一下你的程序花費(fèi)了多少時(shí)間。然后,如果你還想將這些數(shù)據(jù)保存下來,并在將來對其進(jìn)行分析,你可以使用pstats模塊。

假設(shè)你想知道你的程序究竟在哪里花費(fèi)了多少時(shí)間。

如果你只是想簡單地給你的整個(gè)程序計(jì)時(shí)的話,使用Unix中的time命令就已經(jīng)完全能夠應(yīng)付了。例如:

bash % time python3 someprogram.py
real 0m13.937s
user 0m12.162s
sys 0m0.098s
bash %

通常來講,分析代碼的程度會(huì)介于這兩個(gè)極端之間。比如,你可能已經(jīng)知道你的代碼會(huì)在一些特定的函數(shù)中花的時(shí)間特別多。針對這類特定函數(shù)的分析,我們可以使用修飾器decorator,例如:

import time
from functools import wraps

def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        r = func(*args, **kwargs)
        end = time.perf_counter()
        print("{}.{} : {}".format(func.__module__, func.__name__, end - start))
        return r
    return wrapper

使用decorator的方式很簡單,你只需要把它放在你想要分析的函數(shù)的定義前面就可以了。例如:

>>> @timethis
... def countdown(n):
...     while n > 0:
...         n -= 1
...
>>> countdown(10000000)
__main__.countdown : 0.803001880645752
>>>

如果想要分析一個(gè)語句塊的話,你可以定義一個(gè)上下文管理器(context manager)。例如:

import time
from contextlib import contextmanager

@contextmanager
def timeblock(label):
    start = time.perf_counter()
    try:
        yield
    finally:
        end = time.perf_counter()
        print("{} : {}".format(label, end - start))

接下來是如何使用上下文管理器的例子:

>>> with timeblock("counting"):
...     n = 10000000
...     while n > 0:
...         n -= 1
...
counting : 1.5551159381866455
>>>

如果想研究一小段代碼的性能的話,timeit模塊會(huì)非常有用。例如:

>>> from timeit import timeit
>>> timeit("math.sqrt(2)", "import math")
0.1432319980012835
>>> timeit("sqrt(2)", "from math import sqrt")
0.10836604500218527
>>>

timeit的工作原理是,將第一個(gè)參數(shù)中的語句執(zhí)行100萬次,然后計(jì)算所花費(fèi)的時(shí)間。第二個(gè)參數(shù)指定了一些測試之前需要做的環(huán)境準(zhǔn)備工作。如果你需要改變迭代的次數(shù),可以附加一個(gè)number參數(shù),就像這樣:

>>> timeit("math.sqrt(2)", "import math", number=10000000)
1.434852126003534
>>> timeit("sqrt(2)", "from math import sqrt", number=10000000)
1.0270336690009572
>>>

當(dāng)進(jìn)行性能評估的時(shí)候,要牢記任何得出的結(jié)果只是一個(gè)估算值。函數(shù)time.perf_counter()能夠在任一平臺提供最高精度的計(jì)時(shí)器。然而,它也只是記錄了自然時(shí)間,記錄自然時(shí)間會(huì)被很多其他因素影響,比如,計(jì)算機(jī)的負(fù)載。如果你對處理時(shí)間而非自然時(shí)間感興趣的話,你可以使用time.process_time()。例如:

import time
from functools import wraps

def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.process_time()
        r = func(*args, **kwargs)
        end = time.process_time()
        print("{}.{} : {}".format(func.__module__, func.__name__, end - start))
        return r
    return wrapper

最后也是相當(dāng)重要的就是,如果你想做一個(gè)詳細(xì)的性能評估的話,你最好查閱time,timeit以及其他相關(guān)模塊的文檔,這樣你才能夠?qū)ζ脚_相關(guān)的不同之處有所了解。

profile模塊中最基礎(chǔ)的東西就是run()函數(shù)了。該函數(shù)會(huì)把一個(gè)語句字符串作為參數(shù),然后在執(zhí)行語句時(shí)生成所花費(fèi)的時(shí)間報(bào)告。

import profile
def fib(n):
    # from literateprograms.org
    # http://bit.ly/hlOQ5m
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

def fib_seq(n):
    seq = []
    if n > 0:
        seq.extend(fib_seq(n-1))
    seq.append(fib(n))
    return seq
profile.run("print(fib_seq(20)); print")
性能優(yōu)化

當(dāng)你的程序運(yùn)行地很慢的時(shí)候,你就會(huì)想去提升它的運(yùn)行速度,但是你又不想去借用一些復(fù)雜方案的幫助,比如使用C擴(kuò)展或是just-in-time(JIT)編譯器。

那么這時(shí)候應(yīng)該怎么辦呢?要牢記性能優(yōu)化的第一要義就是“不要為了優(yōu)化而去優(yōu)化,應(yīng)該在我們開始寫代碼之前就想好應(yīng)該怎樣編寫高性能的代碼”。第二要義就是“優(yōu)化一定要抓住重點(diǎn),找到程序中最重要的地方去優(yōu)化,而不要去優(yōu)化那些不重要的部分”。

通常來講,你會(huì)發(fā)現(xiàn)你的程序在某些熱點(diǎn)上花費(fèi)了很多時(shí)間,比如內(nèi)部數(shù)據(jù)的循環(huán)處理。一旦你發(fā)現(xiàn)了問題所在,你就可以對癥下藥,讓你的程序更快地執(zhí)行。

使用函數(shù)

許多開發(fā)者剛開始的時(shí)候會(huì)將Python作為一個(gè)編寫簡單腳本的工具。當(dāng)編寫腳本的時(shí)候,很容易就會(huì)寫一些沒有結(jié)構(gòu)的代碼出來。例如:

import sys
import csv
with open(sys.argv[1]) as f:
    for row in csv.reader(f):
    # Some kind of processing

但是,卻很少有人知道,定義在全局范圍內(nèi)的代碼要比定義在函數(shù)中的代碼執(zhí)行地慢。他們之間速度的差別是因?yàn)榫植孔兞颗c全局變量不同的實(shí)現(xiàn)所引起的(局部變量的操作要比全局變量來得快)。所以,如果你想要讓程序更快地運(yùn)行,那么你可以簡單地將代碼放在一個(gè)函數(shù)中,就像這樣:

import sys
import csv
def main(filename):
    with open(filename) as f:
        for row in csv.reader(f):
            # Some kind of processing
            ...
main(sys.argv[1])

這樣操作以后,處理速度會(huì)有提升,但是這個(gè)提升的程度依賴于程序的復(fù)雜性。根據(jù)經(jīng)驗(yàn)來講,通常都會(huì)提升15%到30%之間。

選擇性地減少屬性的訪問

當(dāng)使用點(diǎn)(.)操作符去訪問屬性時(shí)都會(huì)帶來一定的消耗。本質(zhì)上來講,這會(huì)觸發(fā)一些特殊方法的執(zhí)行,比如__getattribute__()__getattr__(),這通常都會(huì)導(dǎo)致去內(nèi)存中字典數(shù)據(jù)的查詢。

你可以通過兩種方式來避免屬性的訪問,第一種是使用from module import name的方式。第二種是將對象的方法名保存下來,在調(diào)用時(shí)直接使用。為了解釋地更加清楚,我們來看一個(gè)例子:

import math
def compute_roots(nums):
    result = []
    for n in nums:
        result.append(math.sqrt(n))
    return result
# Test
nums = range(1000000)
for n in range(100):
    r = compute_roots(nums)

上面的代碼在我的計(jì)算機(jī)上運(yùn)行大概需要40秒的時(shí)間。現(xiàn)在我們把上面代碼中的compute_roots()函數(shù)改寫一下:

from math import sqrt
def compute_roots(nums):
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result

nums = range(1000000)
for n in range(100):
    r = compute_roots(nums)

這個(gè)版本的代碼執(zhí)行一下大概需要29秒。這兩個(gè)版本的代碼唯一的不同之處在于后面一個(gè)版本減少了對屬性的訪問。在后面一段代碼中,我們使用了sqrt()方法,而非math.sqrt()。result.append()函數(shù)也被存進(jìn)了一個(gè)局部變量result_append中,然后在循環(huán)當(dāng)中重復(fù)使用。

然而,有必要強(qiáng)調(diào)一點(diǎn)是說,這種方式的優(yōu)化僅僅針對經(jīng)常運(yùn)行的代碼有效,比如循環(huán)。由此可見,優(yōu)化僅僅在那些小心挑選出來的地方才會(huì)真正得到體現(xiàn)。

理解變量的局部性

上面已經(jīng)講過,局部變量的操作比全局變量來得快。對于經(jīng)常要訪問的變量來說,最好把他們保存成局部變量。例如,考慮剛才已經(jīng)討論過的compute_roots()函數(shù)修改版:

import math

def compute_roots(nums):
    sqrt = math.sqrt
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result

在這個(gè)版本中,sqrt函數(shù)被一個(gè)局部變量所替代。如果你執(zhí)行這段代碼的話,大概需要25秒就執(zhí)行完了(前一個(gè)版本需要29秒)。 這次速度的提升是因?yàn)閟qrt局部變量的查詢比sqrt函數(shù)的全局查詢來得稍快。

局部性原來同樣適用于類的參數(shù)。通常來講,使用self.name要比直接訪問局部變量來得慢。在內(nèi)部循環(huán)中,我們可以將經(jīng)常要訪問的屬性保存為一個(gè)局部變量。例如:

#Slower
class SomeClass:
    ...
    def method(self):
        for x in s:
            op(self.value)
# Faster
class SomeClass:
...
def method(self):
    value = self.value
    for x in s:
        op(value)
避免不必要的抽象

任何時(shí)候當(dāng)你想給你的代碼添加其他處理邏輯,比如添加裝飾器,屬性或是描述符,你都是在拖慢你的程序。例如,考慮這樣一個(gè)類:

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y = value

現(xiàn)在,讓我們簡單地測試一下:

>>> from timeit import timeit
>>> a = A(1,2)
>>> timeit("a.x", "from __main__ import a")
0.07817923510447145
>>> timeit("a.y", "from __main__ import a")
0.35766440676525235
>>>

正如你所看到的,我們訪問屬性y比訪問簡單屬性x不是慢了一點(diǎn)點(diǎn),整整慢了4.5倍之多。如果你在乎性能的話,你就很有必要問一下你自己,對y的那些額外的定義是否都是必要的了。如果不是的話,那么你應(yīng)該把那些額外的定義刪掉,用一個(gè)簡單的屬性就夠了。如果只是因?yàn)樵谄渌Z言里面經(jīng)常使用getter和setter函數(shù)的話,你完全沒有必要在Python中也使用相同的編碼風(fēng)格。

使用內(nèi)置的容器

內(nèi)置的數(shù)據(jù)結(jié)構(gòu),例如字符串(string),元組(tuple),列表(list),集合(set)以及字典(dict)都是用C語言實(shí)現(xiàn)的,正是因?yàn)椴捎昧薈來實(shí)現(xiàn),所以它們的性能表現(xiàn)也很好。如果你傾向于使用你自己的數(shù)據(jù)結(jié)構(gòu)作為替代的話(例如,鏈表,平衡樹或是其他數(shù)據(jù)結(jié)構(gòu)),想達(dá)到內(nèi)置數(shù)據(jù)結(jié)構(gòu)的速度的話是非常困難的。因此,你應(yīng)該盡可能地使用內(nèi)置的數(shù)據(jù)結(jié)構(gòu)。

避免不必要的數(shù)據(jù)結(jié)構(gòu)或是數(shù)據(jù)拷貝

有時(shí)候程序員會(huì)有點(diǎn)兒走神,在不該用到數(shù)據(jù)結(jié)構(gòu)的地方去用數(shù)據(jù)結(jié)構(gòu)。例如,有人可能會(huì)寫這樣的的代碼:

values = [x for x in sequence]
squares = [x*x for x in values]

也許他這么寫是為了先得到一個(gè)列表,然后再在這個(gè)列表上進(jìn)行一些操作。但是第一個(gè)列表是完全沒有必要寫在這里的。我們可以簡單地把代碼寫成這樣就行了:

squares = [x*x for x in sequence]

有鑒于此,你要小心那些偏執(zhí)程序員所寫的代碼了,這些程序員對Python的值共享機(jī)制非常偏執(zhí)。函數(shù)copy.deepcopy()的濫用也許是一個(gè)信號,表明該代碼是由菜鳥或者是不相信Python內(nèi)存模型的人所編寫的。在這樣的代碼里,減少copy的使用也許會(huì)比較安全。

在優(yōu)化之前,很有必要先詳細(xì)了解一下你所要使用的算法。如果你能夠?qū)⑺惴ǖ膹?fù)雜度從O(n^2)降為O(n log n)的話,程序的性能將得到極大的提高。

如果你已經(jīng)打算進(jìn)行優(yōu)化工作了,那就很有必要全局地考慮一下。普適的原則就是,不要想去優(yōu)化程序的每一個(gè)部分,這是因?yàn)閮?yōu)化工作會(huì)讓代碼變得晦澀難懂。相反,你應(yīng)該把注意力集中在已知的性能瓶頸處,例如內(nèi)部循環(huán)。

你需要謹(jǐn)慎地對待微優(yōu)化(micro-optimization)的結(jié)果。例如,考慮下面兩種創(chuàng)建字典結(jié)構(gòu)的方式:

a = {
"name" : "AAPL",
"shares" : 100,
"price" : 534.22
}
b = dict(name="AAPL", shares=100, price=534.22)

后面那一種方式打字打的更少一些(因?yàn)槟悴槐貙ey的名字用雙引號括起來)。然而當(dāng)你將這兩種編碼方式進(jìn)行性能對比時(shí),你會(huì)發(fā)現(xiàn)使用dict()函數(shù)的方式比另一種慢了3倍之多!知道了這一點(diǎn)以后,你也許會(huì)傾向于掃描你的代碼,把任何出現(xiàn)dict()的地方替換為另一種冗余的寫法。然而,一個(gè)聰明的程序員絕對不會(huì)這么做,他只會(huì)將注意力放在值得關(guān)注的地方,比如在循環(huán)上。在其他地方,速度的差異并不是最重要的。但是,如果你想讓你的程序性能有質(zhì)的飛躍的話,你可以去研究下基于JIT技術(shù)的工具。比如,PyPy項(xiàng)目,該項(xiàng)目是Python解釋器的另一種實(shí)現(xiàn),它能夠分析程序的執(zhí)行并為經(jīng)常執(zhí)行的代碼生成機(jī)器碼,有時(shí)它甚至能夠讓Python程序的速度提升一個(gè)數(shù)量級,達(dá)到(甚至超過)C語言編寫的代碼的速度。但是不幸的是,在本文正在寫的時(shí)候,PyPy還沒有完全支持Python 3。所以,我們還是在將來再來看它到底會(huì)發(fā)展的怎么樣。基于JIT技術(shù)的還有Numba項(xiàng)目。該項(xiàng)目實(shí)現(xiàn)的是一個(gè)動(dòng)態(tài)的編譯器,你可以將你想要優(yōu)化的Python函數(shù)以注解的方式進(jìn)行標(biāo)記,然后這些代碼就會(huì)在LLVM的幫助下被編譯成機(jī)器碼。該項(xiàng)目也能夠帶來極大的性能上的提升。然而,就像PyPy一樣,該項(xiàng)目對Python 3的支持還只是實(shí)驗(yàn)性的。

最后,但是也很重要的是,請牢記John Ousterhout(譯者注:Tcl和Tk的發(fā)明者,現(xiàn)為斯坦福大學(xué)計(jì)算機(jī)系的教授)說過的話“將不工作的東西變成能夠工作的,這才是最大的性能提升”。在你需要優(yōu)化前不要過分地考慮程序的優(yōu)化工作。程序的正確性通常來講都比程序的性能要來的重要。

--

原文:Developer Tools in Python
轉(zhuǎn)載自:伯樂在線 - brightconan

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/8678.html

相關(guān)文章

  • JavaScript開發(fā)工具大全

    摘要:發(fā)布于之后,采用了完全不同的方式,使用函數(shù)定義任務(wù)。它允許開發(fā)者使用它們的補(bǔ)丁和更新來修復(fù)這些安全漏洞。提供了工具用于掃描依賴來監(jiān)測漏洞。是一個(gè)開源診斷工具,用于和應(yīng)用。是和開發(fā)的一款新的包管理工具。與相比,它解決了安全性能以及一致性問題。 譯者按: 最全的JavaScript開發(fā)工具列表,總有一款適合你! 原文: THE ULTIMATE LIST OF JAVASCRIPT TOO...

    nifhlheimr 評論0 收藏0
  • 前端資源系列(4)-前端學(xué)習(xí)資源分享&前端面試資源匯總

    摘要:特意對前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...

    princekin 評論0 收藏0
  • 第2章:軟件構(gòu)建過程工具 2.2軟件構(gòu)建過程,系統(tǒng)工具

    摘要:建模語言建模語言是可用于表達(dá)信息或知識或系統(tǒng)的任何人造語言,該結(jié)構(gòu)由一組一致的規(guī)則定義,目標(biāo)是可視化,推理,驗(yàn)證和傳達(dá)系統(tǒng)設(shè)計(jì)。將這些文件安排到不同的地方稱為源代碼樹。源代碼樹的結(jié)構(gòu)通常反映了軟件的體系結(jié)構(gòu)。 大綱 軟件構(gòu)建的一般過程: 編程/重構(gòu) 審查和靜態(tài)代碼分析 調(diào)試(傾倒和記錄)和測試 動(dòng)態(tài)代碼分析/分析 軟件構(gòu)建的狹義過程(Build): 構(gòu)建系統(tǒng):組件和過程 構(gòu)建變體...

    godiscoder 評論0 收藏0
  • 「碼個(gè)蛋」2017年200篇精選干貨集合

    摘要:讓你收獲滿滿碼個(gè)蛋從年月日推送第篇文章一年過去了已累積推文近篇文章,本文為年度精選,共計(jì)篇,按照類別整理便于讀者主題閱讀。本篇文章是今年的最后一篇技術(shù)文章,為了讓大家在家也能好好學(xué)習(xí),特此花了幾個(gè)小時(shí)整理了這些文章。 showImg(https://segmentfault.com/img/remote/1460000013241596); 讓你收獲滿滿! 碼個(gè)蛋從2017年02月20...

    wangtdgoodluck 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<