當我開始學習Python的時候,有些事我希望我一早就知道。我花費了很多時間才學會這些東西。我想要把這些重點都編纂到一篇文章當中。這篇文章的目標讀者,是剛剛開始學習Python語言的有經驗的程式設計師,想要跳過前幾個月研究Python使用的那些他們已經在用的類似工具。包管理和標準工具這兩節對於初學者來說同樣很有幫助。
我的經驗主要基於Python 2.7,但是大多數的工具對任何版本都有效。
如果你從來沒有使用過Python,我強烈建議你閱讀Python introduction,因為你需要知道基本的語法和型別。
包管理
Python世界最棒的地方之一,就是大量的第三方程式包。同樣,管理這些包也非常容易。按照慣例,會在 requirements.txt 檔案中列出專案所需要的包。每個包占一行,通常還包含版本號。這裡有一個例子,本部落格使用Pelican:
1 2 3 |
; html-script: false ]pelican==3.3 Markdown pelican-extended-sitemap==1.0.0 |
Python 程式包有一個缺陷是,它們預設會進行全域性安裝。我們將要使用一個工具,使我們每個專案都有一個獨立的環境,這個工具叫virtualenv。我們同樣要安裝一個更高階的包管理工具,叫做pip,他可以和virtualenv配合工作。
首先,我們需要安裝pip。大多數python安裝程式已經內建了easy_install
(python預設的包管理工具),所以我們就使用easy_install pip
來安裝pip。這應該是你最後一次使用easy_install
了。如果你並沒有安裝easy_install
,在linux系統中,貌似從python-setuptools
包中可以獲得。
如果你使用的Python版本高於等於3.3, 那麼Virtualenv 已經是標準庫的一部分了,所以沒有必要再去安裝它了。
下一步,你希望安裝virtualenv和virtualenvwrapper。Virtualenv使你能夠為每個專案創造一個獨立的環境。尤其是當你的不同專案使用不同版本的包時,這一點特別有用。Virtualenv wrapper 提供了一些不錯的指令碼,可以讓一些事情變得容易。
1 |
; html-script: false ]sudo pip install virtualenvwrapper |
當virtualenvwrapper安裝後,它會把virtualenv列為依賴包,所以會自動安裝。
開啟一個新的shell,輸入mkvirtualenv test
。如果你開啟另外一個shell,則你就不在這個virtualenv中了,你可以通過workon test
來啟動。如果你的工作完成了,可以使用deactivate
來停用。
IPython
IPython是標準Python互動式的程式設計環境的一個替代品,支援自動補全,文件快速訪問,以及標準互動式程式設計環境本應該具備的很多其他功能。
當你處在一個虛擬環境中的時候,可以很簡單的使用pip install ipython
來進行安裝,在命令列中使用ipython
來啟動
另一個不錯的功能是”筆記本”,這個功能需要額外的元件。安裝完成後,你可以使用ipython notebook
,而且會有一個不錯的網頁UI,你可以建立筆記本。這在科學計算領域很流行。
測試
我推薦使用nose
或是py.test
。我大部分情況下用nose
。它們基本上是類似的。我將講解nose的一些細節。
這裡有一個人為建立的可笑的使用nose進行測試的例子。在一個以test_
開頭的檔案中的所有以test_
開頭的函式,都會被呼叫:
1 2 |
; html-script: false ]def test_equality(): assert True == False |
不出所料,當執行nose的時候,我們的測試沒有通過。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
; html-script: false ](test)jhaddad@jons-mac-pro ~VIRTUAL_ENV/src$ nosetests F ====================================================================== FAIL: test_nose_example.test_equality ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/jhaddad/.virtualenvs/test/lib/python2.7/site-packages/nose/case.py", line 197, in runTest self.test(*self.arg) File "/Users/jhaddad/.virtualenvs/test/src/test_nose_example.py", line 3, in test_equality assert True == False AssertionError ---------------------------------------------------------------------- |
nose.tools中同樣也有一些便捷的方法可以呼叫
1 2 3 |
; html-script: false ]from nose.tools import assert_true def test_equality(): assert_true(False) |
如果你想使用更加類似JUnit的方法,也是可以的:
1 2 3 4 5 6 7 8 9 10 |
; html-script: false ]from nose.tools import assert_true from unittest import TestCase class ExampleTest(TestCase): def setUp(self): # setUp & tearDown are both available self.blah = False def test_blah(self): self.assertTrue(self.blah) |
開始測試:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
; html-script: false ](test)jhaddad@jons-mac-pro ~VIRTUAL_ENV/src$ nosetests F ====================================================================== FAIL: test_blah (test_nose_example.ExampleTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/jhaddad/.virtualenvs/test/src/test_nose_example.py", line 11, in test_blah self.assertTrue(self.blah) AssertionError: False is not true ---------------------------------------------------------------------- Ran 1 test in 0.003s FAILED (failures=1) |
卓越的Mock庫包含在Python 3 中,但是如果你在使用Python 2,可以使用pypi來獲取。這個測試將進行一個遠端呼叫,但是這次呼叫將耗時10s。這個例子顯然是人為捏造的。我們使用mock來返回樣本資料而不是真正的進行呼叫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
; html-script: false ]import mock from mock import patch from time import sleep class Sweetness(object): def slow_remote_call(self): sleep(10) return "some_data" # lets pretend we get this back from our remote api call def test_long_call(): s = Sweetness() result = s.slow_remote_call() assert result == "some_data" |
當然,我們的測試需要很長的時間。
1 2 3 4 5 |
; html-script: false ](test)jhaddad@jons-mac-pro ~VIRTUAL_ENV/src$ nosetests test_mock.py Ran 1 test in 10.001s OK |
太慢了!因此我們會問自己,我們在測試什麼?我們需要測試遠端呼叫是否有用,還是我們要測試當我們獲得資料後要做什麼?大多數情況下是後者。讓我們擺脫這個愚蠢的遠端呼叫吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
; html-script: false ]import mock from mock import patch from time import sleep class Sweetness(object): def slow_remote_call(self): sleep(10) return "some_data" # lets pretend we get this back from our remote api call def test_long_call(): s = Sweetness() with patch.object(s, "slow_remote_call", return_value="some_data"): result = s.slow_remote_call() assert result == "some_data" |
好吧,讓我們再試一次:
1 2 3 4 5 6 |
; html-script: false ](test)jhaddad@jons-mac-pro ~VIRTUAL_ENV/src$ nosetests test_mock.py . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK |
好多了。記住,這個例子進行了荒唐的簡化。就我個人來講,我僅僅會忽略從遠端系統的呼叫,而不是我的資料庫呼叫。
nose-progressive是一個很好的模組,它可以改善nose的輸出,讓錯誤在發生時就顯示出來,而不是留到最後。如果你的測試需要花費一定的時間,那麼這是件好事。
pip install nose-progressive
並且在你的nosetests
中新增--with-progressive
除錯
iPDB是一個極好的工具,我已經用它查出了很多匪夷所思的bug。pip install ipdb
安裝該工具,然後在你的程式碼中import ipdb; ipdb.set_trace()
,然後你會在你的程式執行時,獲得一個很好的互動式提示。它每次執行程式的一行並且檢查變數。
python內建了一個很好的追蹤模組,幫助我搞清楚發生了什麼。這裡有一個沒什麼用的python程式:
1 2 3 |
; html-script: false ]a = 1 b = 2 a = b |
這裡是對這個程式的追蹤結果:
1 2 3 4 5 6 7 |
; html-script: false ](test)jhaddad@jons-mac-pro ~VIRTUAL_ENV/src$ python -m trace --trace tracing.py 1 ↵ --- modulename: tracing, funcname: <module> tracing.py(1): a = 1 tracing.py(2): b = 2 tracing.py(3): a = b --- modulename: trace, funcname: _unsettrace trace.py(80): sys.settrace(None) |
當你想要搞清楚其他程式的內部構造的時候,這個功能非常有用。如果你以前用過strace,它們的工作方式很相像
在一些場合,我使用pycallgraph來追蹤效能問題。它可以建立函式呼叫時間和次數的圖表。
最後,objgraph對於查詢記憶體洩露非常有用。這裡有一篇關於如何使用它查詢記憶體洩露的好文。
Gevent
Gevent 是一個很好的庫,封裝了Greenlets,使得Python具備了非同步呼叫的功能。是的,非常棒。我最愛的功能是Pool,它抽象了非同步呼叫部分,給我們提供了可以簡單使用的途徑,一個非同步的map()函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
; html-script: false ]from gevent import monkey monkey.patch_all() from time import sleep, time def fetch_url(url): print "Fetching %s" % url sleep(10) print "Done fetching %s" % url from gevent.pool import Pool urls = ["http://test.com", "http://bacon.com", "http://eggs.com"] p = Pool(10) start = time() p.map(fetch_url, urls) print time() - start |
非常重要的是,需要注意這段程式碼頂部對gevent monkey進行的補丁,如果沒有它的話,就不能正確的執行。如果我們讓Python連續呼叫 fetch_url 3次,通常我們期望這個過程花費30秒時間。使用gevent:
1 2 3 4 5 6 7 8 |
; html-script: false ](test)jhaddad@jons-mac-pro ~VIRTUAL_ENV/src$ python g.py Fetching http://test.com Fetching http://bacon.com Fetching http://eggs.com Done fetching http://test.com Done fetching http://bacon.com Done fetching http://eggs.com 10.001791954 |
如果你有很多資料庫呼叫或是從遠端URLs獲取,這是非常有用的。我並不是很喜歡回撥函式,所以這一抽象對我來說效果很好。
結論
好吧,如果你看到這裡了,那麼你很可能已經學到了一些新東西。這些工具,在過去的一年裡對我影響重大。找打它們花費了不少時間,所以希望本文能夠減少其他人想要很好利用這門語言需要付出的努力。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式