前言
學習python,官方版本其實足夠了。但是如果追求更好的開發體驗,耐得住不厭其煩地折騰。那么我可以負責任的告訴你:IPython是我認為的唯一顯著好於原版python的工具。
整理了《Python 二三事》:http://pre-sence.com/archives/python-intro 《Python 四五事》:http://pre-sence.com/archives/python-misc 並加入安裝IPython部分。
寫這篇隨筆的原因是:忽然醒悟之前我安裝IPython折騰許久不成功可能是我未能想起pip或easy_install這兩個python的上帝工具。參考:Python包管理工具pip與easy_install
個人經驗總結:IPython,是學習python的利器,是讓Python顯得友好十倍的外套,是我唯一的強烈推薦。
安裝IPython
任何Linux發行版對編程者都十分友好:
Ubuntu為例: sudo apt-get install ipython
windows環境:
1、下載ez_setup.py ,右擊左邊鏈接,另存為,使用python ez_setup.py運行,或直接雙擊。
2、步驟1成功后,cmd下輸入命令easy_install -h可以測試,正常反應說明已經可以使用easy_install了。
3、cmd下輸入easy_install pip安裝pip,這是因為pip正是easy_install的下一代,比easy_install好用。
4、步驟3成功后,pip install ipython。
5、如果步驟4不行,退一步,使用easy_install ipython安裝。
運行IPython
cmd提示符下,輸入ipython運行就可以使用除了原python外,IPython多出來的貼心的“I”了。
退出IPython
與python一樣也是輸入exit
Python實用技巧:
1、關於 "_" 字符使用
在 Python shell 下 _ 總是被賦予之前最后一個表達式的值(注:@pythonwood)。這里看個例子應該就能清楚:
>>> import string >>> string.letters 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' >>> print _ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ >>>2014 2014 >>> v = _ >>> v 2014
舉個實際的例子,比如你在調試時讀文件的時候直接進行 f.read() ,你看了看發現輸出結果很有意思,想要對它進行進一步處理,但發現讀的時候忘記賦值了。以往你只能嘆嘆氣重新開文件再讀一次,現在你只要執行 result = _,把 _ 附到另外一個變量就可以了。
2、python -m
相信很多人應該用過這個東西,Python 很多標准庫都提供這樣的調用方式來實現一些簡單的命令行功能。Python 3 現在自帶 pip。比如我們想使用 Python 3 的 pip 來安裝別的庫,我們可以這樣:
py -3 -m pip install bottle
跟你預料的一樣,這樣就可以了。當然你可以用個 .bat 文件來把這些包裹起來並放在 Path 上,一個簡單的例子,把下面的內容寫到一個叫 pip3.bat 的文件里:
@echo off
py -3 -m pip %*
並放到 Path 上,就可以方便調用了。其中 %* 負責傳遞所有的命令行參數。
實際上 python -m 可以用的東西還真的挺多,這里給出一個不完全的列表:
###################################################### # 最強功能 ###################################################### # 局域網共享,宿舍中任意一台筆記本都可以瞬間變身web資源共享服務器 # 命令ipconfig可以看到局域網ip地址,一般是192,172這些開頭的。 # 使用本機80端口,可任意設置。只共享當前運行目錄。 # python -m SimpleHTTPServer 80 # # 本機任意瀏覽器輸入 http://localhost 或 http://127.0.0.1 可以訪問。 # (80端口瀏覽器默認的,不需輸入)甚至在地址欄直接輸入localhost即可。 # 局域網,(宿舍)任意電腦輸入上面所說192或172等開頭的IP地址即可訪問。 ###################################################### # 縮進輸出 JSON echo {"hey" : "kid"} | python -m json.tool # 簡單的執行時間測量 python -m timeit [ix*ix for ix in range(100)] # 簡單的 Profiling python -m cProfile myscript.py # 比較兩個文件夾的區別 python -m filecmp path/to/a path/to/b # base64 轉換 echo foo bar | python -m base64 # 調用默認瀏覽器打開一個新標簽頁 python -m webbrowser -t http://google.com # 生成程序文檔 python -m pydoc myscript.py # 類似 nose 的自動搜索 unittest python -m unittest discover # 調用 pdb 執行代碼 python -m pdb myscript.py
IPython實用技巧:
1、Tab自動補全,一種是簡單的關鍵字補全,另外一種是對象的方法和屬性補全。
作為例子,我們先引入 sys
模塊,之后再輸入 sys.
(注意有個點),此時按下 tab 鍵,IPython 會列出所有 sys 模塊下的方法和屬性。
接着上面的例子,我們輸入 sys?
,這樣會顯示出 sys
模塊的 docstring及相關信息。很多時候這個也是很方便的功能。

2、IPython 還有強大之處很大部分還體現在它的 magic function 中。它是指的在 IPython 環境下執行以 %
開頭的一些命令來對 IPython 進行一些設定或者執行某些功能。在 IPython 中輸入 %lsmagic
就能列出所有的 magic functions。在這里簡單介紹下幾個比較有意思的,你也可以自己通過查看文檔來找找有哪些你特別用的到得。
-
之前看到能用
?
來查詢函數的文檔,對於 magic function 也是如此。比如%run?
。 -
!cd ..
在命令前面加上!
則它會被作為命令行命令執行,這樣你就不用退出 IPython 來進行命令行操作。 -
%run foo.py
在當前環境下直接執行foo.py
,效果跟命令行下調用ipython foo.py
相同。 -
%time foo.bar()
跟timeit decorator
作用相同,進行簡單的 profile。 -
%hist
能顯示之前輸入過的命令的歷史,同時你可以用In[<linenumber>]
來訪問之前的命令。比如%exec In[10]
就能執行列表中第十行。 -
%rep
類似上面的_
變量,但是是以字串的形式返回 -
最后,如果
%automagic
是打開的狀態的話,所有 magic function 不需要在前面加%
就能正確調用。
在當前 IPython 版本中還有一個由於安全原因沒有默認引入的 %autoreload
,它的作用是在可以自動重新載入你調用的函數,以及其相關模塊。接觸過 django 的同學對這個應該比較熟悉,在 IPython 中的效果就是,當你在調試一個一直在反復改動的函數時,你可以開啟這個功能保證每次調用都會重新讀取最新的版本,讓你在源碼中的改動馬上生效。在 IPython 中執行
import ipy_autoreload %%autoreload 2
這樣 IPython 會對所有的模塊都進行 autoreload。你可以通過執行 %autoreload?
來查詢它的文檔來進行進一步設定。如果你希望 IPython 每次啟動自動載入次功能,那么可以通過配置 ipythonrc (在 Windows 下可以在 C:\Users\<username>\_ipython\ipythonrc.ini
找到) 來進行相關設置。
3、還有一個很神奇的功能。如果你的程序是由命令行開始執行的,即在命令行下輸入 python foo.py
(大部分 Python 程序都是),那么你還可以利用 IPython 在你的程序任意地方進行斷點調試!
在你程序中任意地方,加入如下語句:
from IPython.Shell import IPShellEmbed IPShellEmbed([])()
注意:最近 IPython 發布了 0.11 版本,各方面變化都非常大,API 也經過了重新設計。如果你使用的是 0.11 那么上面兩行對應的是這樣的:
from IPython import embed embed()
再和平常一樣運行你的程序,你會發現在程序運行到插入語句的地方時,會轉到 IPython 環境下。你可以試試運行些指令,就會發現此刻 IPython 的環境就是在程序的那個位置。你可以逐個瀏覽當前狀態下的各個變量,調用各種函數,輸出你感興趣的值來幫助調試。之后你可以照常退出 IPython,然后程序會繼續運行下去,自然地你在當時 IPython 下執行的語句也會對程序接下來的運行造成影響。
這個方法我實在這里看 到的。想象一下,這樣做就像讓高速運轉的程序暫停下來,你再對運行中的程序進行檢查和修改,之后再讓他繼續運行下去。這里舉一個例子,比如編寫網頁 bot ,你在每取回一個頁面后你都得看看它的內容,再嘗試如何處理他獲得下一個頁面的地址。運用這個技巧,你可以在取回頁面后讓程序中斷,再那里實驗各種處理方 法,在找到正確的處理方式后寫回到你的代碼中,再進行下一步。這種工作流程只有像 Python 這種動態語言才可以做到。
4、一個實際的例子
這里以一個簡單的例子來講解一下是怎樣的一個情況。我們要寫一個可以將簡單的數據表達式,類似 1 + (2 - 3) * 456
解析成樹的 Pratt Parser。首先我們需要一個 lexer 把每個 token 解析出來,那么最開始的代碼就是:
# simple math expression parser def lexer(s): '''token generator, yields a list of tokens''' yield s if __name__ == '__main__': for token in lexer("1 + (2 - 3) * 456"): print token
明顯這個沒有任何意義,但現在程序已經有足夠的東西能夠跑起來。我們把這個程序存為 expr.py
,開啟一個命令行窗口,運行 ipython
然后像這樣執行它:
$ ipython IPython 0.13.1 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. ... In [1]: run expr.py 1 + (2 - 3) * 456
在 IPython 里面用 run
跑的好處有很多,首先是你在程序執行完畢后整個程序的狀態,比如最后全局變量的值,你寫的函數這些你都是可以隨便執行的!同樣的你可以在 IPython 里面保存一些用來測試的常量,每次用 run
跑的話新的程序會被重新載入,你可以這樣方便的測試每個函數,有一個非常動態的環境來調試你的程序:
In [2]: print token # 注意這里 token 就是 __main__ 里面的那個 token 的值 1 + (2 - 3) * 456 In [3]: print list(lexer('1+2+3')) # 可以運行你寫的函數 ['1+2+3']
然后按照之前的想法,我們嘗試把這個 lexer 寫出來。在這個過程中,IPython 可以用來查看函數的文檔,測試如何調用某些函數,看看返回值是什么樣子等等,還是跟上面的說的一樣,我們有一個動態的環境可以真真正正的執行程序,你可以 在把代碼寫到你珍貴的主程序之前就有機會運行它,這樣你可以更確認你的代碼能正常工作:
In [4]: s = "foo" # 忘記判斷字符串是數字的函數的名字了,用一個字符串試試看 In [5]: s.is # 開頭大概是 is,這里按下 tab 鍵 IPython 會幫我們補全 s.isalnum s.isalpha s.isdigit s.islower s.isspace s.istitle In [6]: s.isdigit? # 結果是 isdigit,在表達式后加上問號並回車查看文檔 Type: builtin_function_or_method String Form:<built-in method isdigit of str object at 0x1264f08> Docstring: S.isdigit() -> bool Return True if all characters in S are digits and there is at least one character in S, False otherwise. In [8]: s.isdigit() # 調用試試看 Out[8]: False In [9]: 'f' in 'foo' # 試試字符串能不能用 in 來判斷 Out[9]: True
確認了各個步驟以后,我們把 lexer 的代碼填起來。我們為了節省縱向的空間我們把很多東西寫在一行里面:
# simple math expression parser (broken lexer) def lexer(s): '''token generator''' ix = 0 while ix < len(s): if s[ix].isspace(): ix += 1 if s[ix] in "+-*/()": yield s[ix]; ix += 1 if s[ix].isdigit(): jx = ix + 1 while jx < len(s) and s[jx].isdigit(): jx += 1 yield s[ix:jx]; ix = jx else: raise Exception("invalid char at %d: '%s'" % (ix, s[ix])) yield "" if __name__ == '__main__': print list(lexer("1 + (2 - 3) * 456"))
看起來不錯,我們還是在 IPython 里執行試試,結果發現程序拋出了一個異常:
In [6]: run expr.py ------------------------------------------------------------------ Exception Traceback (most recent call last) py/expr.py in <module>() 18 19 if __name__ == '__main__': ---> 20 print list(lexer("1 + (2 - 3) * 456")) py/expr.py in lexer(s) 13 yield s[ix:jx]; ix = jx 14 else: ---> 15 raise Exception("invalid character at ...)) 16 yield "" 17 Exception: invalid character at 3: ' '
嗯?好像程序里已經處理了空格的情況。怎么會這樣?不知道你碰到異常的時候一般都怎么辦。你可能會選擇到處添加 print
,用 IDE 斷點調試。其實這種情況用 pdb
是很明智的選擇,在 IPython 里我們可以非常輕松的使用它。
In [13]: pdb # 開啟 pdb ,這樣在異常的時候我們會自動的 break 到異常處 Automatic pdb calling has been turned ON In [14]: run expr.py ----------------------------------------------------------------- Exception: invalid character at 3: ' ' > py/expr.py(15)lexer() 14 else: ---> 15 raise Exception("invalid char at ...)) 16 yield "" ipdb> print ix # 這里我們可以執行任何 Python 的代碼 3 ipdb> whatis ix # 也可以用 pdb 提供的命令,輸入 help 可以查看所有命令 <type 'int'>
通過方便的調試和仔細檢查代碼,我們發現是沒有正確的使用 elif
造成了問題!(我知道這個過程不是太符合情理...)。把代碼里的后面的幾個 if
都換成 elif
以后我們發現結果基本上是對的了。我們可以馬上再跑幾個類似的例子,確認不同的輸入是否都有比較好的結果:
In [18]: run expr.py # 這次差不多對了,我們可以試試幾個別的例子 ['1', '+', '(', '2', '-', '3', ')', '*', '456', ''] In [19]: print list(lexer("1*123*87-2*5")) ['1', '*', '123', '*', '87', '-', '2', '*', '5', ''] # 跟在 shell 里面一樣,你可以用上下來選取之前的記錄,然后簡單的修改再重新執行。 # 記得每次 run 后你的函數都是最新版本,你可以很簡單的用重復的數據來測試你的函數 # IPython 甚至還實現了 Ctrl+R!自己試試看吧 In [19]: print list(lexer("1 + two")) Exception: invalid character at 2: 't'...
在一段痛苦的調試之后,我們最終把程序寫出來了。很遺憾程序超出了我預計的長度,就不貼在這里了。后面部分的開發過程跟前面基本還是一樣,總結起來就是:
- 保持你的程序是一個可以運行並且有意義的狀態,盡可能頻繁的運行。
- 在 IPython 里查看文檔,嘗試小的程序片段,測試些你不確定的做法,確定之后再把東西添加到你的代碼里。
- 用不同的參數在 IPython 里測試你正在編寫的函數/class。
- 當遇到問題的時候,先簡單的用
pdb
在異常處 break,十有八九都能有些頭緒。
額外的注意事項
這里舉的例子是你所有的開發都是在單個 .py
文件里的。現實生活中你很有可能會橫跨幾個文件一起修改。請務必注意,在 IPython 里你每次 run
的時候只有被 run
的那個文件里的東西會是最后修改的版本,其 import
的東西如果在期間被修改是不會反應出來的。
這個的原理就跟你在 Python shell 里在修改前修改后重復 import
某個模塊不會有作用是一樣的,Python 神奇的 import
機制不會去追蹤其他模塊的修改。你可以手動用 reload
函數來重新載入,你也可以使用 IPython 的 autoreload
功能來讓你忽略這個問題。個人來說我沒怎么用過這個功能,IPython 沒有默認開啟它可能也是有些顧慮,請自己評估看看。
另外你應該已經注意到,run
的效果基本上就是把你的代碼拷貝進 IPython 里執行一遍。對於沒有 __main__
的文件,你也可以 run
,這樣里面定義的函數和 class 就會反映出你的更改。