IPython,讓Python顯得友好十倍的外套——windows XP/Win7安裝詳解
前言
學習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)。這里看個例子應該就能清楚:
>>>importstring
>>> 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 SimpleHTTPServer80
#
# 本機任意瀏覽器輸入http://localhost或http://127.0.0.1可以訪問。
# (80端口瀏覽器默認的,不需輸入)甚至在地址欄直接輸入localhost即可。
# 局域網,(宿舍)任意電腦輸入上面所說192或172等開頭的IP地址即可訪問。
######################################################
# 縮進輸出 JSON
echo {"hey":"kid"} | python-m json.tool
# 簡單的執行時間測量
python-m timeit [ix*ixforixinrange(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[]來訪問之前的命令。比如%exec In[10]就能執行列表中第十行。
%rep類似上面的_變量,但是是以字串的形式返回
最后,如果%automagic是打開的狀態的話,所有 magic function 不需要在前面加%就能正確調用。
在當前 IPython 版本中還有一個由于安全原因沒有默認引入的%autoreload,它的作用是在可以自動重新載入你調用的函數,以及其相關模塊。接觸過django的同學對這個應該比較熟悉,在 IPython 中的效果就是,當你在調試一個一直在反復改動的函數時,你可以開啟這個功能保證每次調用都會重新讀取最新的版本,讓你在源碼中的改動馬上生效。在 IPython 中執行
importipy_autoreload
%%autoreload2
這樣 IPython 會對所有的模塊都進行 autoreload。你可以通過執行%autoreload?來查詢它的文檔來進行進一步設定。如果你希望 IPython 每次啟動自動載入次功能,那么可以通過配置 ipythonrc (在 Windows 下可以在C:\Users\\_ipython\ipythonrc.ini找到) 來進行相關設置。
3、還有一個很神奇的功能。如果你的程序是由命令行開始執行的,即在命令行下輸入python foo.py(大部分 Python 程序都是),那么你還可以利用 IPython 在你的程序任意地方進行斷點調試!
在你程序中任意地方,加入如下語句:
fromIPython.ShellimportIPShellEmbed
IPShellEmbed([])()
注意:最近 IPython 發布了0.11 版本,各方面變化都非常大,API 也經過了重新設計。如果你使用的是 0.11 那么上面兩行對應的是這樣的:
fromIPythonimportembed
embed()
再和平常一樣運行你的程序,你會發現在程序運行到插入語句的地方時,會轉到 IPython 環境下。你可以試試運行些指令,就會發現此刻 IPython 的環境就是在程序的那個位置。你可以逐個瀏覽當前狀態下的各個變量,調用各種函數,輸出你感興趣的值來幫助調試。之后你可以照常退出 IPython,然后程序會繼續運行下去,自然地你在當時 IPython 下執行的語句也會對程序接下來的運行造成影響。
這個方法我實在這里看 到的。想象一下,這樣做就像讓高速運轉的程序暫停下來,你再對運行中的程序進行檢查和修改,之后再讓他繼續運行下去。這里舉一個例子,比如編寫網頁 bot ,你在每取回一個頁面后你都得看看它的內容,再嘗試如何處理他獲得下一個頁面的地址。運用這個技巧,你可以在取回頁面后讓程序中斷,再那里實驗各種處理方 法,在找到正確的處理方式后寫回到你的代碼中,再進行下一步。這種工作流程只有像 Python 這種動態語言才可以做到。
4、一個實際的例子
這里以一個簡單的例子來講解一下是怎樣的一個情況。我們要寫一個可以將簡單的數據表達式,類似1 + (2 - 3) * 456解析成樹的Pratt Parser。首先我們需要一個 lexer 把每個 token 解析出來,那么最開始的代碼就是:
# simple math expression parser
deflexer(s):
'''token generator, yields a list of tokens'''
yields
if__name__=='__main__':
fortokeninlexer("1 + (2 - 3) * 456"):
printtoken
明顯這個沒有任何意義,但現在程序已經有足夠的東西能夠跑起來。我們把這個程序存為expr.py,開啟一個命令行窗口,運行ipython然后像這樣執行它:
$ ipython
IPython0.13.1--An enhanced Interactive Python.
?-> Introductionandoverview of IPython's features.
...
In [1]: run expr.py
1+(2-3)*456
在 IPython 里面用run跑的好處有很多,首先是你在程序執行完畢后整個程序的狀態,比如最后全局變量的值,你寫的函數這些你都是可以隨便執行的!同樣的你可以在 IPython 里面保存一些用來測試的常量,每次用run跑的話新的程序會被重新載入,你可以這樣方便的測試每個函數,有一個非常動態的環境來調試你的程序:
In [2]:printtoken# 注意這里 token 就是 __main__ 里面的那個 token 的值
1+(2-3)*456
In [3]:printlist(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:
Docstring:
S.isdigit()->bool
ReturnTrueifallcharactersinS are digits
andthereisat least one characterinS,Falseotherwise.
In [8]: s.isdigit()# 調用試試看
Out[8]:False
In [9]:'f'in'foo'# 試試字符串能不能用 in 來判斷
Out[9]:True
確認了各個步驟以后,我們把 lexer 的代碼填起來。我們為了節省縱向的空間我們把很多東西寫在一行里面:
# simple math expression parser (broken lexer)
deflexer(s):
'''token generator'''
ix=0
whileix
ifs[ix].isspace(): ix+=1
ifs[ix]in"+-*/()":
yields[ix]; ix+=1
ifs[ix].isdigit():
jx=ix+1
whilejx
yields[ix:jx]; ix=jx
else:
raiseException("invalid char at %d: '%s'"%(ix, s[ix]))
yield""
if__name__=='__main__':
printlist(lexer("1 + (2 - 3) * 456"))
看起來不錯,我們還是在 IPython 里執行試試,結果發現程序拋出了一個異常:
In [6]: run expr.py
------------------------------------------------------------------
Exception?????????????????????? Traceback (most recent call last)
py/expr.pyin()
if__name__=='__main__':
--->20printlist(lexer("1 + (2 - 3) * 456"))
py/expr.pyinlexer(s)
yields[ix:jx]; ix=jx
else:
--->raiseException("invalid character at ...))
yield""
Exception: invalid character at3:' '
嗯?好像程序里已經處理了空格的情況。怎么會這樣?不知道你碰到異常的時候一般都怎么辦。你可能會選擇到處添加print,用 IDE 斷點調試。其實這種情況用pdb是很明智的選擇,在 IPython 里我們可以非常輕松的使用它。
In [13]: pdb# 開啟 pdb ,這樣在異常的時候我們會自動的 break 到異常處
Automatic pdb calling has been turned ON
In [14]: run expr.py
-----------------------------------------------------------------
Exception: invalid character at3:' '
> py/expr.py(15)lexer()
else:
--->raiseException("invalid char at ...))
yield""
ipdb>printix# 這里我們可以執行任何 Python 的代碼
ipdb> whatis ix# 也可以用 pdb 提供的命令,輸入 help 可以查看所有命令
通過方便的調試和仔細檢查代碼,我們發現是沒有正確的使用elif造成了問題!(我知道這個過程不是太符合情理...)。把代碼里的后面的幾個if都換成elif以后我們發現結果基本上是對的了。我們可以馬上再跑幾個類似的例子,確認不同的輸入是否都有比較好的結果:
In [18]: run expr.py# 這次差不多對了,我們可以試試幾個別的例子
['1','+','(','2','-','3',')','*','456', '']
In [19]:printlist(lexer("1*123*87-2*5"))
['1','*','123','*','87','-','2','*','5', '']
# 跟在 shell 里面一樣,你可以用上下來選取之前的記錄,然后簡單的修改再重新執行。
# 記得每次 run 后你的函數都是最新版本,你可以很簡單的用重復的數據來測試你的函數
# IPython 甚至還實現了 Ctrl+R!自己試試看吧
In [19]:printlist(lexer("1 + two"))
Exception: invalid character at2:'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 就會反映出你的更改。