1. 程式人生 > >IPython :一個互動式計算和開發環境

IPython :一個互動式計算和開發環境

一、 IPython基礎

1.1 程式碼自動補全:Tab鍵

可補全內容包括:變數名、函式名、成員變數函式、目錄檔案

1.2 內省(Itrospection)

在變數名之前或之後加上問號(?),這樣可以顯示這個物件的相關資訊。

  • 如果這個物件是個函式或例項方法,那麼它的docstring也會被顯示出來。

  • 使用??還將顯示該函式的原始碼

  • ?還能搜尋IPython名稱空間。一些字元再配以萬用字元(*)即可顯示出所有與其相匹配的名稱。如,我們可以列出NumPy頂級名稱空間中含有load的所有函式:

    np.*load*?

1.3 %run 命令

在IPython回話環境中,所有檔案都可以通過%run命令當做Python程式來執行。

  • 指令碼的行為和在標準命令列環境(通過python xxx.py啟動的)中執行時一樣,此後,在檔案中所定義的全部變數就可以子當前IPython shell中訪問了。

  • 如果Python指令碼需要用到命令列引數,可以將引數放到檔案路徑後面

  • 中斷正在執行的程式碼:任何程式碼在執行時,只要按下Ctrl+C,就會引發一個KeyboardInterrupt。

1.4 執行剪貼簿中的程式碼

在IPython中執行程式碼最簡單的方式就是貼上剪貼簿中的程式碼。如果你不想為一段程式碼新建一個檔案,那麼可以試試這種方法。在windows中可以使用右鍵的貼上命令貼上,這會模仿IPython的多行輸入功能。

1.5 鍵盤快捷鍵

  • Ctrl-C 終止當前正在執行的程式碼
  • Ctrl-A/E 游標移動到行首/行尾
  • Ctrl-U 清除當前行
  • Ctrl-L 清屏

1.6 異常和跟蹤

如果某段程式碼發生了異常,IPython預設會輸出整個呼叫棧,還會附上呼叫棧各點附近的幾行程式碼作為上下文參考。

上下文程式碼參考數量可由%xmode魔術命令進行控制,稍後還會介紹如如何進行除錯。

1.7 魔術命令

IPython有一些特殊的命令。有的為常見任務提供便利,有的則使你能夠輕鬆控制IPython系統的行為。

  • 魔術命令是以百分號%為字首的命令。

  • 魔術命令可以看做運行於IPython系統中的命令列程式,它們大都還有一些引數選項。在命令後面加問號(?

    )可以檢視。

  • 魔術命令預設是可以不帶百分號使用的,只要沒有定義與其同名的變數即可。這個功能可以通過%automagic命令開啟或者關閉。

  • 通過%quickref%magic命令可以檢視所有的命令。

常用的魔術命令如下:

  • %quickref thon快速參考
  • %magic 顯示magic command詳細文件
  • %debug 從最新的異常跟蹤的底部進入互動式偵錯程式
  • %hist 列印命令輸入歷史
  • %pdb 在發生異常後自動進入偵錯程式
  • %paste 執行剪貼簿中的Python程式碼
  • %cpaste 開啟一個特殊的提示符以便手工貼上待執行的程式碼
  • %reset 刪除interactive空間中的全部變數/名稱
  • %run 執行一個python指令碼
  • %page 分頁顯示一個物件
  • %time 報告statement執行的時間
  • %timeit 多次執行statement以計算平均執行時間,用於執行時間非常小的程式碼。
  • %who%who_is%whos 顯示Interactive名稱空間的中定義的變數,資訊級別/冗餘度可變
  • %xdel 刪除變數,並嘗試清楚其在IPython中的物件上的一切引用

1.8 基於Qt的富GUI控制檯

啟動方法(同時開啟繪圖功能):

ipython qtconsole --pylab=inline

與終端應用程式相比,優點在於:

  • 介面美觀
  • 支援內嵌圖片顯示
  • 支援以標籤頁的形式啟動多個IPython程序

後面會詳細介紹相關功能。

1.9 matplotlib整合與pylab模式

導致IPython廣泛應用於科學計算領域的重要原因在於它能夠跟matplotlib這樣的庫及其他GUI工具的默契配合。

如果在標準 python shell 中建立一個matplotlib繪圖視窗,就會發現GUI時間迴圈會接管Python回話的控制權,知道該視窗關閉。這顯然無法實現互動式的資料分析和視覺化,因此IPython對各個GUI框架進行了專門的處理以使其能夠與shell配合得天衣無縫。

整合matplotlib方法:

ipython --pylab

這將是IPython完成以下工作:

import numpy
import matplotlib
from matplotlib import pylab, mlab, pyplot
np = numpy
plt = pyplot

from IPython.display import display
from IPython.core.pylabtools import figsize, getfigs

from pylab import *
from numpy import *

將import匯入numpy和matplotlib,並新增互動支援。

使用示例:

img = plt.imread('stinkbug.png')
imshow(img)

plot(randn(1000).cumsum())

二、 使用歷史命令

IPython維護者一個位於硬碟上的一個小型資料庫,包含執行過的每一天命令。這樣的目的在於:

  • 方便的搜尋、自動完成之前執行過的命令
  • 在回話間持久化歷史命令
  • 將輸入輸出歷史記錄到日誌檔案

2.1 搜尋並重用命令歷史

  • 上箭頭鍵:搜尋出命令歷史中第一個與你輸入的字元相匹配的命令。多次按將會在歷史中不斷搜尋。
  • 下箭頭鍵:子命令歷史中向前搜尋。
  • Ctrl-R:部分增量搜素,迴圈在命令歷史中搜素與輸入相符的行。

2.2 輸入和輸出變數

IPython會將輸入和輸出的引用儲存在一些特殊變數中。

  • 最近的輸入個輸出分別儲存在_(一個下劃線)和__(兩個下劃線)兩個變數中。

  • 輸入被儲存在_iX變數中,其中X是輸入的行號。

  • 輸出被儲存在_X變數中,其中X是輸出的行號

幾個與輸入輸出有關的魔術命令:

  • %hist 列印輸入歷史
  • %reset 清空interactive名稱空間,可選擇是否清空輸入和輸出快取
  • %xdel 從IPython中移除特定物件的一切引用

2.3 記錄輸入和輸出

執行%logstart能夠開始記錄控制檯回話,包括輸入和輸出。與之配合的命令有:%logoff%logon%logstate%logstop

三、 與作業系統互動

IPython與作業系統shell結合的非常緊密,可以在IPython中執行shell命令。

與系統相關的命令:

  • !cmd 在系統shell中執行cmd
  • output=!cmd args 執行cmd,並將結果放在output中
  • %bookmark 使用IPython的目錄書籤系統
  • %cd directory 更改工作目錄
  • %pwd 返回系統當前工作目錄
  • %env 以字典形式返回系統環境變數

3.1 shell命令

在IPython中以感嘆號(!)開頭的命令表示其後的所有內容將會在系統shell中執行。

使用!時,還允許使用當前環境中定義的Python值,只需在變數名前加上美元($)符號即可:

foo = 'D:/test*'
!dir $foo

3.2 目錄書籤系統

IPython的目錄書籤系統能夠儲存常用目錄的別名以便實現快速跳轉。書籤能夠持久化儲存。

如:

%bookmark pys 'C:/User/xxx/PyWorkSpace'

定義好書籤之後,就可以在執行魔術命令%cd時使用這些書籤了:

cd pys

列出所有書籤:

%bookmark -l

書籤名與目錄衝突

%bookmark -b pys  #強制使用書籤目錄

四、 軟體開發工具

4.1 互動式偵錯程式

IPython增強了pdb,如Tab鍵自動完成、語法高亮、為異常資訊新增上下文參考等。

程式碼除錯的最佳時機就是錯誤發生的時候,在發生異常之後馬上輸入 %debug 命令將會呼叫偵錯程式,並直接跳轉到引發異常的那個幀。

在pdb中,可以檢視各個幀中的一切物件和資料。預設是從最低階開始的。輸入u(p)或d(own)可以在棧的各級別之間切換。

使用 %pdb on 命令可以開啟自動除錯功能, 即當程式異常時直接開始進行除錯。%pdb off命令可關閉自動除錯。如果不加引數,只輸入%pdb命令則在兩種模式之間切換。

如果你想設定斷點或對函式進行單步除錯。 可以使用帶有 -d 選項的%run命令,這將會在執行指令碼檔案中的程式碼之前開啟偵錯程式。然後,如果你想套設定斷點,那麼使用 b(reak) x 命令,其中x為行號。前進一行使用 n(ext) 命令,進入函式體使用 s(tep into) 然後使用 c(ontinue) 命令即可直行至斷點處。要檢視變數可以使用 !x ,其中x為變數名。

run -d ipython_bug.py
b 12
c

想要精通這個互動式偵錯程式,必須經過大量的實踐才行。如果你習慣了使用某款IDE剛開始使用這種終端型偵錯程式可能會覺得有點麻煩,但慢慢就習慣了。雖然大部分IDE都擁有優秀的GUI偵錯程式,但是在IPython中除錯程式卻往往會帶來更高的生產率。

pdb主要命令如下:

  • h(elp) 顯示命令列表
  • help command 顯示命令的說明文件
  • c(continue) 回覆程式的執行
  • q(uit) 退出偵錯程式
  • b(reak) number 在指定行設定斷點
  • s(tep) 單步進入(step into)函式
  • n(next) 執行當前行(step over),並進入下一行
  • u(p)/d(own) 在函式呼叫棧中向上或向下移動
  • a(rgs) 顯示當前函式的引數
  • debug statement 在新的偵錯程式中呼叫語句statement
  • l(ist) statement 顯示當前行,以及當前棧級別上的上下文參考程式碼
  • w(here) 列印當前位置的完整棧跟蹤

4.2 偵錯程式的其他使用場景

除了上面提到的,還有另外的除錯方法。可以使用set_trace者個特別的函式。下面兩個函式或許會對你很有用(你也可以像我一向直接將其新增到IPython配置中):

def set_trace():
    from IPython.core.debugger import Pdb
    Pdb(color_sheme='Linux').set_trace(sys._getframe().f_back)



def debug(f, *args, **kwargs):
    from IPython.core.debugger import Pdb
    pdb = Pdb(color_scheme='Linux')
    return pdb.runcall(f, *args, **kwargs)

第一個函式set_trace很簡單,你可以將它放在程式碼中任何希望停下來檢視一番的地方:

def calling_things():
    set_trace()  #自定義的除錯函式
    works_fine()
    throws_an_exception()

第二個函式(debug),使你能夠直接在任意函式上使用偵錯程式。

假設我們有如下函式:

def f(x, y, z=1):
    tmp = x + y
    return tmp / z

現在想對其進行單步除錯,按如下方式即可:

debug(f, 1, 2, z=3)

4.3 測試程式碼執行時間:%time 和 %timeit

對於大規模執行時間長的程式,你可能希望測試一下各個部分的執行時間。瞭解某個複雜計算過程到底是哪些函式佔用時間最多。

使用內建的time.clock和time.time函式手工測試程式碼執行時間的方式如下:

import time 
start = time.time()
iterations = 10000
for i in range(iterations):
    # some code
    elapsed_per = (time.time() - start) / iterations

由於這是一個非常常用的功能,所以IPython專門提供了兩個魔術函式%time和%timeit以便自動完成該過程。

%time 一次執行一條語句,然後報告總體執行時間。
%timeit會自動多次執行以產生一個非常準確的平均執行時間。

舉例來說,有一個擁有60萬字符換的陣列,你希望選出其中以foo開頭的字串。現有兩種實現方法:

strs = ['foo', 'foobar', 'baz', 'qux', 'python', 'Guido Van Rossum'] * 100000
method1 = [x for x in strs if x.startswith('foo')]
method2 = [x for x in strs if x[:3] == 'foo']

看上去它們差不多是吧?那麼我們通過%time來驗證一下:

In [11]: %time method1 = [x for x in strs if x.startswith('foo')]
Wall time: 148 ms

In [12]: %time method2 = [x for x in strs if x[:3] == 'foo']
Wall time: 109 ms

用%timeit的結果:

In [13]: %timeit method1 = [x for x in strs if x.startswith('foo')]
10 loops, best of 3: 137 ms per loop

In [14]: %timeit method2 = [x for x in strs if x[:3] == 'foo']
10 loops, best of 3: 101 ms per loop

可見method2執行時間比method1減少近 1/3 。

這告訴了我們一個事實,非常有必要了解Numpy、pandas等庫的效能特點。在大型資料分析程式中,這些不起眼的毫秒數會是不斷累積的!

4.4 基本效能分析:%prun 和 %run -p

程式碼效能分析與程式碼執行時間分析的區別在於,它關注的是耗費時間的位置。

python使用cProfile模組進行效能分析,它會記錄各函式所耗費的時間。

使用方法如下:

python -m cProfile cprof_example.py

執行之後會發現輸出結果是按函式名排序的,這我們很難發現哪裡才是最花時間的地方,一次通常會再加一個 -s 指定排序規則:

python -m cProfile -s cumulative cprof_example.py

除此自外,cProfile還能以程式設計的方式分析任意程式碼塊的效能。IPython為此提供了一個方便的介面。即 %prun 和帶 -p 命令的%run

%prun 格式跟cProfle相同,但它分析的是部分語句而不是整個檔案。

%prun -l 7 -s cumulative run_experiment()

利用 %run -p 也能達到上面的系統命令的效果去無需退出IPython:

%run -p -s cumulative cprof_example.py

4.5 逐行分析函式效能

有時從%prun得到的資訊要麼不足以說明函式的執行時間,要麼複雜到難以理解。這是我們可以使用一個叫做line_profiler的小型庫(可以通過PyPI或其他包管理工具獲取)。其中有一個新的魔術函式%lprun,它可以對一個或多個函式進行逐行效能分析。

五、 IPython HTML Notebook

IPython Notebook目前已成為一種非常棒的互動式計算工具,同時還是科研和教學的一種理想媒介。它有一種基於JSON的文件格式.ipynb,一種流行的演示手段就是使用IPython Notebook再將.ipynb檔案釋出到網上以供所有人查閱。

IPython Notebook是一個運行於命令列上的輕量級伺服器程序。

由於我們是在一個Web瀏覽器上使用Notebook的,因此該伺服器程序可以運行於任何其他地方。甚至可以連線到那些執行在雲服務上的Notebook。

六、 使用IPython進行高效開發的提示

6.1 重新載入模組依賴項

在Python中,當你輸入import some_lib時,其中的程式碼就會執行,且其中的變數、函式和引入項都會被儲存在一個新建的some_lib模組名稱空間中。下次再輸入import some_lib時,就會得到這個模組名稱空間的一個引用。

這種”一次載入“方式對於IPython的互動式開發就會帶來問題。

如果一個檔案import了另一個模組some_lib,在執行了這個檔案之後,需要對some_lib進行修改,然後再重新執行檔案。這時仍然會使用老版本的some_lib。因為原來的some_lib在互動式環境中已經被載入過了。

為了解決這個問題,有兩個方法:

  • 使用reload函式:

    import some_lib
    reload(some_lib)

顯然,當依賴變得更強的時候,就需要在很多地方插入很多的reload。下面的方法用於解決模組的”深度“(遞迴)重載入。

  • 使用dreload函式:它會嘗試重新載入some_lib以及其所有的依賴項。

遺憾的是,以上方法不是萬靈丹,如果真的失效了,重啟IPython試試看。

6.2 程式碼設計提示

保留有意義的物件和資料

如果你在IPython中執行了一下程式碼:

from my_funcs import g

def f(x, y):
    return g(x + y)

def main():
    x = 6
    y = 7.5
    result = x + y

if __name__ == '__main__':
    main()

那麼我們將訪問不到任何結果以及main函式中定義的物件。

更好的方法是直接在該模組的全域性名稱空間中執行main中的程式碼(當然如果你希望該模組是可引用的,可以將這些程式碼放到 if __name__ == '__main__'中)。這樣當你 %run 這段程式碼時就可以訪問到main中定義的所有變量了。

對這個簡單的例子而言可能意義不大,但是對於以後將會遇到的針對大資料集的複雜資料分析問題而言就很重要了。

扁平結構要比巢狀結構好

這個思想來自”Zen of Python“,它對互動式的程式碼開發模式同樣有效。編寫函式和類時應儘量注意低耦合和模組化,這樣能使它們更易於測試、除錯和互動式使用。

附Zen of Python(執行import this即可看到):

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
**Flat is better than nested.**  #扁平好於巢狀
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

不要怕大檔案

在IPython開發時,處理10個小的檔案會比處理一個相對較大的檔案來的更頭疼。更少的檔案意味著需要重新載入的模組更少,編輯時需要在各個檔案之間跳轉的次數也越少。

當然,不建議將此原則極端化。對於一個大型程式碼庫而言,要找到一種合乎邏輯的模組/包架構要花點功夫,這對團隊工作非常重要。每個模組都應該高內聚,並能足夠直觀地找到對應各種功能的函式和類。

七、 IPython高階用法

7.1 讓你的類對IPython更加友好

許多物件(如字典、列表、陣列等)在IPython下都能以漂亮的格式輸出。但是對於自定義的類,就需要自己生成所需的字串輸出。做法是過載 __repr__ 函式即可:

class Message:
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return 'Message: %s' % self.msg

類似的過載 __str__ 能夠在print函式時輸出相應內容

7.2 個性化和配置

IPython shell在外觀(如顏色、提示符、行間距)和行為方面的大部分內容都是可以配置的,如:

  • 顏色方案
  • 輸入輸出提示符
  • 去掉Out提示符後的空行
  • 執行任意的Python語句,你可以定製你希望每次啟動IPython都發生的事
  • 啟動IPython擴充套件
  • 自定義魔術命令或系統別名

所有的配置項都在一個叫做 ipython_config.py 的檔案中,可以在%HOME%/.ipython/目錄中找到。