2012年1月30日 星期一

Python:使用 subprocess 調用外部命令取代舊函數os.system()

以调用 Linux 下的 ping 命令为例:
pingPopen = subprocess.Popen(args='ping -c4 www.google.cn', shell=True)
如果要取得 ping 的输出信息:
pingPopen = subprocess.Popen(args='ping -c4 www.google.cn', shell=True, stdout=subprocess.PIPE)
print pingPopen.stdout.read()
外部程序是在一个子进程里执行的,如果要等待该进程的结束,可以使用 wait():
pingPopen.wait()
wait() 方法会返回一个返回代码。
又或者在创建 Popen 对象后调用 communicate() :
stdReturn = subprocess.Popen(args='ping -c4 www.google.cn', shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()
communicate() 返回一个 (stdout, sterr)。

使用 call() 和 check_all()

subprocess 模块里面有两个方便的函数 call() 和 check_all(),可以直接调用来执行外部程序。
call(*popenargs, **kwargs)
check_call(*popenargs, **kwargs)
它们的参数列表跟 Popen 的构造函数参数列表一样。返回一个 returncode。例如:
subprocess.call('ping -c4 www.google.cn',shel=True)

深入了解with用法

Understanding Python's "with" statement

Fredrik Lundh | October 2006 | Originally posted to online.effbot.org
Judging from comp.lang.python and other forums, Python 2.5’s new withstatement seems to be a bit confusing even for experienced Python programmers.
As most other things in Python, the with statement is actually very simple, once you understand the problem it’s trying to solve. Consider this piece of code:
set things up
    try:
        do something
    finally:
        tear things down
Here, “set things up” could be opening a file, or acquiring some sort of external resource, and “tear things down” would then be closing the file, or releasing or removing the resource. The try-finally construct guarantees that the “tear things down” part is always executed, even if the code that does the work doesn’t finish.
If you do this a lot, it would be quite convenient if you could put the “set things up” and “tear things down” code in a library function, to make it easy to reuse. You can of course do something like
def controlled_execution(callback):
        set things up
        try:
            callback(thing)
        finally:
            tear things down

    def my_function(thing):
        do something

    controlled_execution(my_function)
But that’s a bit verbose, especially if you need to modify local variables. Another approach is to use a one-shot generator, and use the for-in statement to “wrap” the code:
def controlled_execution():
        set things up
        try:
            yield thing
        finally:
            tear things down

    for thing in controlled_execution():
        do something with thing
But yield isn’t even allowed inside a try-finally in 2.4 and earlier. And while that could be fixed (and it has been fixed in 2.5), it’s still a bit weird to use a loop construct when you know that you only want to execute something once.
So after contemplating a number of alternatives, GvR and the python-dev team finally came up with a generalization of the latter, using an object instead of a generator to control the behaviour of an external piece of code:
class controlled_execution:
        def __enter__(self):
            set things up
            return thing
        def __exit__(self, type, value, traceback):
            tear things down

    with controlled_execution() as thing:
         some code
Now, when the “with” statement is executed, Python evaluates the expression, calls the __enter__ method on the resulting value (which is called a “context guard”), and assigns whatever __enter__ returns to the variable given by as. Python will then execute the code body, and no matter what happens in that code, call the guard object’s __exit__ method.
As an extra bonus, the __exit__ method can look at the exception, if any, and suppress it or act on it as necessary. To suppress the exception, just return a true value. For example, the following __exit__ method swallows any TypeError, but lets all other exceptions through:
def __exit__(self, type, value, traceback):
        return isinstance(value, TypeError)
In Python 2.5, the file object has been equipped with __enter__ and __exit__methods; the former simply returns the file object itself, and the latter closes the file:
>>> f = open("x.txt")
    >>> f
    <open file 'x.txt', mode 'r' at 0x00AE82F0>
    >>> f.__enter__()
    <open file 'x.txt', mode 'r' at 0x00AE82F0>
    >>> f.read(1)
    'X'
    >>> f.__exit__(None, None, None)
    >>> f.read(1)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: I/O operation on closed file
so to open a file, process its contents, and make sure to close it, you can simply do:
with open("x.txt") as f:
    data = f.read()
    do something with data
This wasn’t very difficult, was it?

optparse 模組取代掉getopt舊式模組

import optparse取代import getopt


下面有事例:



# parserforoption.py
import optparse
import socket


def connectTo(toaddr,toport):
    toaddr = str(toaddr)
    toport = int(toport)
    print "Create socket objetct for {addr},port {port}".format(addr=toaddr,port=toport)
        
    s = socket.socket()
    toaddr = socket.gethostbyname(toaddr)
    
    try:
        s.connect((toaddr, toport))
        print "Connected to {addr} on {port}".format(addr=toaddr , port=toport)
        return True
    except socket.error as e:
        print "Connection to {addr} on {port} FAILED!".format(addr=toaddr,port=toport)
        return False


if __name__ == "__main__":
    parser = optparse.OptionParser()
    parser.add_option("-a","--address",dest="address",default= "localhost",help="address for server",metavar="ADDRESS")
    parser.add_option("-p","--port",dest="port",default= "localhost",help="port for server",metavar="PORT")
    (options,args )= parser.parse_args()
    toaddr = options.address
    toport = options.port
    
    #if connectTo('www.google.com.tw',80):
    if connectTo(toaddr,toport):
        print "GET STATE: OK."
    else:
        print "GET STATE: FAILED."


輸出結果


C:\Python27>python parserforoption.py -h
Usage: parserforoption.py [options]


Options:
  -h, --help            show this help message and exit
  -a ADDRESS, --address=ADDRESS
                        address for server
  -p PORT, --port=PORT  port for server





C:\Python27>python parserforoption.py -a 140.112.27.28 -p 80
Create socket objetct for 140.112.27.28,port 80
Connected to 140.112.27.28 on 80
GET STATE: OK.

2012年1月28日 星期六

用python 作科學計算

要利用python來進行訊號處理,我們除了python需要安裝外,還需要再安裝幾種不同的library。這些library或tool都有提供不同作業系統的版本,而在這裡,我們使用的作業系統是windows,因此,在下載時需要特別注意下載的版本。而雖然python已經更新到3.0版以上,但根據我的經驗,較新版本的scipy或numpy都不是很穩定,所以建議大家仍然使用較舊的版本,以下為測試過較好的組合。

1. 
python: python主程式
    下載並安裝python-2.5.4.msi
    
http://www.python.org/download/releases/2.5.4/

2. numpy: 提供了python在進行數學運算時,有用的N維度陣列形態、線性代數以及傅利葉轉換等函式。
    下載並安裝numpy-1.2.1-win32-superpack-python2.5.exe
    
http://sourceforge.net/projects/numpy/files/NumPy/1.2.1/numpy-1.2.1-win32-superpack-python2.5.exe/download

3. scipy: 以numpy為基礎,分門別類的提供了許多數學、工程或科學運算上所需要的函式(類似Matlab的toolbox)。
    下載並安裝scipy-0.6.0.win32-py2.5.msi
    
http://sourceforge.net/projects/scipy/files/scipy/0.6.0/scipy-0.6.0.win32-py2.5.msi/download

4. matplotlib: 提供容易使用(matlab-like)的作圖函式
    下載並安裝matplotlib-0.98.5.3.win32-py2.5.exe
    
http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-0.98.5/matplotlib-0.98.5.3.win32-py2.5.exe/download
    
    在以SSH連線至Linux運用matplotlib畫圖時,會遇到RuntimeError: could not open display的錯誤,這時需要修改matplotlibrc中backend的預設值。
    要知道matplotlibrc的位置,可以使用下面的方式得知[ref]:
>>> import matplotlib
>>> matplotlib.matplotlib_fname()
'/home/foo/.matplotlib/matplotlibrc'
     將其matplotlibrc中的backend: GTKAgg改為backend: Agg,便可避免掉該錯誤。

5. ipython: 互動式介面(ipython並非一定要安裝,但是如果你已經熟悉Matlab的使用環境,那麼ipython可以讓你更快的熟悉python的使用)。
    下載並安裝ipython-0.9.1.win32-setup.exe
    
http://ipython.scipy.org/dist/ipython-0.9.1.win32-setup.exe

    windows使用者需要額外下載並安裝pyreadline-1.5-win32-setup.exe
    
http://ipython.scipy.org/dist/pyreadline-1.5-win32-setup.exe

如果上述的安裝都成功沒有錯誤的話,你現在就可以馬上透過ipython的介面來體會python了。首先到"程式集"下進到"ipython"的目錄,點選"IPython"就可以啟動ipython,試試在命令列上打上一個簡單的運算吧!


20101009更新:在Windows7下,以下的組合是可以正常工作的:
1. python-2.6.5.msi
2. numpy-1.5.0b1-win32-superpack-python2.6.exe
3. scipy-0.8.0-win32-superpack-python2.6.exe
4. matplotlib-1.0.0.win32-py2.6.exe
5. ipython-0.10.win32-setup.exe
6. pyreadline-1.5-win32-setup.exe
20101025更新:在Windows7下,官方的matplotlib-1.0.0.win32-py2.6.exe與IPython併用時,在使用pyplot模組中的show()函式時,有可能會造成IPython被block住的現象。經過測試後,可下載第三方編譯的 matplotlib-1.0.0.win32-py2.6.exe解決(如附件),提供者網址為http://www.lfd.uci.edu/~gohlke/,原始檔案可自該作者網頁中下載。

2012年1月23日 星期一

如何使用traceback

Python 錯誤處理
by thinker
關鍵字: 
程式執行時,若發生錯誤該怎麼辨?許多時侯,我們都假設程式不應該出錯。但,出錯是在所難免,往往有當初不預期的狀況出現。因此,保留適當的訊息變的非常重要。這些訊息,是 programmer 解決問題的重要依據。尤其網路的 service ,通常在遠端的機器上執行,若不保留足夠的資訊,往往無法重建錯誤發生當時的狀況。因此, error message 的 log 就變的非常重要。
Python 對錯誤的反應,往往是產生 exception 。在 exception 發生時,會將 exception 的內容輸出到 stderr 。似乎只需將這些訊息保留下來, programmer 就能據以追蹤問題發生的原因。但事實上,以預設的錯誤訊息進行除錯,常常很沒有效率。
Traceback (most recent call last):
  File "www/cgi/new.py", line 51, in <module>
    skel(new_log)
  File "www/cgi/new.py", line 44, in skel
    raise '....'
Python 預設的錯誤訊息只有輸出 exception 發生時,程式呼叫的次序,也就是 traceback (上例)。 traceback 只能指出錯誤發生的位置,卻沒有錯誤發生時的變數內容。在不知道變數內容的狀況下,有時我們無法理解程式為何會出錯,而產生疑惑,使的除錯更加困難。這樣的狀況並不少見,因此我很難加以忽視。例如,當我們透過 DB-API 執行 SQL command 時,錯誤原因的判定往往有賴於 command 的參數內容。例如:
001 cx = sqlite3.connect("test.db")
002 cur = cx.cursor()
003 cur.execute('select * from uid = ?', (uid,))
當第三行執行時, Python 通常只會告訴你錯誤發生在該行。然而,單就該行 query ,實在難以斷定錯誤的原因。很有可能 database 裡的 uid 欄位是整數,而傳入的參數卻是字串,因而造成錯誤。這時若能保留 uid 變數的內容,將能很快速的界定錯誤的起因。

捕捉

因此,我們應該改寫 Python 處理 exception 方式,保留錯誤發生時的變數內容。要做到這一點,最簡單的方式就是在程式的進入點,以 try...except... 捕捉所有的錯誤。如下例:
001 if __name__ == '__main__':
002     try:
003         main()
004     except:
005         # exception handler
006         pass
當我們捕捉到錯誤時,我們可以透過 sys module 下的 exc_info() 取得 traceback 的內容,並據以取得所需的資訊。
001 import sys
002 tp, val, tb = sys.exc_info()
  • tp exception 的 type
  • val 是 raise 時,第二個參數
  • tb 則保留了 exception 發生時的呼叫狀態和堆疊內容
例如
001 raise ValueError, 'invalid argument'
捕捉到的內容為
  • tp = ValueError
  • val = ValueError('invalid argument')
於是,我們就可以用下面的程式輸出錯誤內容
001 import sys
002 tp, val, tb = sys.exc_info()
003 print >> sys.stderr, '%s: %s' % (str(tp), str(val)

traceback

traceback 保留程式執行當中,呼叫的次序,和 frame 的內容。透過 traceback ,我們能取得程式呼叫的流程,呼叫的 function 名稱,呼叫者的檔案名稱和行數,甚至是每一個 function 的 global 和 local namespace 的內容。exc_info() 傳回的 traceback 保存的是最外層 function call 的 frame , 也就是程式進入點所在的 module 。透過 traceback.tb_next 可以取得往內一層的 traceback ,可以取得另一個 frame 。所謂的內、外層是以程式呼叫的次序決定。 程式呼叫 A() , A() 呼叫 B() ,B() 再呼叫C() 時,呼叫 A() 的程式是最外層,然後是 A() , 最內層是 C() 。每一次 function 呼叫時,都會建立一個 frame ,以儲存 function 執行時的狀態。因此,這時系統的狀態如下圖:
exc_info() 傳回的是最外層,也就是右邊最上方的 traceback。透過該 traceback ,我們能取得每一層呼叫的 frame 。 frame 記錄執行的 function ,執行的位置,和執行時的 local 和 global namespace 。

function 資訊

透過 frame 的 f_code 屬性,能取得 function 的相關資訊。例如,
  • frame.f_code.co_name 記錄 function 或 module 名稱
  • frame.f_code.co_filename 記錄 function 所在的檔案

變數內容

透過 frame 的 f_locals 和 f_globals 可以取得 frame 執行時的 local 和 global namespace ,兩者皆為 dictionary 。透過該 dictionary ,我們可以取得所有變數的名稱和變數的值。例如,下面的 code 印出所有變數的內容。
001 import sys
002 tp, val, tb = sys.exc_info()
003 while tb:
004     frame = tb.tb_frame
005     print >> sys.stderr, 'locals = %s' % (repr(frame.f_locals))
006     print >> sys.stderr, 'globals = %s' % (repr(frame.f_globals))
007     print >> sys.stderr
008     tb = tb.tb_next
009     pass
透過 traceback 的 tb_next 屬性,我們能一層接著一層,由外往內取得所有的變數內容。

excepthook

前面我們在程式的進入點,透過 try...except.... 語法,捕捉所有漏網的 exception 。這是一種方法,但如果你的系統有許多隻程式組成時,就必需不斷重複這些 code 。另一種方式是設定 excepthook ,任何未被捕獲的 exception ,最後就會導致程式結束。在這種情況下,程式結束之前會先呼叫 excepthook,我們可以設定 excepthook ,以改變錯誤訊息內容。如下:
001 import sys
002 def myhook(tp, val, tb):
003     # .......
004 sys.excepthook = myhook
hook 的三個參數,正是 exc_info() 傳回的三個變數。

結論

程式難免會有意料之外的錯誤,最差的狀況就是把所有的錯誤隱藏起來。將錯誤忠實的記錄下,還是比較好的策略。透過將 exception 的發生,將 frame 次序,和變數的內容記錄下來,能很有效的改善除錯效率。據我過去的經驗,通常我們只需記錄最內層 frame 的 local namespace 就足夠了。但,如果空間不是問題,將所有 frame 的資訊記錄下來,那就更萬無一失了。